From f0dcc4c34c3333c5550b0918c9b6748b70138f21 Mon Sep 17 00:00:00 2001 From: Maarten Billemont Date: Fri, 13 Sep 2013 08:14:58 -0400 Subject: [PATCH] Fixed queuing of password generation logic. [IMPROVED] Removed password logic out of MPEntities so that it can be backgrounded without relying on the persistence layer staying up. [IMPROVED] Some workload removed from the main thread. --- .idea/inspectionProfiles/Project_Default.xml | 1 + External/UbiquityStoreManager | 2 +- MasterPassword/ObjC/MPAlgorithm.h | 14 +- MasterPassword/ObjC/MPAlgorithmV0.m | 261 ++++++++++++++++-- MasterPassword/ObjC/MPAlgorithmV1.m | 4 +- MasterPassword/ObjC/MPAppDelegate_Key.m | 6 +- MasterPassword/ObjC/MPAppDelegate_Store.m | 22 +- MasterPassword/ObjC/MPElementEntity.h | 1 - MasterPassword/ObjC/MPElementEntity.m | 1 - MasterPassword/ObjC/MPEntities.h | 6 - MasterPassword/ObjC/MPEntities.m | 159 +---------- .../ObjC/iOS/MPElementListAllViewController.m | 5 +- .../ObjC/iOS/MPMainViewController.m | 39 +-- .../ObjC/iOS/MPTypeViewController.m | 2 +- 14 files changed, 310 insertions(+), 213 deletions(-) diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index e947e785..87f2daf9 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -9,6 +9,7 @@ + diff --git a/External/UbiquityStoreManager b/External/UbiquityStoreManager index a2ce82ea..0202de6c 160000 --- a/External/UbiquityStoreManager +++ b/External/UbiquityStoreManager @@ -1 +1 @@ -Subproject commit a2ce82ea58d8a02237f33fdc4393259b09cb4967 +Subproject commit 0202de6cf5b1f2847f4726af0788f6fce9683b32 diff --git a/MasterPassword/ObjC/MPAlgorithm.h b/MasterPassword/ObjC/MPAlgorithm.h index 51cf976c..622c2709 100644 --- a/MasterPassword/ObjC/MPAlgorithm.h +++ b/MasterPassword/ObjC/MPAlgorithm.h @@ -16,6 +16,7 @@ // #import "MPKey.h" +#import "MPElementStoredEntity.h" #import "MPElementGeneratedEntity.h" #define MPAlgorithmDefaultVersion 1 @@ -37,8 +38,19 @@ - (NSString *)classNameOfType:(MPElementType)type; - (Class)classOfType:(MPElementType)type; -- (NSString *)generateContentForElement:(MPElementGeneratedEntity *)element usingKey:(MPKey *)key; - (NSString *)generateContentNamed:(NSString *)name ofType:(MPElementType)type withCounter:(NSUInteger)counter usingKey:(MPKey *)key; +- (NSString *)storedContentForElement:(MPElementStoredEntity *)element usingKey:(MPKey *)key; + +- (void)saveContent:(NSString *)clearContent toElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey; +- (NSString *)resolveContentForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey; +- (void)resolveContentForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey + result:(void (^)(NSString *result))resultBlock; + +- (void)importProtectedContent:(NSString *)protectedContent protectedByKey:(MPKey *)importKey + intoElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey; +- (void)importClearTextContent:(NSString *)clearContent intoElement:(MPElementEntity *)element + usingKey:(MPKey *)elementKey; +- (NSString *)exportContentForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey; @end diff --git a/MasterPassword/ObjC/MPAlgorithmV0.m b/MasterPassword/ObjC/MPAlgorithmV0.m index 831bc504..abdcd46d 100644 --- a/MasterPassword/ObjC/MPAlgorithmV0.m +++ b/MasterPassword/ObjC/MPAlgorithmV0.m @@ -203,27 +203,6 @@ Throw(@"Type not supported: %d", type); } -- (NSString *)generateContentForElement:(MPElementGeneratedEntity *)element usingKey:(MPKey *)key { - - if (!element) - return nil; - - if (!(element.type & MPElementTypeClassGenerated)) { - err(@"Incorrect type (is not MPElementTypeClassGenerated): %@, for: %@", [self nameOfType:element.type], element.name); - return nil; - } - if (!element.name.length) { - err(@"Missing name."); - return nil; - } - if (!key.keyData.length) { - err(@"Missing key."); - return nil; - } - - return [self generateContentNamed:element.name ofType:element.type withCounter:element.counter usingKey:key]; -} - - (NSString *)generateContentNamed:(NSString *)name ofType:(MPElementType)type withCounter:(NSUInteger)counter usingKey:(MPKey *)key { static NSDictionary *MPTypes_ciphers = nil; @@ -247,14 +226,14 @@ const char *seedBytes = seed.bytes; // Determine the cipher from the first seed byte. - assert([seed length]); + NSAssert([seed length], @"Missing seed."); NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:[self classNameOfType:type]] valueForKey:[self nameOfType:type]]; NSString *cipher = [typeCiphers objectAtIndex:htons(seedBytes[0]) % [typeCiphers count]]; trc(@"type %@, ciphers: %@, selected: %@", [self nameOfType:type], typeCiphers, cipher); // Encode the content, character by character, using subsequent seed bytes and the cipher. - assert([seed length] >= [cipher length] + 1); + NSAssert([seed length] >= [cipher length] + 1, @"Insufficient seed bytes to encode cipher."); NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]]; for (NSUInteger c = 0; c < [cipher length]; ++c) { uint16_t keyByte = htons(seedBytes[c + 1]); @@ -270,4 +249,240 @@ return content; } +- (NSString *)storedContentForElement:(MPElementStoredEntity *)element usingKey:(MPKey *)key { + + return [self decryptContent:element.contentObject usingKey:key]; +} + +- (void)saveContent:(NSString *)clearContent toElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey { + + NSAssert([elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user."); + switch (element.type) { + case MPElementTypeGeneratedMaximum: + case MPElementTypeGeneratedLong: + case MPElementTypeGeneratedMedium: + case MPElementTypeGeneratedBasic: + case MPElementTypeGeneratedShort: + case MPElementTypeGeneratedPIN: { + NSAssert(NO, @"Cannot save content to element with generated type %d.", element.type); + break; + } + + case MPElementTypeStoredPersonal: { + NSAssert([element isKindOfClass:[MPElementStoredEntity class]], + @"Element with stored type %d is not an MPElementStoredEntity, but a %@.", element.type, [element class]); + + NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding] + encryptWithSymmetricKey:[elementKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES]; + ((MPElementStoredEntity *)element).contentObject = encryptedContent; + break; + } + case MPElementTypeStoredDevicePrivate: { + NSAssert([element isKindOfClass:[MPElementStoredEntity class]], + @"Element with stored type %d is not an MPElementStoredEntity, but a %@.", element.type, [element class]); + + NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding] + encryptWithSymmetricKey:[elementKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES]; + NSDictionary *elementQuery = [self queryForDevicePrivateElementNamed:element.name]; + if (!encryptedContent) + [PearlKeyChain deleteItemForQuery:elementQuery]; + else + [PearlKeyChain addOrUpdateItemForQuery:elementQuery withAttributes:@{ + (__bridge id)kSecValueData : encryptedContent, +#if TARGET_OS_IPHONE + (__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly, +#endif + }]; + ((MPElementStoredEntity *)element).contentObject = nil; + break; + } + } +} + +- (NSString *)resolveContentForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey { + + dispatch_group_t group = dispatch_group_create(); + dispatch_group_enter( group ); + __block NSString *result = nil; + [self resolveContentForElement:element usingKey:elementKey result:^(NSString *result_) { + result = result_; + dispatch_group_leave( group ); + }]; + dispatch_group_wait( group, DISPATCH_TIME_FOREVER ); + + return result; +} + +- (void)resolveContentForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey result:(void (^)(NSString *result))resultBlock { + + NSAssert([elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user."); + switch (element.type) { + case MPElementTypeGeneratedMaximum: + case MPElementTypeGeneratedLong: + case MPElementTypeGeneratedMedium: + case MPElementTypeGeneratedBasic: + case MPElementTypeGeneratedShort: + case MPElementTypeGeneratedPIN: { + NSAssert([element isKindOfClass:[MPElementGeneratedEntity class]], + @"Element with generated type %d is not an MPElementGeneratedEntity, but a %@.", element.type, [element class]); + + NSString *name = element.name; + MPElementType type = element.type; + NSUInteger counter = ((MPElementGeneratedEntity *)element).counter; + id algorithm = nil; + if (!element.name.length) + err(@"Missing name."); + else if (!elementKey.keyData.length) + err(@"Missing key."); + else + algorithm = element.algorithm; + + dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{ + NSString *result = [algorithm generateContentNamed:name ofType:type withCounter:counter usingKey:elementKey]; + resultBlock( result ); + } ); + break; + } + + case MPElementTypeStoredPersonal: { + NSAssert([element isKindOfClass:[MPElementStoredEntity class]], + @"Element with stored type %d is not an MPElementStoredEntity, but a %@.", element.type, [element class]); + + NSData *encryptedContent = ((MPElementStoredEntity *)element).contentObject; + + dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{ + NSString *result = [self decryptContent:encryptedContent usingKey:elementKey]; + resultBlock( result ); + } ); + break; + } + case MPElementTypeStoredDevicePrivate: { + NSAssert([element isKindOfClass:[MPElementStoredEntity class]], + @"Element with stored type %d is not an MPElementStoredEntity, but a %@.", element.type, [element class]); + + NSDictionary *elementQuery = [self queryForDevicePrivateElementNamed:element.name]; + NSData *encryptedContent = [PearlKeyChain dataOfItemForQuery:elementQuery]; + + dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{ + NSString *result = [self decryptContent:encryptedContent usingKey:elementKey]; + resultBlock( result ); + } ); + break; + } + } +} + +- (void)importProtectedContent:(NSString *)protectedContent protectedByKey:(MPKey *)importKey + intoElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey { + + NSAssert([elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user."); + switch (element.type) { + case MPElementTypeGeneratedMaximum: + case MPElementTypeGeneratedLong: + case MPElementTypeGeneratedMedium: + case MPElementTypeGeneratedBasic: + case MPElementTypeGeneratedShort: + case MPElementTypeGeneratedPIN: + break; + + case MPElementTypeStoredPersonal: { + NSAssert([element isKindOfClass:[MPElementStoredEntity class]], + @"Element with stored type %d is not an MPElementStoredEntity, but a %@.", element.type, [element class]); + if ([importKey.keyID isEqualToData:elementKey.keyID]) + ((MPElementStoredEntity *)element).contentObject = [protectedContent decodeBase64]; + + else { + NSString *clearContent = [self decryptContent:[protectedContent decodeBase64] usingKey:importKey]; + [self importClearTextContent:clearContent intoElement:element usingKey:elementKey]; + } + break; + } + + case MPElementTypeStoredDevicePrivate: + break; + } +} + +- (void)importClearTextContent:(NSString *)clearContent intoElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey { + + NSAssert([elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user."); + switch (element.type) { + case MPElementTypeGeneratedMaximum: + case MPElementTypeGeneratedLong: + case MPElementTypeGeneratedMedium: + case MPElementTypeGeneratedBasic: + case MPElementTypeGeneratedShort: + case MPElementTypeGeneratedPIN: + break; + + case MPElementTypeStoredPersonal: { + [self saveContent:clearContent toElement:element usingKey:elementKey]; + break; + } + + case MPElementTypeStoredDevicePrivate: + break; + } +} + +- (NSString *)exportContentForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey { + + NSAssert([elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user."); + if (!(element.type & MPElementFeatureExportContent)) + return nil; + + NSString *result = nil; + switch (element.type) { + case MPElementTypeGeneratedMaximum: + case MPElementTypeGeneratedLong: + case MPElementTypeGeneratedMedium: + case MPElementTypeGeneratedBasic: + case MPElementTypeGeneratedShort: + case MPElementTypeGeneratedPIN: { + result = nil; + break; + } + + case MPElementTypeStoredPersonal: { + NSAssert([element isKindOfClass:[MPElementStoredEntity class]], + @"Element with stored type %d is not an MPElementStoredEntity, but a %@.", element.type, [element class]); + result = [((MPElementStoredEntity *)element).contentObject encodeBase64]; + break; + } + + case MPElementTypeStoredDevicePrivate: { + result = nil; + break; + } + } + + return result; +} + +- (BOOL)migrateExplicitly:(BOOL)explicit { + + return NO; +} + +- (NSDictionary *)queryForDevicePrivateElementNamed:(NSString *)name { + + return [PearlKeyChain createQueryForClass:kSecClassGenericPassword + attributes:@{ + (__bridge id)kSecAttrService : @"DevicePrivate", + (__bridge id)kSecAttrAccount : name + } + matches:nil]; +} + +- (NSString *)decryptContent:(NSData *)encryptedContent usingKey:(MPKey *)key { + + NSData *decryptedContent = nil; + if ([encryptedContent length]) + decryptedContent = [encryptedContent decryptWithSymmetricKey:[key subKeyOfLength:PearlCryptKeySize].keyData padding:YES]; + if (!decryptedContent) + return nil; + + return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding]; +} + @end diff --git a/MasterPassword/ObjC/MPAlgorithmV1.m b/MasterPassword/ObjC/MPAlgorithmV1.m index 4d69b77e..b7b8fa42 100644 --- a/MasterPassword/ObjC/MPAlgorithmV1.m +++ b/MasterPassword/ObjC/MPAlgorithmV1.m @@ -68,13 +68,13 @@ const unsigned char *seedBytes = seed.bytes; // Determine the cipher from the first seed byte. - assert([seed length]); + NSAssert([seed length], @"Missing seed."); NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:[self classNameOfType:type]] valueForKey:[self nameOfType:type]]; NSString *cipher = [typeCiphers objectAtIndex:seedBytes[0] % [typeCiphers count]]; trc(@"type %@, ciphers: %@, selected: %@", [self nameOfType:type], typeCiphers, cipher); // Encode the content, character by character, using subsequent seed bytes and the cipher. - assert([seed length] >= [cipher length] + 1); + NSAssert([seed length] >= [cipher length] + 1, @"Insufficient seed bytes to encode cipher."); NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]]; for (NSUInteger c = 0; c < [cipher length]; ++c) { uint16_t keyByte = seedBytes[c + 1]; diff --git a/MasterPassword/ObjC/MPAppDelegate_Key.m b/MasterPassword/ObjC/MPAppDelegate_Key.m index fd09f616..d17a7023 100644 --- a/MasterPassword/ObjC/MPAppDelegate_Key.m +++ b/MasterPassword/ObjC/MPAppDelegate_Key.m @@ -171,8 +171,8 @@ static NSDictionary *keyQuery(MPUserEntity *user) { for (MPElementEntity *element in user.elements) { if (element.type & MPElementTypeClassStored) { - id content = nil; - while (!(content = [element contentUsingKey:recoverKey])) { + NSString *content; + while (!(content = [element.algorithm storedContentForElement:(MPElementStoredEntity *)element usingKey:recoverKey])) { // Failed to decrypt element with the current recoveryKey. Ask user for a new one to use. __block NSString *masterPassword = nil; @@ -209,7 +209,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) { break; if (![recoverKey isEqualToKey:newKey]) - [element setContent:content usingKey:newKey]; + [element.algorithm saveContent:content toElement:element usingKey:newKey]; } } diff --git a/MasterPassword/ObjC/MPAppDelegate_Store.m b/MasterPassword/ObjC/MPAppDelegate_Store.m index ba473e40..5e49d0ec 100644 --- a/MasterPassword/ObjC/MPAppDelegate_Store.m +++ b/MasterPassword/ObjC/MPAppDelegate_Store.m @@ -336,7 +336,9 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, - (void)ubiquityStoreManager:(UbiquityStoreManager *)manager willLoadStoreIsCloud:(BOOL)isCloudStore { +// [manager setValue:@"C61DCF27-BD25-4CF1-AE8F-8B613DF8AB47" forKey:@"storeUUID"]; NSManagedObjectContext *moc = [self mainManagedObjectContextIfReady]; + NSLog( @"willLoadStoreIsCloud:%d mainMoc:%@", isCloudStore, moc); [moc performBlockAndWait:^{ [moc saveToStore]; @@ -350,6 +352,7 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, - (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didLoadStoreForCoordinator:(NSPersistentStoreCoordinator *)coordinator isCloud:(BOOL)isCloudStore { + NSLog( @"didLoadStoreForCoordinatorStores:%d isCloud:%d", [[coordinator persistentStores] count], isCloudStore ); inf(@"Using iCloud? %@", @(isCloudStore)); MPCheckpoint( MPCheckpointCloud, @{ @"enabled" : @(isCloudStore) @@ -382,16 +385,17 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, self.privateManagedObjectContext = privateManagedObjectContext; self.mainManagedObjectContext = mainManagedObjectContext; + NSLog( @"set mainMoc:%@", mainManagedObjectContext ); } - (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didEncounterError:(NSError *)error cause:(UbiquityStoreErrorCause)cause context:(id)context { - err(@"[StoreManager] ERROR: cause=%d, context=%@, error=%@", cause, context, error); + err(@"[StoreManager] ERROR: cause=%@, context=%@, error=%@", NSStringFromUSMCause( cause ), context, error); MPCheckpoint( MPCheckpointMPErrorUbiquity, @{ @"cause" : @(cause), - @"error.domain" : NilToNSNull(error.domain), - @"error.code" : @(error.code) + @"error.code" : @(error.code), + @"error.domain" : NilToNSNull(error.domain) } ); } @@ -406,7 +410,9 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, [MPAppDelegate_Shared managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { MPUserEntity *activeUser = [self activeUserInContext:context]; - assert(activeUser); + NSAssert(activeUser, @"Missing user."); + if (!activeUser) + return; MPElementType type = activeUser.defaultType; if (!type) @@ -673,14 +679,14 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, element.version = version; if ([exportContent length]) { if (clearText) - [element importClearTextContent:exportContent usingKey:userKey]; + [element.algorithm importClearTextContent:exportContent intoElement:element usingKey:userKey]; else { if (!importKey) importKey = [importAlgorithm keyForPassword:importPassword( user.name ) ofUserNamed:user.name]; if (![importKey.keyID isEqualToData:importKeyID]) return MPImportResultInvalidPassword; - [element importProtectedContent:exportContent protectedByKey:importKey usingKey:userKey]; + [element.algorithm importProtectedContent:exportContent protectedByKey:importKey intoElement:element usingKey:userKey]; } } @@ -735,9 +741,9 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, // Determine the content to export. if (!(type & MPElementFeatureDevicePrivate)) { if (showPasswords) - content = element.content; + content = [element.algorithm resolveContentForElement:element usingKey:self.key]; else if (type & MPElementFeatureExportContent) - content = element.exportContent; + content = [element.algorithm exportContentForElement:element usingKey:self.key]; } [export appendFormat:@"%@ %8ld %8s %20s\t%@\n", diff --git a/MasterPassword/ObjC/MPElementEntity.h b/MasterPassword/ObjC/MPElementEntity.h index 3e962c62..587ea556 100644 --- a/MasterPassword/ObjC/MPElementEntity.h +++ b/MasterPassword/ObjC/MPElementEntity.h @@ -13,7 +13,6 @@ @interface MPElementEntity : NSManagedObject -@property(nonatomic, retain) id content; @property(nonatomic, retain) NSDate *lastUsed; @property(nonatomic, retain) NSString *loginName; @property(nonatomic, retain) NSString *name; diff --git a/MasterPassword/ObjC/MPElementEntity.m b/MasterPassword/ObjC/MPElementEntity.m index 8eee7b4b..52289517 100644 --- a/MasterPassword/ObjC/MPElementEntity.m +++ b/MasterPassword/ObjC/MPElementEntity.m @@ -10,7 +10,6 @@ @implementation MPElementEntity -@dynamic content; @dynamic lastUsed; @dynamic loginName; @dynamic name; diff --git a/MasterPassword/ObjC/MPEntities.h b/MasterPassword/ObjC/MPEntities.h index 17719794..b91737dc 100644 --- a/MasterPassword/ObjC/MPEntities.h +++ b/MasterPassword/ObjC/MPEntities.h @@ -33,13 +33,7 @@ @property(assign) BOOL requiresExplicitMigration; @property(readonly) id algorithm; -- (id)contentUsingKey:(MPKey *)key; -- (void)setContent:(id)content usingKey:(MPKey *)key; - - (NSUInteger)use; -- (NSString *)exportContent; -- (void)importProtectedContent:(NSString *)protectedContent protectedByKey:(MPKey *)contentProtectionKey usingKey:(MPKey *)key2; -- (void)importClearTextContent:(NSString *)clearContent usingKey:(MPKey *)key; - (BOOL)migrateExplicitly:(BOOL)explicit; @end diff --git a/MasterPassword/ObjC/MPEntities.m b/MasterPassword/ObjC/MPEntities.m index 237c5318..d2d17b3d 100644 --- a/MasterPassword/ObjC/MPEntities.m +++ b/MasterPassword/ObjC/MPEntities.m @@ -13,14 +13,21 @@ - (BOOL)saveToStore { - __block BOOL success = NO; - [self performBlockAndWait:^{ - NSError *error = nil; - if (!(success = [self save:&error])) - err(@"While saving: %@", error); - }]; + __block BOOL success = YES; + if ([self hasChanges]) + [self performBlockAndWait:^{ + @try { + NSError *error = nil; + if (!(success = [self save:&error])) + err(@"While saving: %@", error); + } + @catch (NSException *exception) { + success = NO; + err(@"While saving: %@", exception); + } + }]; - return !self.parentContext || [self.parentContext saveToStore]; + return success && (!self.parentContext || [self.parentContext saveToStore]); } @end @@ -111,47 +118,6 @@ return ++self.uses; } -- (id)content { - - MPKey *key = [MPAppDelegate_Shared get].key; - if (!key) - return nil; - - assert([key.keyID isEqualToData:self.user.keyID]); - return [self contentUsingKey:key]; -} - -- (void)setContent:(id)content { - - MPKey *key = [MPAppDelegate_Shared get].key; - if (!key) - return; - - assert([key.keyID isEqualToData:self.user.keyID]); - [self setContent:content usingKey:key]; -} - -- (id)contentUsingKey:(MPKey *)key { - - Throw(@"Content retrieval implementation missing for: %@", [self class]); -} - -- (void)setContent:(id)content usingKey:(MPKey *)key { - - Throw(@"Content assignment implementation missing for: %@", [self class]); -} - -- (NSString *)exportContent { - - return nil; -} - -- (void)importProtectedContent:(NSString *)protectedContent protectedByKey:(MPKey *)contentProtectionKey usingKey:(MPKey *)key { -} - -- (void)importClearTextContent:(NSString *)clearContent usingKey:(MPKey *)key { -} - - (NSString *)description { return PearlString( @"%@:%@", [self class], [self name] ); @@ -191,107 +157,10 @@ self.counter_ = @(aCounter); } -- (id)contentUsingKey:(MPKey *)key { - - assert(self.type & MPElementTypeClassGenerated); - - if (![self.name length]) - return nil; - if (!key) - return nil; - - return [self.algorithm generateContentForElement:self usingKey:key]; -} - @end @implementation MPElementStoredEntity(MP) -+ (NSDictionary *)queryForDevicePrivateElementNamed:(NSString *)name { - - return [PearlKeyChain createQueryForClass:kSecClassGenericPassword - attributes:@{ - (__bridge id)kSecAttrService : @"DevicePrivate", - (__bridge id)kSecAttrAccount : name - } - matches:nil]; -} - -- (id)contentUsingKey:(MPKey *)key { - - assert(self.type & MPElementTypeClassStored); - - if (!key) - return nil; - - NSData *encryptedContent; - if (self.type & MPElementFeatureDevicePrivate) - encryptedContent = [PearlKeyChain dataOfItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]]; - else - encryptedContent = self.contentObject; - - NSData *decryptedContent = nil; - if ([encryptedContent length]) - decryptedContent = [self decryptContent:encryptedContent usingKey:key]; - - if (!decryptedContent) - return nil; - - return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding]; -} - -- (NSData *)decryptContent:(NSData *)encryptedContent usingKey:(MPKey *)key { - - return [encryptedContent decryptWithSymmetricKey:[key subKeyOfLength:PearlCryptKeySize].keyData padding:YES]; -} - -- (void)setContent:(id)content usingKey:(MPKey *)key { - - assert(self.type & MPElementTypeClassStored); - assert([key.keyID isEqualToData:self.user.keyID]); - - NSData *encryptedContent = [[[content description] dataUsingEncoding:NSUTF8StringEncoding] - encryptWithSymmetricKey:[key subKeyOfLength:PearlCryptKeySize].keyData padding:YES]; - - if (self.type & MPElementFeatureDevicePrivate) { - [PearlKeyChain addOrUpdateItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name] - withAttributes:[NSDictionary dictionaryWithObjectsAndKeys: - encryptedContent, (__bridge id)kSecValueData, - #if TARGET_OS_IPHONE - (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly, - (__bridge id)kSecAttrAccessible, - #endif - nil]]; - self.contentObject = nil; - } - else - self.contentObject = encryptedContent; -} - -- (NSString *)exportContent { - - return [self.contentObject encodeBase64]; -} - -- (void)importProtectedContent:(NSString *)protectedContent protectedByKey:(MPKey *)contentProtectionKey usingKey:(MPKey *)key { - - if ([contentProtectionKey.keyID isEqualToData:key.keyID]) - self.contentObject = [protectedContent decodeBase64]; - - else { - NSString *clearContent = [[NSString alloc] initWithData:[self decryptContent:[protectedContent decodeBase64] - usingKey:contentProtectionKey] - encoding:NSUTF8StringEncoding]; - - [self importClearTextContent:clearContent usingKey:key]; - } -} - -- (void)importClearTextContent:(NSString *)clearContent usingKey:(MPKey *)key { - - [self setContent:clearContent usingKey:key]; -} - @end @implementation MPUserEntity(MP) diff --git a/MasterPassword/ObjC/iOS/MPElementListAllViewController.m b/MasterPassword/ObjC/iOS/MPElementListAllViewController.m index da655877..51739c9a 100644 --- a/MasterPassword/ObjC/iOS/MPElementListAllViewController.m +++ b/MasterPassword/ObjC/iOS/MPElementListAllViewController.m @@ -99,11 +99,12 @@ return; } + MPKey *key = [MPAppDelegate_Shared get].key; NSMutableDictionary *elementChanges = [NSMutableDictionary dictionaryWithCapacity:[elements count]]; for (MPElementEntity *element in elements) { - id oldContent = [element content]; + id oldContent = [element.algorithm resolveContentForElement:element usingKey:key]; [element migrateExplicitly:YES]; - id newContent = [element content]; + id newContent = [element.algorithm resolveContentForElement:element usingKey:key]; if (!(element.type & MPElementFeatureDevicePrivate) && (!oldContent || ![oldContent isEqual:newContent])) [elementChanges setObject:@{ diff --git a/MasterPassword/ObjC/iOS/MPMainViewController.m b/MasterPassword/ObjC/iOS/MPMainViewController.m index c5f72190..c3d06146 100644 --- a/MasterPassword/ObjC/iOS/MPMainViewController.m +++ b/MasterPassword/ObjC/iOS/MPMainViewController.m @@ -85,7 +85,8 @@ queue:[NSOperationQueue mainQueue] usingBlock: ^(NSNotification *note) { MPElementEntity *activeElement = [self activeElementForMainThread]; - if (activeElement.type & MPElementTypeClassStored && ![[activeElement.content description] length]) + if (activeElement.type & MPElementTypeClassStored && + ![[activeElement.algorithm resolveContentForElement:activeElement usingKey:[MPAppDelegate_Shared get].key] length]) [self showToolTip:@"Tap to set a password." withIcon:self.toolTipEditIcon]; if (activeElement.requiresExplicitMigration) [self showToolTip:@"Password outdated. Tap to upgrade it." withIcon:nil]; @@ -246,13 +247,11 @@ self.contentField.enabled = NO; self.contentField.text = @""; if (activeElement.name && ![activeElement isDeleted]) - dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0 ), ^{ - NSString *description = [activeElement.content description]; - + [activeElement.algorithm resolveContentForElement:activeElement usingKey:[MPAppDelegate_Shared get].key result:^(NSString *result) { dispatch_async( dispatch_get_main_queue(), ^{ - self.contentField.text = description; + self.contentField.text = result; } ); - } ); + }]; self.loginNameField.enabled = NO; self.loginNameField.text = activeElement.loginName; @@ -474,21 +473,21 @@ - (IBAction)copyContent { MPElementEntity *activeElement = [self activeElementForMainThread]; - id content = activeElement.content; - if (!content) - // Nothing to copy. - return; - inf(@"Copying password for: %@", activeElement.name); - [UIPasteboard generalPasteboard].string = [content description]; - - [self showContentTip:@"Copied!" withIcon:nil]; - MPCheckpoint( MPCheckpointCopyToPasteboard, @{ @"type" : NilToNSNull(activeElement.typeName), @"version" : @(activeElement.version), @"emergency" : @NO } ); + + [activeElement.algorithm resolveContentForElement:activeElement usingKey:[MPAppDelegate_Shared get].key result:^(NSString *result) { + if (!result) + // Nothing to copy. + return; + + [UIPasteboard generalPasteboard].string = result; + [self showContentTip:@"Copied!" withIcon:nil]; + }]; } - (IBAction)copyLoginName:(UITapGestureRecognizer *)sender { @@ -604,12 +603,13 @@ if (!activeElement) return; - NSString *oldPassword = [activeElement.content description]; + MPKey *key = [MPAppDelegate_Shared get].key; + NSString *oldPassword = [activeElement.algorithm resolveContentForElement:activeElement usingKey:key]; if (!task( activeElement, context )) return; activeElement = [self activeElementInContext:context]; - NSString *newPassword = [activeElement.content description]; + NSString *newPassword = [activeElement.algorithm resolveContentForElement:activeElement usingKey:key]; // Save. [context saveToStore]; @@ -865,17 +865,18 @@ if (textField == self.contentField) { self.contentField.enabled = NO; MPElementEntity *activeElement = [self activeElementForMainThread]; + MPKey *key = [MPAppDelegate_Shared get].key; if (![activeElement isKindOfClass:[MPElementStoredEntity class]]) { // Not of a type whose content can be edited. err(@"Cannot update element content: Element is not stored: %@", activeElement.name); return; } - else if ([((MPElementStoredEntity *)activeElement).content isEqual:self.contentField.text]) + else if ([[activeElement.algorithm resolveContentForElement:activeElement usingKey:key] isEqual:self.contentField.text]) // Content hasn't changed. return; [self changeActiveElementWithoutWarningDo:^BOOL(MPElementEntity *activeElement_, NSManagedObjectContext *context) { - ((MPElementStoredEntity *)activeElement_).content = self.contentField.text; + [activeElement_.algorithm saveContent:self.contentField.text toElement:activeElement_ usingKey:key]; return YES; }]; } diff --git a/MasterPassword/ObjC/iOS/MPTypeViewController.m b/MasterPassword/ObjC/iOS/MPTypeViewController.m index 74667272..0771e66a 100644 --- a/MasterPassword/ObjC/iOS/MPTypeViewController.m +++ b/MasterPassword/ObjC/iOS/MPTypeViewController.m @@ -98,7 +98,7 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - assert(self.navigationController.topViewController == self); + NSAssert(self.navigationController.topViewController == self, @"Not the currently active navigation item."); MPElementType type = [self typeAtIndexPath:indexPath]; if (type == (MPElementType)NSNotFound)