pEpObjCAdapter/PEPPassphraseCache.m
author Dirk Zimmermann <dz@pep.security>
Mon, 29 Jun 2020 16:39:43 +0200
branchIOSAD-172
changeset 1532 455eb191aa48
parent 1531 be2d0d6fcf2f
child 1533 ba55bf1eacf6
permissions -rw-r--r--
IOSAD-172 Hold true to the public signature
     1 //
     2 //  PEPPassphraseCache.m
     3 //  pEpObjCAdapter
     4 //
     5 //  Created by Dirk Zimmermann on 25.06.20.
     6 //  Copyright © 2020 p≡p. All rights reserved.
     7 //
     8 
     9 #import "PEPPassphraseCache.h"
    10 
    11 #import "PEPPassphraseCacheInternal.h"
    12 
    13 #import "PEPPassphraseCacheEntry.h"
    14 
    15 static NSUInteger s_maxNumberOfPassphrases = 20;
    16 static NSTimeInterval s_defaultTimeoutInSeconds = 10 * 60;
    17 static NSTimeInterval s_defaultCheckExpiryInterval = 60;
    18 
    19 @interface PEPPassphraseCache ()
    20 
    21 /// Timeout of passwords in seconds.
    22 @property (nonatomic) NSTimeInterval timeout;
    23 
    24 @property (nonatomic) dispatch_queue_t queue;
    25 @property (nonatomic) NSMutableArray<PEPPassphraseCacheEntry *> *mutablePassphraseEntries;
    26 @property (nonatomic) dispatch_source_t timer;
    27 
    28 @end
    29 
    30 @implementation PEPPassphraseCache
    31 
    32 static PEPPassphraseCache *s_sharedInstance;
    33 
    34 + (void)initialize
    35 {
    36     static BOOL initialized = NO;
    37     if (!initialized) {
    38         initialized = YES;
    39         s_sharedInstance = [[PEPPassphraseCache alloc] init];
    40     }
    41 }
    42 
    43 + (instancetype)sharedInstance
    44 {
    45     return s_sharedInstance;
    46 }
    47 
    48 /// Internal constructor (for now).
    49 - (instancetype)initWithPassphraseTimeout:(NSTimeInterval)timeout
    50                       checkExpiryInterval:(NSTimeInterval)checkExpiryInterval
    51 {
    52     self = [super init];
    53     if (self) {
    54         _timeout = timeout;
    55         _queue = dispatch_queue_create("PEPPassphraseCache Queue", DISPATCH_QUEUE_SERIAL);
    56         _mutablePassphraseEntries = [NSMutableArray arrayWithCapacity:s_maxNumberOfPassphrases];
    57 
    58         // we have a strong reference to the timer, but the timer doesn't have one to us
    59         typeof(self) __weak weakSelf = self;
    60 
    61         _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _queue);
    62         dispatch_source_set_timer(_timer,
    63                                   DISPATCH_TIME_NOW,
    64                                   checkExpiryInterval * NSEC_PER_SEC,
    65                                   checkExpiryInterval / 10 * NSEC_PER_SEC);
    66         dispatch_source_set_event_handler(_timer, ^{
    67             [weakSelf removeStaleEntries];
    68         });
    69         dispatch_resume(_timer);
    70     }
    71     return self;
    72 }
    73 
    74 /// Public constructor with default values.
    75 - (instancetype)init
    76 {
    77     return [self initWithPassphraseTimeout:s_defaultTimeoutInSeconds
    78                        checkExpiryInterval:s_defaultCheckExpiryInterval];
    79 }
    80 
    81 - (void)addPassphrase:(NSString *)passphrase
    82 {
    83     PEPPassphraseCacheEntry *entry = [[PEPPassphraseCacheEntry alloc]
    84                                       initWithPassphrase:passphrase];
    85     dispatch_sync(self.queue, ^{
    86         [self.mutablePassphraseEntries addObject:entry];
    87         if (self.mutablePassphraseEntries.count > s_maxNumberOfPassphrases) {
    88             [self.mutablePassphraseEntries removeObjectAtIndex:0];
    89         }
    90     });
    91 }
    92 
    93 - (NSArray<NSString *> *)passphrases
    94 {
    95     NSMutableArray *resultingPassphrases = [NSMutableArray
    96                                             arrayWithCapacity:s_maxNumberOfPassphrases + 1];
    97     [resultingPassphrases addObject:@""];
    98     dispatch_sync(self.queue, ^{
    99         for (PEPPassphraseCacheEntry *entry in self.mutablePassphraseEntries) {
   100             [resultingPassphrases addObject:entry.passphrase];
   101         }
   102     });
   103     return [NSArray arrayWithArray:resultingPassphrases];
   104 }
   105 
   106 /// Remove password entries that have timed out.
   107 /// - Note: Assumes it gets called on `queue`.
   108 - (void)removeStaleEntries
   109 {
   110     NSMutableArray *resultingPassphrases = [NSMutableArray
   111                                             arrayWithCapacity:s_maxNumberOfPassphrases];
   112 
   113     for (PEPPassphraseCacheEntry *entry in self.mutablePassphraseEntries) {
   114         if (![self isExpiredPassphraseEntry:entry]) {
   115             [resultingPassphrases addObject:entry];
   116         }
   117     }
   118 
   119     [self.mutablePassphraseEntries removeAllObjects];
   120     [self.mutablePassphraseEntries addObjectsFromArray:resultingPassphrases];
   121 }
   122 
   123 - (void)resetTimeoutForPassphrase:(NSString *)passphrase
   124 {
   125     if ([passphrase isEqualToString:@""]) {
   126         // ignore the empty passphrase, it's always there
   127         return;
   128     }
   129 
   130     dispatch_sync(self.queue, ^{
   131         for (PEPPassphraseCacheEntry *entry in self.mutablePassphraseEntries) {
   132             if ([entry.passphrase isEqualToString:passphrase]) {
   133                 entry.dateAdded = [NSDate date];
   134             }
   135         }
   136     });
   137 }
   138 
   139 - (BOOL)isExpiredPassphraseEntry:(PEPPassphraseCacheEntry *)passphraseEntry
   140 {
   141     NSDate *now = [NSDate date];
   142     NSDate *minimum = [now dateByAddingTimeInterval:-self.timeout];
   143     NSTimeInterval minimumTimeInterval = [minimum timeIntervalSinceReferenceDate];
   144 
   145     if ([passphraseEntry.dateAdded timeIntervalSinceReferenceDate] < minimumTimeInterval) {
   146         return YES;
   147     }
   148 
   149     return NO;
   150 }
   151 
   152 @end