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)