pEpObjCAdapter/PEPPassphraseCache.m
author Dirk Zimmermann <dz@pep.security>
Mon, 29 Jun 2020 16:39:43 +0200
branchIOSAD-172
changeset 1537 748f192b92e4
parent 1535 b5796927e5b6
child 1538 7b2207f43329
permissions -rw-r--r--
IOSAD-172 Docs
     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         // Note newer passphrases are always added to the _end_
    87         [self.mutablePassphraseEntries addObject:entry];
    88         if (self.mutablePassphraseEntries.count > s_maxNumberOfPassphrases) {
    89             [self.mutablePassphraseEntries removeObjectAtIndex:0];
    90         }
    91     });
    92 }
    93 
    94 - (NSArray<NSString *> *)passphrases
    95 {
    96     NSMutableArray *resultingPassphrases = [NSMutableArray
    97                                             arrayWithCapacity:s_maxNumberOfPassphrases + 1];
    98     dispatch_sync(self.queue, ^{
    99         for (PEPPassphraseCacheEntry *entry in self.mutablePassphraseEntries) {
   100             if (![self isExpiredPassphraseEntry:entry]) {
   101                 [resultingPassphrases addObject:entry.passphrase];
   102             }
   103         }
   104     });
   105     [resultingPassphrases insertObject:@"" atIndex:0];
   106     return [NSArray arrayWithArray:resultingPassphrases];
   107 }
   108 
   109 /// Remove password entries that have timed out.
   110 /// - Note: Assumes it gets called on `queue`.
   111 - (void)removeStaleEntries
   112 {
   113     NSMutableArray *resultingPassphrases = [NSMutableArray
   114                                             arrayWithCapacity:s_maxNumberOfPassphrases];
   115 
   116     for (PEPPassphraseCacheEntry *entry in self.mutablePassphraseEntries) {
   117         if (![self isExpiredPassphraseEntry:entry]) {
   118             [resultingPassphrases addObject:entry];
   119         }
   120     }
   121 
   122     [self.mutablePassphraseEntries removeAllObjects];
   123     [self.mutablePassphraseEntries addObjectsFromArray:resultingPassphrases];
   124 }
   125 
   126 - (void)resetTimeoutForPassphrase:(NSString *)passphrase
   127 {
   128     if ([passphrase isEqualToString:@""]) {
   129         // ignore the empty passphrase, it's always there
   130         return;
   131     }
   132 
   133     dispatch_sync(self.queue, ^{
   134         for (PEPPassphraseCacheEntry *entry in self.mutablePassphraseEntries) {
   135             if ([entry.passphrase isEqualToString:passphrase]) {
   136                 entry.dateAdded = [NSDate date];
   137             }
   138         }
   139     });
   140 }
   141 
   142 - (BOOL)isExpiredPassphraseEntry:(PEPPassphraseCacheEntry *)passphraseEntry
   143 {
   144     NSDate *now = [NSDate date];
   145     NSDate *minimum = [now dateByAddingTimeInterval:-self.timeout];
   146     NSTimeInterval minimumTimeInterval = [minimum timeIntervalSinceReferenceDate];
   147 
   148     if ([passphraseEntry.dateAdded timeIntervalSinceReferenceDate] < minimumTimeInterval) {
   149         return YES;
   150     }
   151 
   152     return NO;
   153 }
   154 
   155 - (NSArray<PEPPassphraseCacheEntry *> *)sortedArrayByDateNewestFirst:(NSArray<PEPPassphraseCacheEntry *> *)array
   156 {
   157     return [array sortedArrayUsingComparator:^NSComparisonResult(PEPPassphraseCacheEntry *entry1,
   158                                                                  PEPPassphraseCacheEntry *entry2) {
   159         NSTimeInterval interval1 = [entry1.dateAdded timeIntervalSinceReferenceDate];
   160         NSTimeInterval interval2 = [entry2.dateAdded timeIntervalSinceReferenceDate];
   161 
   162         if (interval1 > interval2) {
   163             return NSOrderedAscending;
   164         } else if (interval1 < interval2) {
   165             return NSOrderedDescending;
   166         } else {
   167             return NSOrderedSame;
   168         }
   169     }];
   170 }
   171 
   172 @end