2
0

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.
This commit is contained in:
Maarten Billemont 2013-09-13 08:14:58 -04:00
parent 36386c3213
commit f0dcc4c34c
14 changed files with 310 additions and 213 deletions

View File

@ -9,6 +9,7 @@
<inspection_tool class="OCNotLocalizedStringInspection" enabled="false" level="WARNING" enabled_by_default="false" /> <inspection_tool class="OCNotLocalizedStringInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="OCUnusedMacroInspection" enabled="false" level="WARNING" enabled_by_default="false" /> <inspection_tool class="OCUnusedMacroInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="OCUnusedMethodInspection" enabled="false" level="WARNING" enabled_by_default="false" /> <inspection_tool class="OCUnusedMethodInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UnavailableInDeploymentTarget" enabled="true" level="INFO" enabled_by_default="true" />
<inspection_tool class="UnusedLocalVariable" enabled="false" level="WARNING" enabled_by_default="false" /> <inspection_tool class="UnusedLocalVariable" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UnusedParameter" enabled="false" level="WARNING" enabled_by_default="false" /> <inspection_tool class="UnusedParameter" enabled="false" level="WARNING" enabled_by_default="false" />
</profile> </profile>

@ -1 +1 @@
Subproject commit a2ce82ea58d8a02237f33fdc4393259b09cb4967 Subproject commit 0202de6cf5b1f2847f4726af0788f6fce9683b32

View File

@ -16,6 +16,7 @@
// //
#import "MPKey.h" #import "MPKey.h"
#import "MPElementStoredEntity.h"
#import "MPElementGeneratedEntity.h" #import "MPElementGeneratedEntity.h"
#define MPAlgorithmDefaultVersion 1 #define MPAlgorithmDefaultVersion 1
@ -37,8 +38,19 @@
- (NSString *)classNameOfType:(MPElementType)type; - (NSString *)classNameOfType:(MPElementType)type;
- (Class)classOfType:(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 *)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 @end

View File

@ -203,27 +203,6 @@
Throw(@"Type not supported: %d", type); 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 { - (NSString *)generateContentNamed:(NSString *)name ofType:(MPElementType)type withCounter:(NSUInteger)counter usingKey:(MPKey *)key {
static NSDictionary *MPTypes_ciphers = nil; static NSDictionary *MPTypes_ciphers = nil;
@ -247,14 +226,14 @@
const char *seedBytes = seed.bytes; const char *seedBytes = seed.bytes;
// Determine the cipher from the first seed byte. // Determine the cipher from the first seed byte.
assert([seed length]); NSAssert([seed length], @"Missing seed.");
NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:[self classNameOfType:type]] NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:[self classNameOfType:type]]
valueForKey:[self nameOfType:type]]; valueForKey:[self nameOfType:type]];
NSString *cipher = [typeCiphers objectAtIndex:htons(seedBytes[0]) % [typeCiphers count]]; NSString *cipher = [typeCiphers objectAtIndex:htons(seedBytes[0]) % [typeCiphers count]];
trc(@"type %@, ciphers: %@, selected: %@", [self nameOfType:type], typeCiphers, cipher); trc(@"type %@, ciphers: %@, selected: %@", [self nameOfType:type], typeCiphers, cipher);
// Encode the content, character by character, using subsequent seed bytes and the 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]]; NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]];
for (NSUInteger c = 0; c < [cipher length]; ++c) { for (NSUInteger c = 0; c < [cipher length]; ++c) {
uint16_t keyByte = htons(seedBytes[c + 1]); uint16_t keyByte = htons(seedBytes[c + 1]);
@ -270,4 +249,240 @@
return content; 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<MPAlgorithm> 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 @end

View File

@ -68,13 +68,13 @@
const unsigned char *seedBytes = seed.bytes; const unsigned char *seedBytes = seed.bytes;
// Determine the cipher from the first seed byte. // 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]]; NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:[self classNameOfType:type]] valueForKey:[self nameOfType:type]];
NSString *cipher = [typeCiphers objectAtIndex:seedBytes[0] % [typeCiphers count]]; NSString *cipher = [typeCiphers objectAtIndex:seedBytes[0] % [typeCiphers count]];
trc(@"type %@, ciphers: %@, selected: %@", [self nameOfType:type], typeCiphers, cipher); trc(@"type %@, ciphers: %@, selected: %@", [self nameOfType:type], typeCiphers, cipher);
// Encode the content, character by character, using subsequent seed bytes and the 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]]; NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]];
for (NSUInteger c = 0; c < [cipher length]; ++c) { for (NSUInteger c = 0; c < [cipher length]; ++c) {
uint16_t keyByte = seedBytes[c + 1]; uint16_t keyByte = seedBytes[c + 1];

View File

@ -171,8 +171,8 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
for (MPElementEntity *element in user.elements) { for (MPElementEntity *element in user.elements) {
if (element.type & MPElementTypeClassStored) { if (element.type & MPElementTypeClassStored) {
id content = nil; NSString *content;
while (!(content = [element contentUsingKey:recoverKey])) { 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. // Failed to decrypt element with the current recoveryKey. Ask user for a new one to use.
__block NSString *masterPassword = nil; __block NSString *masterPassword = nil;
@ -209,7 +209,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
break; break;
if (![recoverKey isEqualToKey:newKey]) if (![recoverKey isEqualToKey:newKey])
[element setContent:content usingKey:newKey]; [element.algorithm saveContent:content toElement:element usingKey:newKey];
} }
} }

View File

@ -336,7 +336,9 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager willLoadStoreIsCloud:(BOOL)isCloudStore { - (void)ubiquityStoreManager:(UbiquityStoreManager *)manager willLoadStoreIsCloud:(BOOL)isCloudStore {
// [manager setValue:@"C61DCF27-BD25-4CF1-AE8F-8B613DF8AB47" forKey:@"storeUUID"];
NSManagedObjectContext *moc = [self mainManagedObjectContextIfReady]; NSManagedObjectContext *moc = [self mainManagedObjectContextIfReady];
NSLog( @"willLoadStoreIsCloud:%d mainMoc:%@", isCloudStore, moc);
[moc performBlockAndWait:^{ [moc performBlockAndWait:^{
[moc saveToStore]; [moc saveToStore];
@ -350,6 +352,7 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didLoadStoreForCoordinator:(NSPersistentStoreCoordinator *)coordinator - (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didLoadStoreForCoordinator:(NSPersistentStoreCoordinator *)coordinator
isCloud:(BOOL)isCloudStore { isCloud:(BOOL)isCloudStore {
NSLog( @"didLoadStoreForCoordinatorStores:%d isCloud:%d", [[coordinator persistentStores] count], isCloudStore );
inf(@"Using iCloud? %@", @(isCloudStore)); inf(@"Using iCloud? %@", @(isCloudStore));
MPCheckpoint( MPCheckpointCloud, @{ MPCheckpoint( MPCheckpointCloud, @{
@"enabled" : @(isCloudStore) @"enabled" : @(isCloudStore)
@ -382,16 +385,17 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
self.privateManagedObjectContext = privateManagedObjectContext; self.privateManagedObjectContext = privateManagedObjectContext;
self.mainManagedObjectContext = mainManagedObjectContext; self.mainManagedObjectContext = mainManagedObjectContext;
NSLog( @"set mainMoc:%@", mainManagedObjectContext );
} }
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didEncounterError:(NSError *)error cause:(UbiquityStoreErrorCause)cause - (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didEncounterError:(NSError *)error cause:(UbiquityStoreErrorCause)cause
context:(id)context { 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, @{ MPCheckpoint( MPCheckpointMPErrorUbiquity, @{
@"cause" : @(cause), @"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) { [MPAppDelegate_Shared managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPUserEntity *activeUser = [self activeUserInContext:context]; MPUserEntity *activeUser = [self activeUserInContext:context];
assert(activeUser); NSAssert(activeUser, @"Missing user.");
if (!activeUser)
return;
MPElementType type = activeUser.defaultType; MPElementType type = activeUser.defaultType;
if (!type) if (!type)
@ -673,14 +679,14 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
element.version = version; element.version = version;
if ([exportContent length]) { if ([exportContent length]) {
if (clearText) if (clearText)
[element importClearTextContent:exportContent usingKey:userKey]; [element.algorithm importClearTextContent:exportContent intoElement:element usingKey:userKey];
else { else {
if (!importKey) if (!importKey)
importKey = [importAlgorithm keyForPassword:importPassword( user.name ) ofUserNamed:user.name]; importKey = [importAlgorithm keyForPassword:importPassword( user.name ) ofUserNamed:user.name];
if (![importKey.keyID isEqualToData:importKeyID]) if (![importKey.keyID isEqualToData:importKeyID])
return MPImportResultInvalidPassword; 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. // Determine the content to export.
if (!(type & MPElementFeatureDevicePrivate)) { if (!(type & MPElementFeatureDevicePrivate)) {
if (showPasswords) if (showPasswords)
content = element.content; content = [element.algorithm resolveContentForElement:element usingKey:self.key];
else if (type & MPElementFeatureExportContent) else if (type & MPElementFeatureExportContent)
content = element.exportContent; content = [element.algorithm exportContentForElement:element usingKey:self.key];
} }
[export appendFormat:@"%@ %8ld %8s %20s\t%@\n", [export appendFormat:@"%@ %8ld %8s %20s\t%@\n",

View File

@ -13,7 +13,6 @@
@interface MPElementEntity : NSManagedObject @interface MPElementEntity : NSManagedObject
@property(nonatomic, retain) id content;
@property(nonatomic, retain) NSDate *lastUsed; @property(nonatomic, retain) NSDate *lastUsed;
@property(nonatomic, retain) NSString *loginName; @property(nonatomic, retain) NSString *loginName;
@property(nonatomic, retain) NSString *name; @property(nonatomic, retain) NSString *name;

View File

@ -10,7 +10,6 @@
@implementation MPElementEntity @implementation MPElementEntity
@dynamic content;
@dynamic lastUsed; @dynamic lastUsed;
@dynamic loginName; @dynamic loginName;
@dynamic name; @dynamic name;

View File

@ -33,13 +33,7 @@
@property(assign) BOOL requiresExplicitMigration; @property(assign) BOOL requiresExplicitMigration;
@property(readonly) id<MPAlgorithm> algorithm; @property(readonly) id<MPAlgorithm> algorithm;
- (id)contentUsingKey:(MPKey *)key;
- (void)setContent:(id)content usingKey:(MPKey *)key;
- (NSUInteger)use; - (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; - (BOOL)migrateExplicitly:(BOOL)explicit;
@end @end

View File

@ -13,14 +13,21 @@
- (BOOL)saveToStore { - (BOOL)saveToStore {
__block BOOL success = NO; __block BOOL success = YES;
[self performBlockAndWait:^{ if ([self hasChanges])
NSError *error = nil; [self performBlockAndWait:^{
if (!(success = [self save:&error])) @try {
err(@"While saving: %@", error); 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 @end
@ -111,47 +118,6 @@
return ++self.uses; 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 { - (NSString *)description {
return PearlString( @"%@:%@", [self class], [self name] ); return PearlString( @"%@:%@", [self class], [self name] );
@ -191,107 +157,10 @@
self.counter_ = @(aCounter); 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 @end
@implementation MPElementStoredEntity(MP) @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 @end
@implementation MPUserEntity(MP) @implementation MPUserEntity(MP)

View File

@ -99,11 +99,12 @@
return; return;
} }
MPKey *key = [MPAppDelegate_Shared get].key;
NSMutableDictionary *elementChanges = [NSMutableDictionary dictionaryWithCapacity:[elements count]]; NSMutableDictionary *elementChanges = [NSMutableDictionary dictionaryWithCapacity:[elements count]];
for (MPElementEntity *element in elements) { for (MPElementEntity *element in elements) {
id oldContent = [element content]; id oldContent = [element.algorithm resolveContentForElement:element usingKey:key];
[element migrateExplicitly:YES]; [element migrateExplicitly:YES];
id newContent = [element content]; id newContent = [element.algorithm resolveContentForElement:element usingKey:key];
if (!(element.type & MPElementFeatureDevicePrivate) && (!oldContent || ![oldContent isEqual:newContent])) if (!(element.type & MPElementFeatureDevicePrivate) && (!oldContent || ![oldContent isEqual:newContent]))
[elementChanges setObject:@{ [elementChanges setObject:@{

View File

@ -85,7 +85,8 @@
queue:[NSOperationQueue mainQueue] usingBlock: queue:[NSOperationQueue mainQueue] usingBlock:
^(NSNotification *note) { ^(NSNotification *note) {
MPElementEntity *activeElement = [self activeElementForMainThread]; 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]; [self showToolTip:@"Tap to set a password." withIcon:self.toolTipEditIcon];
if (activeElement.requiresExplicitMigration) if (activeElement.requiresExplicitMigration)
[self showToolTip:@"Password outdated. Tap to upgrade it." withIcon:nil]; [self showToolTip:@"Password outdated. Tap to upgrade it." withIcon:nil];
@ -246,13 +247,11 @@
self.contentField.enabled = NO; self.contentField.enabled = NO;
self.contentField.text = @""; self.contentField.text = @"";
if (activeElement.name && ![activeElement isDeleted]) if (activeElement.name && ![activeElement isDeleted])
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0 ), ^{ [activeElement.algorithm resolveContentForElement:activeElement usingKey:[MPAppDelegate_Shared get].key result:^(NSString *result) {
NSString *description = [activeElement.content description];
dispatch_async( dispatch_get_main_queue(), ^{ dispatch_async( dispatch_get_main_queue(), ^{
self.contentField.text = description; self.contentField.text = result;
} ); } );
} ); }];
self.loginNameField.enabled = NO; self.loginNameField.enabled = NO;
self.loginNameField.text = activeElement.loginName; self.loginNameField.text = activeElement.loginName;
@ -474,21 +473,21 @@
- (IBAction)copyContent { - (IBAction)copyContent {
MPElementEntity *activeElement = [self activeElementForMainThread]; MPElementEntity *activeElement = [self activeElementForMainThread];
id content = activeElement.content;
if (!content)
// Nothing to copy.
return;
inf(@"Copying password for: %@", activeElement.name); inf(@"Copying password for: %@", activeElement.name);
[UIPasteboard generalPasteboard].string = [content description];
[self showContentTip:@"Copied!" withIcon:nil];
MPCheckpoint( MPCheckpointCopyToPasteboard, @{ MPCheckpoint( MPCheckpointCopyToPasteboard, @{
@"type" : NilToNSNull(activeElement.typeName), @"type" : NilToNSNull(activeElement.typeName),
@"version" : @(activeElement.version), @"version" : @(activeElement.version),
@"emergency" : @NO @"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 { - (IBAction)copyLoginName:(UITapGestureRecognizer *)sender {
@ -604,12 +603,13 @@
if (!activeElement) if (!activeElement)
return; return;
NSString *oldPassword = [activeElement.content description]; MPKey *key = [MPAppDelegate_Shared get].key;
NSString *oldPassword = [activeElement.algorithm resolveContentForElement:activeElement usingKey:key];
if (!task( activeElement, context )) if (!task( activeElement, context ))
return; return;
activeElement = [self activeElementInContext:context]; activeElement = [self activeElementInContext:context];
NSString *newPassword = [activeElement.content description]; NSString *newPassword = [activeElement.algorithm resolveContentForElement:activeElement usingKey:key];
// Save. // Save.
[context saveToStore]; [context saveToStore];
@ -865,17 +865,18 @@
if (textField == self.contentField) { if (textField == self.contentField) {
self.contentField.enabled = NO; self.contentField.enabled = NO;
MPElementEntity *activeElement = [self activeElementForMainThread]; MPElementEntity *activeElement = [self activeElementForMainThread];
MPKey *key = [MPAppDelegate_Shared get].key;
if (![activeElement isKindOfClass:[MPElementStoredEntity class]]) { if (![activeElement isKindOfClass:[MPElementStoredEntity class]]) {
// Not of a type whose content can be edited. // Not of a type whose content can be edited.
err(@"Cannot update element content: Element is not stored: %@", activeElement.name); err(@"Cannot update element content: Element is not stored: %@", activeElement.name);
return; 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. // Content hasn't changed.
return; return;
[self changeActiveElementWithoutWarningDo:^BOOL(MPElementEntity *activeElement_, NSManagedObjectContext *context) { [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; return YES;
}]; }];
} }

View File

@ -98,7 +98,7 @@
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - (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]; MPElementType type = [self typeAtIndexPath:indexPath];
if (type == (MPElementType)NSNotFound) if (type == (MPElementType)NSNotFound)