diff --git a/External/Pearl b/External/Pearl index 08c42ba0..65b4e3d9 160000 --- a/External/Pearl +++ b/External/Pearl @@ -1 +1 @@ -Subproject commit 08c42ba0f96a95703ec67cbf5846bfe6680dd0a6 +Subproject commit 65b4e3d9984d077f66e6ab15f2ffcc4965d07825 diff --git a/External/iOS/Crashlytics.framework/Versions/A/Crashlytics b/External/iOS/Crashlytics.framework/Versions/A/Crashlytics index ec79a372..df0fe475 100644 Binary files a/External/iOS/Crashlytics.framework/Versions/A/Crashlytics and b/External/iOS/Crashlytics.framework/Versions/A/Crashlytics differ diff --git a/External/iOS/Crashlytics.framework/Versions/A/Headers/Crashlytics.h b/External/iOS/Crashlytics.framework/Versions/A/Headers/Crashlytics.h index 7102c5c1..9683173c 100644 --- a/External/iOS/Crashlytics.framework/Versions/A/Headers/Crashlytics.h +++ b/External/iOS/Crashlytics.framework/Versions/A/Headers/Crashlytics.h @@ -218,3 +218,8 @@ OBJC_EXTERN void CLSNSLogv(NSString *format, va_list args) NS_FORMAT_FUNCTION(1, - (void)crashlytics:(Crashlytics *)crashlytics didDetectCrashDuringPreviousExecution:(id )crash; @end + +/** + * `CrashlyticsKit` can be used as a parameter to `[Fabric with:@[CrashlyticsKit]];` in Objective-C. In Swift, simply use `Crashlytics()` + */ +#define CrashlyticsKit [Crashlytics sharedInstance] diff --git a/External/iOS/Crashlytics.framework/Versions/A/Resources/Info.plist b/External/iOS/Crashlytics.framework/Versions/A/Resources/Info.plist index ab0c6383..a10d5efb 100644 --- a/External/iOS/Crashlytics.framework/Versions/A/Resources/Info.plist +++ b/External/iOS/Crashlytics.framework/Versions/A/Resources/Info.plist @@ -15,13 +15,13 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.2.5 + 2.2.9 CFBundleSupportedPlatforms iPhoneOS CFBundleVersion - 40 + 44 DTPlatformName iphoneos MinimumOSVersion diff --git a/External/iOS/Crashlytics.framework/run b/External/iOS/Crashlytics.framework/run index b34d4f75..b9211c3a 100755 Binary files a/External/iOS/Crashlytics.framework/run and b/External/iOS/Crashlytics.framework/run differ diff --git a/External/iOS/Crashlytics.framework/submit b/External/iOS/Crashlytics.framework/submit index c34d5c2e..32c9ca36 100755 Binary files a/External/iOS/Crashlytics.framework/submit and b/External/iOS/Crashlytics.framework/submit differ diff --git a/MasterPassword/ObjC/MPAlgorithm.h b/MasterPassword/ObjC/MPAlgorithm.h index 958b9ba4..65a0cb43 100644 --- a/MasterPassword/ObjC/MPAlgorithm.h +++ b/MasterPassword/ObjC/MPAlgorithm.h @@ -48,9 +48,8 @@ NSString *NSStringFromTimeToCrack(TimeToCrack timeToCrack); - (BOOL)tryMigrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc; - (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit; -- (MPKey *)keyForPassword:(NSString *)password ofUserNamed:(NSString *)userName; -- (MPKey *)keyFromKeyData:(NSData *)keyData; - (NSData *)keyIDForKeyData:(NSData *)keyData; +- (NSData *)keyDataForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword; - (NSString *)nameOfType:(MPSiteType)type; - (NSString *)shortNameOfType:(MPSiteType)type; diff --git a/MasterPassword/ObjC/MPAlgorithmV0.m b/MasterPassword/ObjC/MPAlgorithmV0.m index a6f938c6..bf5a7f46 100644 --- a/MasterPassword/ObjC/MPAlgorithmV0.m +++ b/MasterPassword/ObjC/MPAlgorithmV0.m @@ -89,7 +89,7 @@ - (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit { - if (site.version != [self version] - 1) + if ([site.algorithm version] != [self version] - 1) // Only migrate from previous version. return NO; @@ -101,24 +101,19 @@ // Apply migration. site.requiresExplicitMigration = NO; - site.version = [self version]; + site.algorithm = self; return YES; } -- (MPKey *)keyForPassword:(NSString *)password ofUserNamed:(NSString *)userName { +- (NSData *)keyDataForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword { NSDate *start = [NSDate date]; - uint8_t const *masterKeyBytes = mpw_masterKeyForUser( userName.UTF8String, password.UTF8String, [self version] ); - MPKey *masterKey = [self keyFromKeyData:[NSData dataWithBytes:masterKeyBytes length:MP_dkLen]]; + uint8_t const *masterKeyBytes = mpw_masterKeyForUser( fullName.UTF8String, masterPassword.UTF8String, [self version] ); + NSData *keyData = [NSData dataWithBytes:masterKeyBytes length:MP_dkLen]; + trc( @"User: %@, password: %@ derives to key ID: %@ (took %0.2fs)", // + fullName, masterPassword, [self keyIDForKeyData:keyData], -[start timeIntervalSinceNow] ); mpw_free( masterKeyBytes, MP_dkLen ); - trc( @"User: %@, password: %@ derives to key ID: %@ (took %0.2fs)", userName, password, [masterKey.keyID encodeHex], - -[start timeIntervalSinceNow] ); - return masterKey; -} - -- (MPKey *)keyFromKeyData:(NSData *)keyData { - - return [[MPKey alloc] initWithKeyData:keyData algorithm:self]; + return keyData; } - (NSData *)keyIDForKeyData:(NSData *)keyData { @@ -322,8 +317,8 @@ - (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key { - char const *contentBytes = mpw_passwordForSite( key.keyData.bytes, name.UTF8String, type, (uint32_t)counter, - variant, context.UTF8String, [self version] ); + char const *contentBytes = mpw_passwordForSite( [key keyDataForAlgorithm:self].bytes, + name.UTF8String, type, (uint32_t)counter, variant, context.UTF8String, [self version] ); NSString *content = [NSString stringWithCString:contentBytes encoding:NSUTF8StringEncoding]; mpw_freeString( contentBytes ); @@ -342,7 +337,7 @@ - (BOOL)savePassword:(NSString *)clearContent toSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey { - NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." ); + NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." ); switch (site.type) { case MPSiteTypeGeneratedMaximum: case MPSiteTypeGeneratedLong: @@ -363,8 +358,9 @@ return NO; } + NSData *encryptionKey = [siteKey keyDataForAlgorithm:self trimmedLength:PearlCryptKeySize]; NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding] - encryptWithSymmetricKey:[siteKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES]; + encryptWithSymmetricKey:encryptionKey padding:YES]; if ([((MPStoredSiteEntity *)site).contentObject isEqualToData:encryptedContent]) return NO; @@ -378,8 +374,9 @@ return NO; } + NSData *encryptionKey = [siteKey keyDataForAlgorithm:self trimmedLength:PearlCryptKeySize]; NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding] - encryptWithSymmetricKey:[siteKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES]; + encryptWithSymmetricKey:encryptionKey padding:YES]; NSDictionary *siteQuery = [self queryForDevicePrivateSiteNamed:site.name]; if (!encryptedContent) [PearlKeyChain deleteItemForQuery:siteQuery]; @@ -456,14 +453,14 @@ - (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock { - NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." ); + NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." ); NSString *name = site.name; BOOL loginGenerated = site.loginGenerated && [[MPAppDelegate_Shared get] isFeatureUnlocked:MPProductGenerateLogins]; NSString *loginName = loginGenerated? nil: site.loginName; id algorithm = nil; if (!name.length) err( @"Missing name." ); - else if (!siteKey.keyData.length) + else if (!siteKey) err( @"Missing key." ); else algorithm = site.algorithm; @@ -478,7 +475,7 @@ - (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock { - NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." ); + NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." ); switch (site.type) { case MPSiteTypeGeneratedMaximum: case MPSiteTypeGeneratedLong: @@ -500,7 +497,7 @@ id algorithm = nil; if (!site.name.length) err( @"Missing name." ); - else if (!siteKey.keyData.length) + else if (!siteKey) err( @"Missing key." ); else algorithm = site.algorithm; @@ -546,12 +543,12 @@ - (void)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock { - NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." ); + NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." ); NSString *name = site.name; id algorithm = nil; if (!site.name.length) err( @"Missing name." ); - else if (!siteKey.keyData.length) + else if (!siteKey) err( @"Missing key." ); else algorithm = site.algorithm; @@ -565,13 +562,13 @@ - (void)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock { - NSAssert( [siteKey.keyID isEqualToData:question.site.user.keyID], @"Site does not belong to current user." ); + NSAssert( [[siteKey keyIDForAlgorithm:question.site.user.algorithm] isEqualToData:question.site.user.keyID], @"Site does not belong to current user." ); NSString *name = question.site.name; NSString *keyword = question.keyword; id algorithm = nil; if (!name.length) err( @"Missing name." ); - else if (!siteKey.keyData.length) + else if (!siteKey) err( @"Missing key." ); else algorithm = question.site.algorithm; @@ -585,7 +582,7 @@ - (void)importProtectedPassword:(NSString *)protectedContent protectedByKey:(MPKey *)importKey intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey { - NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." ); + NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." ); switch (site.type) { case MPSiteTypeGeneratedMaximum: case MPSiteTypeGeneratedLong: @@ -603,7 +600,7 @@ (long)site.type, [site class] ); break; } - if ([importKey.keyID isEqualToData:siteKey.keyID]) + if ([[importKey keyIDForAlgorithm:self] isEqualToData:[siteKey keyIDForAlgorithm:self]]) ((MPStoredSiteEntity *)site).contentObject = [protectedContent decodeBase64]; else { @@ -620,7 +617,7 @@ - (void)importClearTextPassword:(NSString *)clearContent intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey { - NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." ); + NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." ); switch (site.type) { case MPSiteTypeGeneratedMaximum: case MPSiteTypeGeneratedLong: @@ -644,7 +641,7 @@ - (NSString *)exportPasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey { - NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." ); + NSAssert( [[siteKey keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." ); if (!(site.type & MPSiteFeatureExportContent)) return nil; @@ -701,8 +698,10 @@ if (!key) return nil; NSData *decryptedContent = nil; - if ([encryptedContent length]) - decryptedContent = [encryptedContent decryptWithSymmetricKey:[key subKeyOfLength:PearlCryptKeySize].keyData padding:YES]; + if ([encryptedContent length]) { + NSData *encryptionKey = [key keyDataForAlgorithm:self trimmedLength:PearlCryptKeySize]; + decryptedContent = [encryptedContent decryptWithSymmetricKey:encryptionKey padding:YES]; + } if (!decryptedContent) return nil; @@ -711,7 +710,7 @@ - (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPSiteType)type byAttacker:(MPAttacker)attacker { - if (!type) + if (!(type & MPSiteTypeClassGenerated)) return NO; size_t count = 0; const char **templates = mpw_templatesForType( type, &count ); diff --git a/MasterPassword/ObjC/MPAlgorithmV1.m b/MasterPassword/ObjC/MPAlgorithmV1.m index 44be3caa..a174de06 100644 --- a/MasterPassword/ObjC/MPAlgorithmV1.m +++ b/MasterPassword/ObjC/MPAlgorithmV1.m @@ -27,7 +27,7 @@ - (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit { - if (site.version != [self version] - 1) + if ([site.algorithm version] != [self version] - 1) // Only migrate from previous version. return NO; @@ -41,7 +41,7 @@ // Apply migration. site.requiresExplicitMigration = NO; - site.version = [self version]; + site.algorithm = self; return YES; } diff --git a/MasterPassword/ObjC/MPAlgorithmV2.m b/MasterPassword/ObjC/MPAlgorithmV2.m index a68d24bf..b5e67f68 100644 --- a/MasterPassword/ObjC/MPAlgorithmV2.m +++ b/MasterPassword/ObjC/MPAlgorithmV2.m @@ -15,7 +15,6 @@ // Copyright 2012 lhunath (Maarten Billemont). All rights reserved. // -#import #import "MPAlgorithmV2.h" #import "MPEntities.h" @@ -28,7 +27,7 @@ - (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit { - if (site.version != [self version] - 1) + if ([site.algorithm version] != [self version] - 1) // Only migrate from previous version. return NO; @@ -42,7 +41,7 @@ // Apply migration. site.requiresExplicitMigration = NO; - site.version = [self version]; + site.algorithm = self; return YES; } diff --git a/MasterPassword/ObjC/MPAlgorithmV3.m b/MasterPassword/ObjC/MPAlgorithmV3.m index 0be028ee..4d4f8b19 100644 --- a/MasterPassword/ObjC/MPAlgorithmV3.m +++ b/MasterPassword/ObjC/MPAlgorithmV3.m @@ -27,12 +27,13 @@ - (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit { - if (site.version != [self version] - 1) + if ([site.algorithm version] != [self version] - 1) // Only migrate from previous version. return NO; if (!explicit) { - if (site.type & MPSiteTypeClassGenerated && site.name.length != [site.name dataUsingEncoding:NSUTF8StringEncoding].length) { + if (site.type & MPSiteTypeClassGenerated && + site.user.name.length != [site.user.name dataUsingEncoding:NSUTF8StringEncoding].length) { // This migration requires explicit permission for types of the generated class. site.requiresExplicitMigration = YES; return NO; @@ -41,7 +42,7 @@ // Apply migration. site.requiresExplicitMigration = NO; - site.version = [self version]; + site.algorithm = self; return YES; } diff --git a/MasterPassword/ObjC/MPAppDelegate_Key.m b/MasterPassword/ObjC/MPAppDelegate_Key.m index 0ba0fa90..2b993521 100644 --- a/MasterPassword/ObjC/MPAppDelegate_Key.m +++ b/MasterPassword/ObjC/MPAppDelegate_Key.m @@ -31,12 +31,12 @@ static NSDictionary *keyQuery(MPUserEntity *user) { NSData *keyData = [PearlKeyChain dataOfItemForQuery:keyQuery( user )]; if (!keyData) { - inf( @"No key found in keychain for: %@", user.userID ); + inf( @"No key found in keychain for user: %@", user.userID ); return nil; } - inf( @"Found key in keychain for: %@", user.userID ); - return [MPAlgorithmDefault keyFromKeyData:keyData]; + inf( @"Found key in keychain for user: %@", user.userID ); + return [[MPKey alloc] initForFullName:user.name withKeyData:keyData forAlgorithm:user.algorithm]; } - (void)storeSavedKeyFor:(MPUserEntity *)user { @@ -44,12 +44,12 @@ static NSDictionary *keyQuery(MPUserEntity *user) { if (user.saveKey) { NSData *existingKeyData = [PearlKeyChain dataOfItemForQuery:keyQuery( user )]; - if (![existingKeyData isEqualToData:self.key.keyData]) { - inf( @"Saving key in keychain for: %@", user.userID ); + if (![existingKeyData isEqualToData:[self.key keyDataForAlgorithm:user.algorithm]]) { + inf( @"Saving key in keychain for user: %@", user.userID ); [PearlKeyChain addOrUpdateItemForQuery:keyQuery( user ) withAttributes:@{ - (__bridge id)kSecValueData : self.key.keyData, + (__bridge id)kSecValueData : [self.key keyDataForAlgorithm:user.algorithm], #if TARGET_OS_IPHONE (__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly, #endif @@ -62,7 +62,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) { OSStatus result = [PearlKeyChain deleteItemForQuery:keyQuery( user )]; if (result == noErr) { - inf( @"Removed key from keychain for: %@", user.userID ); + inf( @"Removed key from keychain for user: %@", user.userID ); [[NSNotificationCenter defaultCenter] postNotificationName:MPKeyForgottenNotification object:self]; } @@ -88,8 +88,8 @@ static NSDictionary *keyQuery(MPUserEntity *user) { // Method 1: When the user has no keyID set, set a new key from the given master password. if (!user.keyID) { - if ([password length] && (tryKey = [MPAlgorithmDefault keyForPassword:password ofUserNamed:user.name])) { - user.keyID = tryKey.keyID; + if ([password length] && (tryKey = [[MPKey alloc] initForFullName:user.name withMasterPassword:password])) { + user.keyID = [tryKey keyIDForAlgorithm:MPAlgorithmDefault]; // Migrate existing sites. [self migrateSitesForUser:user saveInContext:moc toKey:tryKey]; @@ -103,9 +103,11 @@ static NSDictionary *keyQuery(MPUserEntity *user) { else if (!tryKey) { // Key should be saved in keychain. Load it. - if ((tryKey = [self loadSavedKeyFor:user]) && ![user.keyID isEqual:tryKey.keyID]) { + if ((tryKey = [self loadSavedKeyFor:user]) && ![user.keyID isEqual:[tryKey keyIDForAlgorithm:user.algorithm]]) { // Loaded password doesn't match user's keyID. Forget saved password: it is incorrect. - inf( @"Saved password doesn't match keyID for: %@", user.userID ); + inf( @"Saved password doesn't match keyID for user: %@", user.userID ); + trc( @"user keyID: %@ (version: %d) != authentication keyID: %@", + user.keyID, user.algorithm.version, [tryKey keyIDForAlgorithm:user.algorithm] ); tryKey = nil; [self forgetSavedKeyFor:user]; @@ -113,9 +115,11 @@ static NSDictionary *keyQuery(MPUserEntity *user) { } // Method 3: Check the given master password string. - if (!tryKey && [password length] && (tryKey = [MPAlgorithmDefault keyForPassword:password ofUserNamed:user.name]) && - ![user.keyID isEqual:tryKey.keyID]) { - inf( @"Key derived from password doesn't match keyID for: %@", user.userID ); + if (!tryKey && [password length] && (tryKey = [[MPKey alloc] initForFullName:user.name withMasterPassword:password]) && + ![user.keyID isEqual:[tryKey keyIDForAlgorithm:user.algorithm]]) { + inf( @"Key derived from password doesn't match keyID for user: %@", user.userID ); + trc( @"user keyID: %@ (version: %u) != authentication keyID: %@", + user.keyID, user.algorithm.version, [tryKey keyIDForAlgorithm:user.algorithm] ); tryKey = nil; } @@ -123,13 +127,22 @@ static NSDictionary *keyQuery(MPUserEntity *user) { // No more methods left, fail if key still not known. if (!tryKey) { if (password) - inf( @"Login failed for: %@", user.userID ); + inf( @"Password login failed for user: %@", user.userID ); + else + dbg( @"Automatic login failed for user: %@", user.userID ); return NO; } - inf( @"Logged in: %@", user.userID ); + inf( @"Logged in user: %@", user.userID ); if (![self.key isEqualToKey:tryKey]) { + // Upgrade the user's keyID if not at the default version yet. + if (user.algorithm.version != MPAlgorithmDefaultVersion) { + user.algorithm = MPAlgorithmDefault; + user.keyID = [tryKey keyIDForAlgorithm:user.algorithm]; + inf( @"Upgraded keyID to version %u for user: %@", user.algorithm.version, user.userID ); + } + self.key = tryKey; [self storeSavedKeyFor:user]; } @@ -205,7 +218,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) { // Don't Migrate break; - recoverKey = [site.algorithm keyForPassword:masterPassword ofUserNamed:user.name]; + recoverKey = [[MPKey alloc] initForFullName:user.name withMasterPassword:masterPassword]; } if (!content) diff --git a/MasterPassword/ObjC/MPAppDelegate_Store.m b/MasterPassword/ObjC/MPAppDelegate_Store.m index ca44a589..730f8622 100644 --- a/MasterPassword/ObjC/MPAppDelegate_Store.m +++ b/MasterPassword/ObjC/MPAppDelegate_Store.m @@ -423,14 +423,15 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted ); } MPSiteType type = activeUser.defaultType; - NSString *typeEntityName = [MPAlgorithmDefault classNameOfType:type]; + id algorithm = MPAlgorithmDefault; + Class entityType = [algorithm classOfType:type]; - MPSiteEntity *site = [NSEntityDescription insertNewObjectForEntityForName:typeEntityName inManagedObjectContext:context]; + MPSiteEntity *site = (MPSiteEntity *)[entityType insertNewObjectInContext:context]; site.name = siteName; site.user = activeUser; site.type = type; site.lastUsed = [NSDate date]; - site.version = MPAlgorithmDefaultVersion; + site.algorithm = algorithm; NSError *error = nil; if (site.objectID.isTemporaryID && ![context obtainPermanentIDsForObjects:@[ site ] error:&error]) @@ -454,14 +455,14 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted ); else { // Type requires a different class of site. Recreate the site. - NSString *typeEntityName = [site.algorithm classNameOfType:type]; - MPSiteEntity *newSite = [NSEntityDescription insertNewObjectForEntityForName:typeEntityName inManagedObjectContext:context]; + Class entityType = [site.algorithm classOfType:type]; + MPSiteEntity *newSite = (MPSiteEntity *)[entityType insertNewObjectInContext:context]; newSite.type = type; newSite.name = site.name; newSite.user = site.user; newSite.uses = site.uses; newSite.lastUsed = site.lastUsed; - newSite.version = site.version; + newSite.algorithm = site.algorithm; newSite.loginName = site.loginName; NSError *error = nil; @@ -685,13 +686,13 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted ); inf( @"Import cancelled." ); return MPImportResultCancelled; } - MPKey *userKey = [MPAlgorithmDefault keyForPassword:userMasterPassword ofUserNamed:user? user.name: importUserName]; - if (user && ![userKey.keyID isEqualToData:user.keyID]) + MPKey *userKey = [[MPKey alloc] initForFullName:user? user.name: importUserName withMasterPassword:userMasterPassword]; + if (user && ![[userKey keyIDForAlgorithm:user.algorithm] isEqualToData:user.keyID]) return MPImportResultInvalidPassword; __block MPKey *importKey = userKey; - if (importKeyID && ![importKey.keyID isEqualToData:importKeyID]) - importKey = [importAlgorithm keyForPassword:askImportPassword( importUserName ) ofUserNamed:importUserName]; - if (importKeyID && ![importKey.keyID isEqualToData:importKeyID]) + if (importKeyID && ![[importKey keyIDForAlgorithm:importAlgorithm] isEqualToData:importKeyID]) + importKey = [[MPKey alloc] initForFullName:importUserName withMasterPassword:askImportPassword( importUserName )]; + if (importKeyID && ![[importKey keyIDForAlgorithm:importAlgorithm] isEqualToData:importKeyID]) return MPImportResultInvalidPassword; // Delete existing sites. @@ -710,7 +711,8 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted ); else { user = [MPUserEntity insertNewObjectInContext:context]; user.name = importUserName; - user.keyID = [userKey keyID]; + user.algorithm = MPAlgorithmDefault; + user.keyID = [userKey keyIDForAlgorithm:user.algorithm]; if (importAvatar != NSNotFound) user.avatar = importAvatar; dbg( @"Created User: %@", [user debugDescription] ); @@ -728,19 +730,20 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted ); NSString *exportContent = siteElements[7]; // Create new site. - NSString *typeEntityName = [MPAlgorithmForVersion( version ) classNameOfType:type]; - if (!typeEntityName) { + id algorithm = MPAlgorithmForVersion( version ); + Class entityType = [algorithm classOfType:type]; + if (!entityType) { err( @"Invalid site type in import file: %@ has type %lu", siteName, (long)type ); return MPImportResultInternalError; } - MPSiteEntity *site = [NSEntityDescription insertNewObjectForEntityForName:typeEntityName inManagedObjectContext:context]; + MPSiteEntity *site = (MPSiteEntity *)[entityType insertNewObjectInContext:context]; site.name = siteName; site.loginName = loginName; site.user = user; site.type = type; site.uses = uses; site.lastUsed = lastUsed; - site.version = version; + site.algorithm = algorithm; if ([exportContent length]) { if (clearText) [site.algorithm importClearTextPassword:exportContent intoSite:site usingKey:userKey]; @@ -768,7 +771,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted ); - (NSString *)exportSitesRevealPasswords:(BOOL)revealPasswords { MPUserEntity *activeUser = [self activeUserForMainThread]; - inf( @"Exporting sites, %@, for: %@", revealPasswords? @"revealing passwords": @"omitting passwords", activeUser.userID ); + inf( @"Exporting sites, %@, for user: %@", revealPasswords? @"revealing passwords": @"omitting passwords", activeUser.userID ); // Header. NSMutableString *export = [NSMutableString new]; @@ -799,7 +802,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted ); NSDate *lastUsed = site.lastUsed; NSUInteger uses = site.uses; MPSiteType type = site.type; - NSUInteger version = site.version; + id algorithm = site.algorithm; NSUInteger counter = 0; NSString *loginName = site.loginName; NSString *siteName = site.name; @@ -820,7 +823,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted ); [export appendFormat:@"%@ %8ld %8s %25s\t%25s\t%@\n", [[NSDateFormatter rfc3339DateFormatter] stringFromDate:lastUsed], (long)uses, - [strf( @"%lu:%lu:%lu", (long)type, (long)version, (long)counter ) UTF8String], + [strf( @"%lu:%lu:%lu", (long)type, (long)[algorithm version], (long)counter ) UTF8String], [(loginName?: @"") UTF8String], [siteName UTF8String], content?: @""]; } diff --git a/MasterPassword/ObjC/MPEntities.h b/MasterPassword/ObjC/MPEntities.h index 0d1930f3..db2a82c8 100644 --- a/MasterPassword/ObjC/MPEntities.h +++ b/MasterPassword/ObjC/MPEntities.h @@ -31,9 +31,8 @@ @property(readonly) NSString *typeClassName; @property(readonly) Class typeClass; @property(assign) NSUInteger uses; -@property(assign) NSUInteger version; @property(assign) BOOL requiresExplicitMigration; -@property(readonly) id algorithm; +@property(strong) id algorithm; - (NSUInteger)use; - (BOOL)tryMigrateExplicitly:(BOOL)explicit; @@ -56,6 +55,7 @@ @property(assign) BOOL saveKey; @property(assign) MPSiteType defaultType; @property(readonly) NSString *userID; +@property(strong) id algorithm; + (NSString *)idFor:(NSString *)userName; diff --git a/MasterPassword/ObjC/MPEntities.m b/MasterPassword/ObjC/MPEntities.m index b69d90a7..54dc7381 100644 --- a/MasterPassword/ObjC/MPEntities.m +++ b/MasterPassword/ObjC/MPEntities.m @@ -8,6 +8,7 @@ #import "MPEntities.h" #import "MPAppDelegate_Shared.h" +#import "MPAppDelegate_Key.h" @implementation NSManagedObjectContext(MP) @@ -90,14 +91,14 @@ self.uses_ = @(anUses); } -- (NSUInteger)version { +- (id)algorithm { - return [self.version_ unsignedIntegerValue]; + return MPAlgorithmForVersion( MIN( MPAlgorithmVersionCurrent, MAX( MPAlgorithmVersion0, [self.version_ unsignedIntegerValue] ) ) ); } -- (void)setVersion:(NSUInteger)version { +- (void)setAlgorithm:(id)algorithm { - self.version_ = @(version); + self.version_ = @([algorithm version]); } - (BOOL)requiresExplicitMigration { @@ -110,11 +111,6 @@ self.requiresExplicitMigration_ = @(requiresExplicitMigration); } -- (id)algorithm { - - return MPAlgorithmForVersion( self.version ); -} - - (NSUInteger)use { self.lastUsed = [NSDate date]; @@ -128,21 +124,31 @@ - (NSString *)debugDescription { - @try { - return strf( @"{%@: name=%@, user=%@, type=%lu, uses=%ld, lastUsed=%@, version=%ld, loginName=%@, requiresExplicitMigration=%d}", - NSStringFromClass( [self class] ), self.name, self.user.name, (long)self.type, (long)self.uses, self.lastUsed, - (long)self.version, - self.loginName, self.requiresExplicitMigration ); - } @catch (NSException *exception) { - return strf( @"{%@: inaccessible: %@}", - NSStringFromClass( [self class] ), [exception fullDescription] ); - } + __block NSString *debugDescription = strf( @"{%@: [recursing]}", [self class] ); + + static BOOL recursing = NO; + PearlIfNotRecursing( &recursing, ^{ + @try { + debugDescription = strf( + @"{%@: name=%@, user=%@, type=%lu, uses=%ld, lastUsed=%@, version=%ld, loginName=%@, requiresExplicitMigration=%d}", + NSStringFromClass( [self class] ), self.name, self.user.name, (long)self.type, (long)self.uses, self.lastUsed, + (long)[self.algorithm version], + self.loginName, self.requiresExplicitMigration ); + } + @catch (NSException *exception) { + debugDescription = strf( @"{%@: inaccessible: %@}", + NSStringFromClass( [self class] ), [exception fullDescription] ); + } + } ); + + return debugDescription; } - (BOOL)tryMigrateExplicitly:(BOOL)explicit { - while (self.version < MPAlgorithmDefaultVersion) { - NSUInteger toVersion = self.version + 1; + MPAlgorithmVersion algorithmVersion; + while ((algorithmVersion = [self.algorithm version]) < MPAlgorithmDefaultVersion) { + NSUInteger toVersion = algorithmVersion + 1; if (![MPAlgorithmForVersion( toVersion ) tryMigrateSite:self explicit:explicit]) { wrn( @"%@ migration to version: %ld failed for site: %@", explicit? @"Explicit": @"Automatic", (long)toVersion, self ); @@ -287,6 +293,17 @@ self.defaultType_ = @(aDefaultType); } +- (id)algorithm { + + return MPAlgorithmForVersion( MIN( MPAlgorithmVersionCurrent, MAX( MPAlgorithmVersion0, [self.version_ unsignedIntegerValue] ) ) ); +} + +- (void)setAlgorithm:(id)version { + + self.version_ = @([version version]); + [[MPAppDelegate_Shared get] forgetSavedKeyFor:self]; +} + - (NSString *)userID { return [MPUserEntity idFor:self.name]; diff --git a/MasterPassword/ObjC/MPKey.h b/MasterPassword/ObjC/MPKey.h index ac8b2854..084c5d17 100644 --- a/MasterPassword/ObjC/MPKey.h +++ b/MasterPassword/ObjC/MPKey.h @@ -16,17 +16,21 @@ // #import +#import "MPAlgorithm.h" @protocol MPAlgorithm; @interface MPKey : NSObject -@property(nonatomic, readonly, strong) id algorithm; -@property(nonatomic, readonly, strong) NSData *keyData; -@property(nonatomic, readonly, strong) NSData *keyID; +@property(nonatomic, readonly) NSString *fullName; + +- (instancetype)initForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword; +- (instancetype)initForFullName:(NSString *)fullName withKeyData:(NSData *)keyData forAlgorithm:(id)algorithm; + +- (NSData *)keyIDForAlgorithm:(id)algorithm; +- (NSData *)keyDataForAlgorithm:(id)algorithm; +- (NSData *)keyDataForAlgorithm:(id)algorithm trimmedLength:(NSUInteger)subKeyLength; -- (id)initWithKeyData:(NSData *)keyData algorithm:(id)algorithm; -- (MPKey *)subKeyOfLength:(NSUInteger)subKeyLength; - (BOOL)isEqualToKey:(MPKey *)key; @end diff --git a/MasterPassword/ObjC/MPKey.m b/MasterPassword/ObjC/MPKey.m index 940d096d..cdef3611 100644 --- a/MasterPassword/ObjC/MPKey.m +++ b/MasterPassword/ObjC/MPKey.m @@ -1,12 +1,12 @@ /** - * Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com) - * - * See the enclosed file LICENSE for license information (LGPLv3). If you did - * not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt - * - * @author Maarten Billemont - * @license http://www.gnu.org/licenses/lgpl-3.0.txt - */ +* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com) +* +* See the enclosed file LICENSE for license information (LGPLv3). If you did +* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt +* +* @author Maarten Billemont +* @license http://www.gnu.org/licenses/lgpl-3.0.txt +*/ // // MPKey @@ -19,38 +19,61 @@ @interface MPKey() -@property(nonatomic, readwrite, strong) id algorithm; -@property(nonatomic, readwrite, strong) NSData *keyData; -@property(nonatomic, readwrite, strong) NSData *keyID; +@property(nonatomic) NSString *fullName; +@property(nonatomic) NSString *masterPassword; @end -@implementation MPKey +@implementation MPKey { + NSCache *_keyCache; +}; -@synthesize algorithm = _algorithm, keyData = _keyData, keyID = _keyID; - -- (id)initWithKeyData:(NSData *)keyData algorithm:(id)algorithm { +- (instancetype)initForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword { if (!(self = [super init])) return nil; - self.keyData = keyData; - self.algorithm = algorithm; - self.keyID = [self.algorithm keyIDForKeyData:keyData]; + _keyCache = [NSCache new]; + self.fullName = fullName; + self.masterPassword = masterPassword; return self; } -- (MPKey *)subKeyOfLength:(NSUInteger)subKeyLength { +- (instancetype)initForFullName:(NSString *)fullName withKeyData:(NSData *)keyData forAlgorithm:(id)algorithm { - NSData *subKeyData = [self.keyData subdataWithRange:NSMakeRange( 0, MIN(subKeyLength, self.keyData.length) )]; + if (!(self = [self initForFullName:fullName withMasterPassword:nil])) + return nil; - return [self.algorithm keyFromKeyData:subKeyData]; + [_keyCache setObject:keyData forKey:algorithm]; + + return self; +} + +- (NSData *)keyIDForAlgorithm:(id)algorithm { + + return [algorithm keyIDForKeyData:[self keyDataForAlgorithm:algorithm]]; +} + +- (NSData *)keyDataForAlgorithm:(id)algorithm { + + NSData *keyData = [_keyCache objectForKey:algorithm]; + if (!keyData) + [_keyCache setObject:keyData = [algorithm keyDataForFullName:self.fullName withMasterPassword:self.masterPassword] + forKey:algorithm]; + + return keyData; +} + +- (NSData *)keyDataForAlgorithm:(id)algorithm trimmedLength:(NSUInteger)subKeyLength { + + NSData *keyData = [self keyDataForAlgorithm:algorithm]; + return [keyData subdataWithRange:NSMakeRange( 0, MIN( subKeyLength, keyData.length ) )]; } - (BOOL)isEqualToKey:(MPKey *)key { - return [self.keyID isEqualToData:key.keyID]; + return [self.fullName isEqualToString:key.fullName] && [self.masterPassword isEqualToString:self.masterPassword]; } - (BOOL)isEqual:(id)object { diff --git a/MasterPassword/ObjC/MPUserEntity.h b/MasterPassword/ObjC/MPUserEntity.h index eb615712..ef838dd0 100644 --- a/MasterPassword/ObjC/MPUserEntity.h +++ b/MasterPassword/ObjC/MPUserEntity.h @@ -19,6 +19,7 @@ @property (nonatomic, retain) NSDate * lastUsed; @property (nonatomic, retain) NSString * name; @property (nonatomic, retain) NSNumber * saveKey_; +@property (nonatomic, retain) NSNumber * version_; @property (nonatomic, retain) NSSet *sites; @end diff --git a/MasterPassword/ObjC/MPUserEntity.m b/MasterPassword/ObjC/MPUserEntity.m index da97fdcc..c2cdaff0 100644 --- a/MasterPassword/ObjC/MPUserEntity.m +++ b/MasterPassword/ObjC/MPUserEntity.m @@ -18,6 +18,7 @@ @dynamic lastUsed; @dynamic name; @dynamic saveKey_; +@dynamic version_; @dynamic sites; @end diff --git a/MasterPassword/ObjC/Mac/MasterPassword-Mac.xcodeproj/project.pbxproj b/MasterPassword/ObjC/Mac/MasterPassword-Mac.xcodeproj/project.pbxproj index 51a87fa0..5b2d3466 100644 --- a/MasterPassword/ObjC/Mac/MasterPassword-Mac.xcodeproj/project.pbxproj +++ b/MasterPassword/ObjC/Mac/MasterPassword-Mac.xcodeproj/project.pbxproj @@ -31,7 +31,6 @@ DA16B345170661F2000A0EAB /* libPearl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC77CAD148291A600BCF976 /* libPearl.a */; }; DA2508F119511D3600AC23F1 /* MPPasswordWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = DA2508F019511D3600AC23F1 /* MPPasswordWindowController.xib */; }; DA250925195148E200AC23F1 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAEBC45214F6364500987BF6 /* QuartzCore.framework */; }; - DA29992C19C6A89900AF7DF1 /* MasterPassword.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DA29992619C6A89900AF7DF1 /* MasterPassword.xcdatamodeld */; }; DA2CA4ED18D323D3007798F8 /* NSError+PearlFullDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = DA2CA4E718D323D3007798F8 /* NSError+PearlFullDescription.m */; }; DA2CA4EE18D323D3007798F8 /* NSError+PearlFullDescription.h in Headers */ = {isa = PBXBuildFile; fileRef = DA2CA4E818D323D3007798F8 /* NSError+PearlFullDescription.h */; }; DA2CA4EF18D323D3007798F8 /* NSArray+Pearl.m in Sources */ = {isa = PBXBuildFile; fileRef = DA2CA4E918D323D3007798F8 /* NSArray+Pearl.m */; }; @@ -92,6 +91,7 @@ DA6774451A474A3B004F356A /* mpw-types.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773C21A4746AF004F356A /* mpw-types.c */; }; DA6774461A474A3B004F356A /* mpw-util.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773C51A4746AF004F356A /* mpw-util.c */; }; DA67744A1A47C8F7004F356A /* mpw-tests-util.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6774481A47C8F7004F356A /* mpw-tests-util.c */; }; + DA8495301A915EF400B3053D /* MasterPassword.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DA8495281A915EF400B3053D /* MasterPassword.xcdatamodeld */; }; DA89D4EC1A51EABD00AC64D7 /* Pearl-Cocoa.h in Headers */ = {isa = PBXBuildFile; fileRef = DA89D4EA1A51EABD00AC64D7 /* Pearl-Cocoa.h */; }; DA89D4ED1A51EABD00AC64D7 /* Pearl-Cocoa.m in Sources */ = {isa = PBXBuildFile; fileRef = DA89D4EB1A51EABD00AC64D7 /* Pearl-Cocoa.m */; }; DA8ED895192906920099B726 /* PearlTween.m in Sources */ = {isa = PBXBuildFile; fileRef = DA8ED891192906920099B726 /* PearlTween.m */; }; @@ -294,11 +294,6 @@ DA25090719513C1400AC23F1 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; DA2509261951B86C00AC23F1 /* small-screen.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "small-screen.png"; sourceTree = ""; }; DA2509271951B86C00AC23F1 /* screen.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = screen.png; sourceTree = ""; }; - DA29992719C6A89900AF7DF1 /* MasterPassword 1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 1.xcdatamodel"; sourceTree = ""; }; - DA29992819C6A89900AF7DF1 /* MasterPassword 2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 2.xcdatamodel"; sourceTree = ""; }; - DA29992919C6A89900AF7DF1 /* MasterPassword 3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 3.xcdatamodel"; sourceTree = ""; }; - DA29992A19C6A89900AF7DF1 /* MasterPassword 4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 4.xcdatamodel"; sourceTree = ""; }; - DA29992B19C6A89900AF7DF1 /* MasterPassword 5.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 5.xcdatamodel"; sourceTree = ""; }; DA2CA4E718D323D3007798F8 /* NSError+PearlFullDescription.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSError+PearlFullDescription.m"; sourceTree = ""; }; DA2CA4E818D323D3007798F8 /* NSError+PearlFullDescription.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSError+PearlFullDescription.h"; sourceTree = ""; }; DA2CA4E918D323D3007798F8 /* NSArray+Pearl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Pearl.m"; sourceTree = ""; }; @@ -321,7 +316,6 @@ DA32CFE119CF1C71004F3F0E /* MPSiteQuestionEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSiteQuestionEntity.m; sourceTree = ""; }; DA32CFE319CF1C71004F3F0E /* MPUserEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUserEntity.h; sourceTree = ""; }; DA32CFE419CF1C71004F3F0E /* MPUserEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUserEntity.m; sourceTree = ""; }; - DA32D00019CF470E004F3F0E /* MasterPassword 6.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 6.xcdatamodel"; sourceTree = ""; }; DA3509FC15F101A500C14A8E /* PearlQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlQueue.h; sourceTree = ""; }; DA3509FD15F101A500C14A8E /* PearlQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlQueue.m; sourceTree = ""; }; DA3B844A190FC5A900246EEA /* Crashlytics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Crashlytics.framework; sourceTree = ""; }; @@ -796,6 +790,13 @@ DA831A281A6E1146000AC234 /* mpw-algorithm_v1.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = "mpw-algorithm_v1.c"; sourceTree = ""; }; DA831A291A6E1146000AC234 /* mpw-algorithm_v2.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = "mpw-algorithm_v2.c"; sourceTree = ""; }; DA831A2A1A6E1146000AC234 /* mpw-algorithm_v3.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = "mpw-algorithm_v3.c"; sourceTree = ""; }; + DA8495291A915EF400B3053D /* MasterPassword 1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 1.xcdatamodel"; sourceTree = ""; }; + DA84952A1A915EF400B3053D /* MasterPassword 2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 2.xcdatamodel"; sourceTree = ""; }; + DA84952B1A915EF400B3053D /* MasterPassword 3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 3.xcdatamodel"; sourceTree = ""; }; + DA84952C1A915EF400B3053D /* MasterPassword 4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 4.xcdatamodel"; sourceTree = ""; }; + DA84952D1A915EF400B3053D /* MasterPassword 5.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 5.xcdatamodel"; sourceTree = ""; }; + DA84952E1A915EF400B3053D /* MasterPassword 6.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 6.xcdatamodel"; sourceTree = ""; }; + DA84952F1A915EF400B3053D /* MasterPassword 7.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 7.xcdatamodel"; sourceTree = ""; }; DA89D4EA1A51EABD00AC64D7 /* Pearl-Cocoa.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "Pearl-Cocoa.h"; sourceTree = ""; }; DA89D4EB1A51EABD00AC64D7 /* Pearl-Cocoa.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "Pearl-Cocoa.m"; sourceTree = ""; }; DA8ED891192906920099B726 /* PearlTween.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlTween.m; sourceTree = ""; }; @@ -1060,8 +1061,8 @@ DA5E5C961724A667003798D8 /* ObjC */ = { isa = PBXGroup; children = ( + DA8495281A915EF400B3053D /* MasterPassword.xcdatamodeld */, DA5E5CB21724A667003798D8 /* Mac */, - DA29992619C6A89900AF7DF1 /* MasterPassword.xcdatamodeld */, DA5E5C971724A667003798D8 /* MPAlgorithm.h */, DA5E5C981724A667003798D8 /* MPAlgorithm.m */, DA5E5C991724A667003798D8 /* MPAlgorithmV0.h */, @@ -2245,7 +2246,6 @@ DA5180CE19FF307E00A587E9 /* MPAppDelegate_Store.m in Sources */, DA5E5CFA1724A667003798D8 /* MPAppDelegate_Shared.m in Sources */, DA5E5CFC1724A667003798D8 /* MPConfig.m in Sources */, - DA29992C19C6A89900AF7DF1 /* MasterPassword.xcdatamodeld in Sources */, DA3B8456190FC89700246EEA /* MPFixable.m in Sources */, DA5E5D001724A667003798D8 /* MPEntities.m in Sources */, DA5E5D011724A667003798D8 /* MPKey.m in Sources */, @@ -2262,6 +2262,7 @@ 93D39784E725A34D1EE3FB3B /* MPInitialWindowController.m in Sources */, DA32CFDF19CF1C70004F3F0E /* MPSiteEntity.m in Sources */, 93D394C4254EEB45FB335AFB /* MPSitesTableView.m in Sources */, + DA8495301A915EF400B3053D /* MasterPassword.xcdatamodeld in Sources */, DA6774291A4746AF004F356A /* mpw-algorithm.c in Sources */, 93D395E4830290EBB6E71F34 /* MPNoStateButton.m in Sources */, DA4DAE941A7D8117003E5423 /* MPAlgorithmV3.m in Sources */, @@ -2943,17 +2944,18 @@ /* End XCConfigurationList section */ /* Begin XCVersionGroup section */ - DA29992619C6A89900AF7DF1 /* MasterPassword.xcdatamodeld */ = { + DA8495281A915EF400B3053D /* MasterPassword.xcdatamodeld */ = { isa = XCVersionGroup; children = ( - DA29992719C6A89900AF7DF1 /* MasterPassword 1.xcdatamodel */, - DA29992819C6A89900AF7DF1 /* MasterPassword 2.xcdatamodel */, - DA29992919C6A89900AF7DF1 /* MasterPassword 3.xcdatamodel */, - DA29992A19C6A89900AF7DF1 /* MasterPassword 4.xcdatamodel */, - DA29992B19C6A89900AF7DF1 /* MasterPassword 5.xcdatamodel */, - DA32D00019CF470E004F3F0E /* MasterPassword 6.xcdatamodel */, + DA8495291A915EF400B3053D /* MasterPassword 1.xcdatamodel */, + DA84952A1A915EF400B3053D /* MasterPassword 2.xcdatamodel */, + DA84952B1A915EF400B3053D /* MasterPassword 3.xcdatamodel */, + DA84952C1A915EF400B3053D /* MasterPassword 4.xcdatamodel */, + DA84952D1A915EF400B3053D /* MasterPassword 5.xcdatamodel */, + DA84952E1A915EF400B3053D /* MasterPassword 6.xcdatamodel */, + DA84952F1A915EF400B3053D /* MasterPassword 7.xcdatamodel */, ); - currentVersion = DA32D00019CF470E004F3F0E /* MasterPassword 6.xcdatamodel */; + currentVersion = DA84952F1A915EF400B3053D /* MasterPassword 7.xcdatamodel */; path = MasterPassword.xcdatamodeld; sourceTree = ""; versionGroupType = wrapper.xcdatamodel; diff --git a/MasterPassword/ObjC/MasterPassword.xcdatamodeld/.xccurrentversion b/MasterPassword/ObjC/MasterPassword.xcdatamodeld/.xccurrentversion index 19de7217..1ac59ba4 100644 --- a/MasterPassword/ObjC/MasterPassword.xcdatamodeld/.xccurrentversion +++ b/MasterPassword/ObjC/MasterPassword.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MasterPassword 6.xcdatamodel + MasterPassword 7.xcdatamodel diff --git a/MasterPassword/ObjC/MasterPassword.xcdatamodeld/MasterPassword 6.xcdatamodel/contents b/MasterPassword/ObjC/MasterPassword.xcdatamodeld/MasterPassword 6.xcdatamodel/contents index f305f0c2..7c5f7ca9 100644 --- a/MasterPassword/ObjC/MasterPassword.xcdatamodeld/MasterPassword 6.xcdatamodel/contents +++ b/MasterPassword/ObjC/MasterPassword.xcdatamodeld/MasterPassword 6.xcdatamodel/contents @@ -1,5 +1,5 @@ - + diff --git a/MasterPassword/ObjC/MasterPassword.xcdatamodeld/MasterPassword 7.xcdatamodel/contents b/MasterPassword/ObjC/MasterPassword.xcdatamodeld/MasterPassword 7.xcdatamodel/contents new file mode 100644 index 00000000..536886aa --- /dev/null +++ b/MasterPassword/ObjC/MasterPassword.xcdatamodeld/MasterPassword 7.xcdatamodel/contents @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MasterPassword/ObjC/iOS/MPEmergencyViewController.h b/MasterPassword/ObjC/iOS/MPEmergencyViewController.h index 9eac4c9c..a974cc27 100644 --- a/MasterPassword/ObjC/iOS/MPEmergencyViewController.h +++ b/MasterPassword/ObjC/iOS/MPEmergencyViewController.h @@ -22,7 +22,7 @@ @property(weak, nonatomic) IBOutlet UIScrollView *scrollView; @property(weak, nonatomic) IBOutlet UIView *dialogView; @property(weak, nonatomic) IBOutlet UIView *containerView; -@property(weak, nonatomic) IBOutlet UITextField *userNameField; +@property(weak, nonatomic) IBOutlet UITextField *fullNameField; @property(weak, nonatomic) IBOutlet UITextField *masterPasswordField; @property(weak, nonatomic) IBOutlet UITextField *siteField; @property(weak, nonatomic) IBOutlet UIStepper *counterStepper; diff --git a/MasterPassword/ObjC/iOS/MPEmergencyViewController.m b/MasterPassword/ObjC/iOS/MPEmergencyViewController.m index b54718e5..99eeaa0d 100644 --- a/MasterPassword/ObjC/iOS/MPEmergencyViewController.m +++ b/MasterPassword/ObjC/iOS/MPEmergencyViewController.m @@ -75,7 +75,7 @@ - (IBAction)controlChanged:(UIControl *)control { - if (control == self.userNameField || control == self.masterPasswordField) + if (control == self.fullNameField || control == self.masterPasswordField) [self updateKey]; else [self updatePassword]; @@ -103,15 +103,15 @@ - (void)updateKey { - NSString *userName = self.userNameField.text; + NSString *fullName = self.fullNameField.text; NSString *masterPassword = self.masterPasswordField.text; self.passwordLabel.text = nil; [self.activity startAnimating]; [_emergencyKeyQueue cancelAllOperations]; [_emergencyKeyQueue addOperationWithBlock:^{ - if ([masterPassword length] && [userName length]) - _key = [MPAlgorithmDefault keyForPassword:masterPassword ofUserNamed:userName]; + if ([masterPassword length] && [fullName length]) + _key = [[MPKey alloc] initForFullName:fullName withMasterPassword:masterPassword]; else _key = nil; @@ -165,7 +165,7 @@ - (void)reset { - self.userNameField.text = nil; + self.fullNameField.text = nil; self.masterPasswordField.text = nil; self.siteField.text = nil; self.counterStepper.value = 1; diff --git a/MasterPassword/ObjC/iOS/MPMessageViewController.h b/MasterPassword/ObjC/iOS/MPMessageViewController.h new file mode 100644 index 00000000..5fd1578c --- /dev/null +++ b/MasterPassword/ObjC/iOS/MPMessageViewController.h @@ -0,0 +1,25 @@ +// +// MPPreferencesViewController.h +// MasterPassword-iOS +// +// Created by Maarten Billemont on 04/06/12. +// Copyright (c) 2012 Lyndir. All rights reserved. +// + +#import + +@interface MPMessage : NSObject + +@property(nonatomic) NSString *title; +@property(nonatomic) NSString *text; +@property(nonatomic) BOOL info; + ++ (instancetype)messageWithTitle:(NSString *)title text:(NSString *)text info:(BOOL)info; + +@end + +@interface MPMessageViewController : UIViewController + +@property (nonatomic) MPMessage *message; + +@end diff --git a/MasterPassword/ObjC/iOS/MPMessageViewController.m b/MasterPassword/ObjC/iOS/MPMessageViewController.m new file mode 100644 index 00000000..b6c2e0d0 --- /dev/null +++ b/MasterPassword/ObjC/iOS/MPMessageViewController.m @@ -0,0 +1,78 @@ +// +// MPPreferencesViewController.m +// MasterPassword-iOS +// +// Created by Maarten Billemont on 04/06/12. +// Copyright (c) 2012 Lyndir. All rights reserved. +// + +#import "MPMessageViewController.h" +#import "MPiOSAppDelegate.h" +#import "MPAppDelegate_Store.h" +#import "MPOverlayViewController.h" + +@interface MPMessageViewController() + +@property(nonatomic) IBOutlet UILabel *titleLabel; +@property(nonatomic) IBOutlet UILabel *messageLabel; +@property(nonatomic) IBOutlet UIView *infoView; + +@end + +@implementation MPMessage + ++ (instancetype)messageWithTitle:(NSString *)title text:(NSString *)text info:(BOOL)info { + + MPMessage *message = [MPMessage new]; + message.title = title; + message.text = text; + message.info = info; + + return message; +} + +@end + +@implementation MPMessageViewController + +#pragma mark - Life + +- (void)viewDidLoad { + + [super viewDidLoad]; + + self.view.backgroundColor = [UIColor clearColor]; +} + +- (void)viewWillAppear:(BOOL)animated { + + [super viewWillAppear:animated]; + + self.titleLabel.text = self.message.title; + self.messageLabel.text = self.message.text; + self.infoView.gone = !self.message.info; + + PearlAddNotificationObserver( MPSignedOutNotification, nil, [NSOperationQueue mainQueue], + ^(MPMessageViewController *self, NSNotification *note) { + if (![note.userInfo[@"animated"] boolValue]) + [UIView setAnimationsEnabled:NO]; + [[MPOverlaySegue dismissViewController:self] perform]; + [UIView setAnimationsEnabled:YES]; + } ); +} + +- (void)viewWillDisappear:(BOOL)animated { + + [super viewWillDisappear:animated]; + + PearlRemoveNotificationObservers(); +} + +- (UIStatusBarStyle)preferredStatusBarStyle { + + return UIStatusBarStyleLightContent; +} + +#pragma mark - State + +@end diff --git a/MasterPassword/ObjC/iOS/MPPasswordCell.m b/MasterPassword/ObjC/iOS/MPPasswordCell.m index 5fe6ec36..7261aa89 100644 --- a/MasterPassword/ObjC/iOS/MPPasswordCell.m +++ b/MasterPassword/ObjC/iOS/MPPasswordCell.m @@ -59,6 +59,8 @@ [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector( doRevealPassword: )]]; [self.counterButton addGestureRecognizer: [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector( doResetCounter: )]]; + [self.upgradeButton addGestureRecognizer: + [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector( doDowngrade: )]]; [self setupLayer]; @@ -331,13 +333,36 @@ - (IBAction)doUpgrade:(UIButton *)sender { [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { - if (![[self siteInContext:context] tryMigrateExplicitly:YES]) { + MPSiteEntity *siteEntity = [self siteInContext:context]; + if (![siteEntity tryMigrateExplicitly:YES]) { [PearlOverlay showTemporaryOverlayWithTitle:@"Couldn't Upgrade Site" dismissAfter:2]; return; } [context saveToStore]; - [PearlOverlay showTemporaryOverlayWithTitle:@"Site Upgraded" dismissAfter:2]; + [PearlOverlay showTemporaryOverlayWithTitle:strf( @"Site Upgraded to V%d", siteEntity.algorithm.version ) + dismissAfter:2]; + [self updateAnimated:YES]; + }]; +} + +- (IBAction)doDowngrade:(UILongPressGestureRecognizer *)recognizer { + + if (recognizer.state != UIGestureRecognizerStateBegan) + return; + + if (![[MPiOSConfig get].allowDowngrade boolValue]) + return; + + [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { + MPSiteEntity *siteEntity = [self siteInContext:context]; + if (siteEntity.algorithm.version <= 0) + return; + + siteEntity.algorithm = MPAlgorithmForVersion( siteEntity.algorithm.version - 1 ); + [context saveToStore]; + [PearlOverlay showTemporaryOverlayWithTitle:strf( @"Site Downgraded to V%d", siteEntity.algorithm.version ) + dismissAfter:2]; [self updateAnimated:YES]; }]; } @@ -479,7 +504,7 @@ MPSiteEntity *mainSite = [self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]]; // UI - self.upgradeButton.gone = !mainSite.requiresExplicitMigration; + self.upgradeButton.gone = !mainSite.requiresExplicitMigration && ![[MPiOSConfig get].allowDowngrade boolValue]; self.answersButton.gone = ![[MPiOSAppDelegate get] isFeatureUnlocked:MPProductGenerateAnswers]; BOOL settingsMode = self.mode == MPPasswordCellModeSettings; self.loginNameContainer.alpha = settingsMode || mainSite.loginGenerated || [mainSite.loginName length]? 0.7f: 0; @@ -533,15 +558,15 @@ [algorithm timeToCrack:&timeToCrack passwordString:password byAttacker:attackHardware]) timeToCrackString = NSStringFromTimeToCrack( timeToCrack ); + BOOL requiresExplicitMigration = site.requiresExplicitMigration; + PearlMainQueue( ^{ self.loginNameField.text = loginName; self.passwordField.text = password; self.strengthLabel.text = timeToCrackString; self.loginNameButton.titleLabel.alpha = [loginName length] || self.loginNameField.enabled? 0: 1; - if ([password length]) - self.indicatorView.alpha = 0; - else { + if (![password length]) { self.indicatorView.alpha = 1; [self.indicatorView removeFromSuperview]; [self.modeScrollView addSubview:self.indicatorView]; @@ -551,6 +576,18 @@ @"target" : settingsMode? self.editButton: self.modeButton }]; } + else if (requiresExplicitMigration) { + self.indicatorView.alpha = 1; + [self.indicatorView removeFromSuperview]; + [self.modeScrollView addSubview:self.indicatorView]; + [self.contentView addConstraintsWithVisualFormat:@"V:[indicator][target]" options:NSLayoutFormatAlignAllCenterX + metrics:nil views:@{ + @"indicator" : self.indicatorView, + @"target" : settingsMode? self.upgradeButton: self.modeButton + }]; + } + else + self.indicatorView.alpha = 0; } ); }]; diff --git a/MasterPassword/ObjC/iOS/MPPasswordsViewController.m b/MasterPassword/ObjC/iOS/MPPasswordsViewController.m index e0aa4753..3a75b112 100644 --- a/MasterPassword/ObjC/iOS/MPPasswordsViewController.m +++ b/MasterPassword/ObjC/iOS/MPPasswordsViewController.m @@ -23,6 +23,7 @@ #import "MPAppDelegate_Key.h" #import "MPPasswordCell.h" #import "MPAnswersViewController.h" +#import "MPMessageViewController.h" typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) { MPPasswordsBadNameTip = 1 << 0, @@ -90,7 +91,13 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) { [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:context]; if (![MPAlgorithmDefault tryMigrateUser:activeUser inContext:context]) - [PearlOverlay showTemporaryOverlayWithTitle:@"Some Sites Need Upgrade" dismissAfter:2]; + [self performSegueWithIdentifier:@"message" sender: + [MPMessage messageWithTitle:@"You have sites that can be upgraded." text: + @"Upgrading a site allows it to take advantage of the latest improvements in the Master Password algorithm.\n\n" + "When you upgrade a site, a new and stronger password will be generated for it. To upgrade a site, first log into the site, navigate to your account preferences where you can change the site's password. Make sure you fill in any \"current password\" fields on the website first, then press the upgrade button here to get your new site password.\n\n" + "You can then update your site's account with the new and stronger password.\n\n" + "The upgrade button can be found in the site's settings and looks like this:" + info:YES]]; [context saveToStore]; }]; } @@ -109,6 +116,8 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) { if ([segue.identifier isEqualToString:@"answers"]) ((MPAnswersViewController *)segue.destinationViewController).site = [[MPPasswordCell findAsSuperviewOf:sender] siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]]; + if ([segue.identifier isEqualToString:@"message"]) + ((MPMessageViewController *)segue.destinationViewController).message = sender; } - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { diff --git a/MasterPassword/ObjC/iOS/MPUsersViewController.m b/MasterPassword/ObjC/iOS/MPUsersViewController.m index f25869bc..f5a21501 100644 --- a/MasterPassword/ObjC/iOS/MPUsersViewController.m +++ b/MasterPassword/ObjC/iOS/MPUsersViewController.m @@ -214,6 +214,7 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) { MPUserEntity *user = [self userForAvatar:avatarCell inContext:context isNew:&isNew]; if (isNew) { user = [MPUserEntity insertNewObjectInContext:context]; + user.algorithm = MPAlgorithmDefault; user.avatar = avatarCell.avatar; user.name = avatarCell.name; } diff --git a/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m b/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m index 7b751758..eea75a52 100644 --- a/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m +++ b/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m @@ -73,9 +73,9 @@ PearlAddNotificationObserver( MPCheckConfigNotification, nil, [NSOperationQueue mainQueue], ^(id self, NSNotification *note) { [self updateConfigKey:note.object]; } ); - PearlAddNotificationObserver( kIASKAppSettingChanged, nil, nil, ^(id self, NSNotification *note) { - [[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:note.object]; - } ); +// PearlAddNotificationObserver( kIASKAppSettingChanged, nil, nil, ^(id self, NSNotification *note) { +// [[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:note.object]; +// } ); PearlAddNotificationObserver( NSUserDefaultsDidChangeNotification, nil, nil, ^(id self, NSNotification *note) { [[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:nil]; } ); @@ -496,7 +496,7 @@ return; [moc performBlockAndWait:^{ - inf( @"Unsetting master password for: %@.", user.userID ); + inf( @"Clearing keyID for user: %@.", user.userID ); user.keyID = nil; [self forgetSavedKeyFor:user]; [moc saveToStore]; diff --git a/MasterPassword/ObjC/iOS/MPiOSConfig.h b/MasterPassword/ObjC/iOS/MPiOSConfig.h index 4061ce56..0c99d306 100644 --- a/MasterPassword/ObjC/iOS/MPiOSConfig.h +++ b/MasterPassword/ObjC/iOS/MPiOSConfig.h @@ -18,6 +18,7 @@ @property(nonatomic, retain) NSNumber *loginNameTipShown; @property(nonatomic, retain) NSNumber *traceMode; @property(nonatomic, retain) NSNumber *dictationSearch; +@property(nonatomic, retain) NSNumber *allowDowngrade; @property(nonatomic, retain) NSNumber *developmentFuelRemaining; @property(nonatomic, retain) NSNumber *developmentFuelInvested; @property(nonatomic, retain) NSNumber *developmentFuelConsumption; diff --git a/MasterPassword/ObjC/iOS/MPiOSConfig.m b/MasterPassword/ObjC/iOS/MPiOSConfig.m index b6806958..4baf43d6 100644 --- a/MasterPassword/ObjC/iOS/MPiOSConfig.m +++ b/MasterPassword/ObjC/iOS/MPiOSConfig.m @@ -8,7 +8,7 @@ @implementation MPiOSConfig -@dynamic helpHidden, siteInfoHidden, showSetup, actionsTipShown, typeTipShown, loginNameTipShown, traceMode, dictationSearch; +@dynamic helpHidden, siteInfoHidden, showSetup, actionsTipShown, typeTipShown, loginNameTipShown, traceMode, dictationSearch, allowDowngrade; @dynamic developmentFuelRemaining, developmentFuelInvested, developmentFuelConsumption, developmentFuelChecked; - (id)init { @@ -26,6 +26,7 @@ NSStringFromSelector( @selector( loginNameTipShown ) ) : @NO, NSStringFromSelector( @selector( traceMode ) ) : @NO, NSStringFromSelector( @selector( dictationSearch ) ) : @NO, + NSStringFromSelector( @selector( allowDowngrade ) ) : @NO, }]; return self; diff --git a/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj b/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj index 01fc85a2..dfb53cfd 100644 --- a/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj +++ b/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ 93D393DB5325820241BA90A7 /* PearlSizedTextView.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39A4759186F6D2D34AA6B /* PearlSizedTextView.h */; }; 93D394982CBD25D46692DD7C /* MPWebViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3990E0CD1B5CF9FBB2C07 /* MPWebViewController.m */; }; 93D394B5036C882B33C71872 /* MPPasswordsSegue.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39E7A12CC352B2825AA66 /* MPPasswordsSegue.m */; }; + 93D39508A6814612A5B3C226 /* MPMessageViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D399B36CDB2004D7C51391 /* MPMessageViewController.m */; }; 93D39536EB550E811CCD04BC /* UIResponder+PearlFirstResponder.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D394482BB07F90E8FD1314 /* UIResponder+PearlFirstResponder.h */; }; 93D3954E96236384AFA00453 /* UIScrollView+PearlAdjustInsets.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D390FB3110DCCE68E600DC /* UIScrollView+PearlAdjustInsets.m */; }; 93D3954FCE045A3CC7E804B7 /* MPUsersViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D399E571F61E50A9BF8FAF /* MPUsersViewController.m */; }; @@ -179,6 +180,8 @@ DA7304E5194E025900E72520 /* tip_basic_black.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD38901711E29700CF925C /* tip_basic_black.png */; }; DA7304E6194E025900E72520 /* tip_basic_black@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD38911711E29700CF925C /* tip_basic_black@2x.png */; }; DA7304E7194E027C00E72520 /* Square-bottom.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5E5C3C1723681B003798D8 /* Square-bottom.png */; }; + DA8495311A93049300B3053D /* icon_down.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD375C1711E29500CF925C /* icon_down.png */; }; + DA8495321A93049300B3053D /* icon_down@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD375D1711E29500CF925C /* icon_down@2x.png */; }; DA854C8318D4CFBF00106317 /* avatar-add@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA854C8118D4CFBF00106317 /* avatar-add@2x.png */; }; DA854C8418D4CFBF00106317 /* avatar-add.png in Resources */ = {isa = PBXBuildFile; fileRef = DA854C8218D4CFBF00106317 /* avatar-add.png */; }; DA945C8717E3F3FD0053236B /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DA945C8617E3F3FD0053236B /* Images.xcassets */; }; @@ -515,6 +518,7 @@ 93D39975CE5AEC99E3F086C7 /* MPPasswordCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordCell.h; sourceTree = ""; }; 93D3999693660C89A7465F4E /* MPCoachmarkViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPCoachmarkViewController.h; sourceTree = ""; }; 93D399A8E3181B442D347CD7 /* MPAlgorithmV2.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAlgorithmV2.m; sourceTree = ""; }; + 93D399B36CDB2004D7C51391 /* MPMessageViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPMessageViewController.m; sourceTree = ""; }; 93D399C2F3D48E57C4803BDC /* NSPersistentStore+PearlMigration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSPersistentStore+PearlMigration.m"; sourceTree = ""; }; 93D399E571F61E50A9BF8FAF /* MPUsersViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUsersViewController.m; sourceTree = ""; }; 93D399F244BB522A317811BB /* MPFixable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPFixable.h; sourceTree = ""; }; @@ -540,6 +544,7 @@ 93D39C426E03358384018E85 /* MPAnswersViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAnswersViewController.m; sourceTree = ""; }; 93D39C44361BE57AF0B3071F /* MPPasswordsSegue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordsSegue.h; sourceTree = ""; }; 93D39C86E984EC65DA5ACB1D /* MPAppSettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAppSettingsViewController.h; sourceTree = ""; }; + 93D39CB0EABD2748740992D8 /* MPMessageViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPMessageViewController.h; sourceTree = ""; }; 93D39CC01630D0421205C4C4 /* MPNavigationController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNavigationController.m; sourceTree = ""; }; 93D39CDD434AFD6E1B0DA359 /* MPEmergencyViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPEmergencyViewController.h; sourceTree = ""; }; 93D39CECA10BCCB0BA581BF1 /* MPAppDelegate_InApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAppDelegate_InApp.h; sourceTree = ""; }; @@ -669,6 +674,7 @@ DA70EC7F1811B13C00F65DB2 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; DA72BD7419C133BF00E6ACFE /* libscryptenc-ios-sim.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libscryptenc-ios-sim.a"; sourceTree = ""; }; DA72BD7719C137D500E6ACFE /* libopensslcrypto-ios-dev.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libopensslcrypto-ios-dev.a"; sourceTree = ""; }; + DA8495271A9146E600B3053D /* MasterPassword 7.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 7.xcdatamodel"; sourceTree = ""; }; DA854C8118D4CFBF00106317 /* avatar-add@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-add@2x.png"; sourceTree = ""; }; DA854C8218D4CFBF00106317 /* avatar-add.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-add.png"; sourceTree = ""; }; DA945C8617E3F3FD0053236B /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; @@ -1745,6 +1751,8 @@ 93D3924D6F77E6BF41AC32D3 /* MPRootSegue.h */, 93D39C41A27AA42D044D68AE /* NSString+MPMarkDown.m */, 93D393CB0B1F4EC8C17CFE43 /* NSString+MPMarkDown.h */, + 93D399B36CDB2004D7C51391 /* MPMessageViewController.m */, + 93D39CB0EABD2748740992D8 /* MPMessageViewController.h */, ); sourceTree = ""; }; @@ -2606,55 +2614,55 @@ DABD3BD71711E2DC00CF925C /* iOS */ = { isa = PBXGroup; children = ( - DA38D6A218CCB5BF009AEB3E /* Storyboard.storyboard */, - 93D39975CE5AEC99E3F086C7 /* MPPasswordCell.h */, - 93D39DEA995041A13DC9CAF7 /* MPPasswordCell.m */, - 93D39ACBA9F4878B6A1CC33B /* MPEmergencyViewController.m */, - 93D39CDD434AFD6E1B0DA359 /* MPEmergencyViewController.h */, - DABD3BD81711E2DC00CF925C /* MPiOSAppDelegate.h */, - DABD3BD91711E2DC00CF925C /* MPiOSAppDelegate.m */, - DABD3BE61711E2DC00CF925C /* MPGuideViewController.h */, - DABD3BE71711E2DC00CF925C /* MPGuideViewController.m */, - DABD3BEA1711E2DC00CF925C /* MPPreferencesViewController.h */, - DABD3BEB1711E2DC00CF925C /* MPPreferencesViewController.m */, - DABD3BEC1711E2DC00CF925C /* MPTypeViewController.h */, - DABD3BED1711E2DC00CF925C /* MPTypeViewController.m */, - DABD3BF01711E2DC00CF925C /* MPiOSConfig.h */, - DABD3BF11711E2DC00CF925C /* MPiOSConfig.m */, + DABD3BFA1711E2DC00CF925C /* InfoPlist.strings */, + DABD3BFC1711E2DC00CF925C /* main.m */, DABD3BF31711E2DC00CF925C /* MasterPassword-Info.plist */, DABD3BF41711E2DC00CF925C /* MasterPassword-Prefix.pch */, DABD3BF81711E2DC00CF925C /* MasterPassword.entitlements */, - DABD3BF91711E2DC00CF925C /* Settings.bundle */, - DABD3BFA1711E2DC00CF925C /* InfoPlist.strings */, - DABD3BFC1711E2DC00CF925C /* main.m */, - 93D39A28369954D147E239BA /* MPSetupViewController.m */, - 93D39730673227EFF6DEFF19 /* MPSetupViewController.h */, - 93D3979190DACEBD1F6AE9F4 /* MPLogsViewController.m */, - 93D391943675426839501BB8 /* MPLogsViewController.h */, - 93D3924EE15017F8A12CB436 /* MPPasswordsViewController.m */, - 93D3914D7597F9A28DB9D85E /* MPPasswordsViewController.h */, - 93D393310223DDB35218467A /* MPCombinedViewController.m */, - 93D39CF8ADF4542CDC4CD385 /* MPCombinedViewController.h */, - 93D39B381350802A194BF332 /* MPAvatarCell.m */, - 93D39DA27D768B53C8B1330C /* MPAvatarCell.h */, - 93D399E571F61E50A9BF8FAF /* MPUsersViewController.m */, - 93D3971FE104BB4052484151 /* MPUsersViewController.h */, - 93D39BAA71DE51B4D8A1286C /* MPCell.m */, - 93D390519405B76CC6A57C4F /* MPCell.h */, - 93D39E7A12CC352B2825AA66 /* MPPasswordsSegue.m */, - 93D39C44361BE57AF0B3071F /* MPPasswordsSegue.h */, - 93D39B050DD5F55E9794EFD4 /* MPPopdownSegue.m */, - 93D392876BE5C011DE73B43F /* MPPopdownSegue.h */, - 93D3916C1D8F1427DFBDEBCA /* MPAppSettingsViewController.m */, 93D39C86E984EC65DA5ACB1D /* MPAppSettingsViewController.h */, - 93D3995B1D4DCE5A30D882BA /* MPCoachmarkViewController.m */, + 93D3916C1D8F1427DFBDEBCA /* MPAppSettingsViewController.m */, + 93D39DA27D768B53C8B1330C /* MPAvatarCell.h */, + 93D39B381350802A194BF332 /* MPAvatarCell.m */, + 93D390519405B76CC6A57C4F /* MPCell.h */, + 93D39BAA71DE51B4D8A1286C /* MPCell.m */, 93D3999693660C89A7465F4E /* MPCoachmarkViewController.h */, - 93D3990E0CD1B5CF9FBB2C07 /* MPWebViewController.m */, - 93D39F556F2F142740A65E59 /* MPWebViewController.h */, - 93D39CC01630D0421205C4C4 /* MPNavigationController.m */, + 93D3995B1D4DCE5A30D882BA /* MPCoachmarkViewController.m */, + 93D39CF8ADF4542CDC4CD385 /* MPCombinedViewController.h */, + 93D393310223DDB35218467A /* MPCombinedViewController.m */, + 93D39CDD434AFD6E1B0DA359 /* MPEmergencyViewController.h */, + 93D39ACBA9F4878B6A1CC33B /* MPEmergencyViewController.m */, + DABD3BE61711E2DC00CF925C /* MPGuideViewController.h */, + DABD3BE71711E2DC00CF925C /* MPGuideViewController.m */, + DABD3BD81711E2DC00CF925C /* MPiOSAppDelegate.h */, + DABD3BD91711E2DC00CF925C /* MPiOSAppDelegate.m */, + DABD3BF01711E2DC00CF925C /* MPiOSConfig.h */, + DABD3BF11711E2DC00CF925C /* MPiOSConfig.m */, + 93D391943675426839501BB8 /* MPLogsViewController.h */, + 93D3979190DACEBD1F6AE9F4 /* MPLogsViewController.m */, 93D3970502644794E8A027BE /* MPNavigationController.h */, - 93D395105935859D71679931 /* MPOverlayViewController.m */, + 93D39CC01630D0421205C4C4 /* MPNavigationController.m */, 93D39B455A71EC98C749E623 /* MPOverlayViewController.h */, + 93D395105935859D71679931 /* MPOverlayViewController.m */, + 93D39975CE5AEC99E3F086C7 /* MPPasswordCell.h */, + 93D39DEA995041A13DC9CAF7 /* MPPasswordCell.m */, + 93D39C44361BE57AF0B3071F /* MPPasswordsSegue.h */, + 93D39E7A12CC352B2825AA66 /* MPPasswordsSegue.m */, + 93D3914D7597F9A28DB9D85E /* MPPasswordsViewController.h */, + 93D3924EE15017F8A12CB436 /* MPPasswordsViewController.m */, + 93D392876BE5C011DE73B43F /* MPPopdownSegue.h */, + 93D39B050DD5F55E9794EFD4 /* MPPopdownSegue.m */, + DABD3BEA1711E2DC00CF925C /* MPPreferencesViewController.h */, + DABD3BEB1711E2DC00CF925C /* MPPreferencesViewController.m */, + 93D39730673227EFF6DEFF19 /* MPSetupViewController.h */, + 93D39A28369954D147E239BA /* MPSetupViewController.m */, + DABD3BEC1711E2DC00CF925C /* MPTypeViewController.h */, + DABD3BED1711E2DC00CF925C /* MPTypeViewController.m */, + 93D3971FE104BB4052484151 /* MPUsersViewController.h */, + 93D399E571F61E50A9BF8FAF /* MPUsersViewController.m */, + 93D39F556F2F142740A65E59 /* MPWebViewController.h */, + 93D3990E0CD1B5CF9FBB2C07 /* MPWebViewController.m */, + DABD3BF91711E2DC00CF925C /* Settings.bundle */, + DA38D6A218CCB5BF009AEB3E /* Storyboard.storyboard */, ); path = iOS; sourceTree = ""; @@ -3503,6 +3511,7 @@ DABD3B961711E29800CF925C /* pull-down@2x.png in Resources */, DABD3B971711E29800CF925C /* pull-up.png in Resources */, DA24EBEA19DAD6EE00FF010B /* Icon-Small.png in Resources */, + DA8495321A93049300B3053D /* icon_down@2x.png in Resources */, DABD3B981711E29800CF925C /* pull-up@2x.png in Resources */, DA7304A0194E022B00E72520 /* ui_textfield@2x.png in Resources */, DAA1765119D8B82B0044227B /* copy_pw@2x.png in Resources */, @@ -3526,6 +3535,7 @@ DABD3FCF1714F45C00CF925C /* identity@2x.png in Resources */, DAA1764619D8B82B0044227B /* name_new.png in Resources */, DA45224B190628B2008F650A /* icon_gear.png in Resources */, + DA8495311A93049300B3053D /* icon_down.png in Resources */, DA25C5FF197DBF200046CDCF /* icon_thumbs-up@2x.png in Resources */, DAE1EF2217E942DE00BC0086 /* Localizable.strings in Resources */, DA38D6A318CCB5BF009AEB3E /* Storyboard.storyboard in Resources */, @@ -3656,6 +3666,7 @@ 93D39943D01E70DAC3B0DF76 /* mpw-util.c in Sources */, 93D39577FD8BB0945DB2F0A3 /* MPAlgorithmV3.m in Sources */, 93D39E5F7F6D7F5C0FAD090F /* MPTypes.m in Sources */, + 93D39508A6814612A5B3C226 /* MPMessageViewController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4423,6 +4434,7 @@ DA32D00119CF4735004F3F0E /* MasterPassword.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + DA8495271A9146E600B3053D /* MasterPassword 7.xcdatamodel */, DA32D00219CF4735004F3F0E /* MasterPassword 1.xcdatamodel */, DA32D00319CF4735004F3F0E /* MasterPassword 2.xcdatamodel */, DA32D00419CF4735004F3F0E /* MasterPassword 3.xcdatamodel */, @@ -4430,7 +4442,7 @@ DA32D00619CF4735004F3F0E /* MasterPassword 5.xcdatamodel */, DA32D00719CF4735004F3F0E /* MasterPassword 6.xcdatamodel */, ); - currentVersion = DA32D00719CF4735004F3F0E /* MasterPassword 6.xcdatamodel */; + currentVersion = DA8495271A9146E600B3053D /* MasterPassword 7.xcdatamodel */; path = MasterPassword.xcdatamodeld; sourceTree = ""; versionGroupType = wrapper.xcdatamodel; diff --git a/MasterPassword/ObjC/iOS/Settings.bundle/Root.plist b/MasterPassword/ObjC/iOS/Settings.bundle/Root.plist index c0e333e3..981c5279 100644 --- a/MasterPassword/ObjC/iOS/Settings.bundle/Root.plist +++ b/MasterPassword/ObjC/iOS/Settings.bundle/Root.plist @@ -147,6 +147,24 @@ To see a site's password anyway, tap and hold your finger down for a while Type PSToggleSwitchSpecifier + + FooterText + When site downgrades are enabled, a long tap on the upgrade button will downgrade the site instead. This is useful if you accidentally upgraded a site and need to downgrade it again temporarily to see your old password. + Title + + Type + PSGroupSpecifier + + + DefaultValue + + Key + allowDowngrade + Title + Allow Downgrade + Type + PSToggleSwitchSpecifier + Type PSGroupSpecifier diff --git a/MasterPassword/ObjC/iOS/Storyboard.storyboard b/MasterPassword/ObjC/iOS/Storyboard.storyboard index 120847c0..0eacb454 100644 --- a/MasterPassword/ObjC/iOS/Storyboard.storyboard +++ b/MasterPassword/ObjC/iOS/Storyboard.storyboard @@ -1,10 +1,11 @@ - + + @@ -31,6 +32,7 @@ Exo2.0-Bold Exo2.0-Bold Exo2.0-Bold + Exo2.0-Bold Exo2.0-ExtraBold @@ -182,10 +184,10 @@ - + -