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:
parent
36386c3213
commit
f0dcc4c34c
@ -9,6 +9,7 @@
|
||||
<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="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="UnusedParameter" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
</profile>
|
||||
|
2
External/UbiquityStoreManager
vendored
2
External/UbiquityStoreManager
vendored
@ -1 +1 @@
|
||||
Subproject commit a2ce82ea58d8a02237f33fdc4393259b09cb4967
|
||||
Subproject commit 0202de6cf5b1f2847f4726af0788f6fce9683b32
|
@ -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
|
||||
|
||||
|
@ -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<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
|
||||
|
@ -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];
|
||||
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
|
@ -10,7 +10,6 @@
|
||||
|
||||
@implementation MPElementEntity
|
||||
|
||||
@dynamic content;
|
||||
@dynamic lastUsed;
|
||||
@dynamic loginName;
|
||||
@dynamic name;
|
||||
|
@ -33,13 +33,7 @@
|
||||
@property(assign) BOOL requiresExplicitMigration;
|
||||
@property(readonly) id<MPAlgorithm> 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
|
||||
|
@ -13,14 +13,21 @@
|
||||
|
||||
- (BOOL)saveToStore {
|
||||
|
||||
__block BOOL success = NO;
|
||||
__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)
|
||||
|
@ -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:@{
|
||||
|
@ -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;
|
||||
}];
|
||||
}
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user