From 4d4ba3425e5a37933e0468deb94d58c9cedd332f Mon Sep 17 00:00:00 2001 From: Maarten Billemont Date: Fri, 22 Jun 2012 16:52:33 +0200 Subject: [PATCH] Improvements to password import. [FIXED] Importing of mpsites with passwords showing for stored password types. [FIXED] Don't try to show mail composition dialog when the user has no mail account configured. This will crash. Instead, show a friendly popup explaining things. [IMPROVED] Message of password export emails. [FIXED] Hierarchy of MPUnlockVC so password field becomes touchable. --- MasterPassword/MPAppDelegate_Key.h | 2 - MasterPassword/MPAppDelegate_Key.m | 5 - MasterPassword/MPAppDelegate_Store.m | 25 +++-- MasterPassword/MPEntities.h | 3 +- MasterPassword/MPEntities.m | 37 +++++-- MasterPassword/MPTypes.h | 1 + MasterPassword/MPTypes.m | 7 ++ MasterPassword/iOS/MPAppDelegate.m | 43 +++++--- .../iOS/MainStoryboard_iPhone.storyboard | 99 +++++++++---------- 9 files changed, 136 insertions(+), 86 deletions(-) diff --git a/MasterPassword/MPAppDelegate_Key.h b/MasterPassword/MPAppDelegate_Key.h index a75f3ed5..d11a0d4b 100644 --- a/MasterPassword/MPAppDelegate_Key.h +++ b/MasterPassword/MPAppDelegate_Key.h @@ -16,6 +16,4 @@ - (void)storeSavedKeyFor:(MPUserEntity *)user; - (void)forgetSavedKeyFor:(MPUserEntity *)user; -- (NSData *)keyWithLength:(NSUInteger)keyLength; - @end diff --git a/MasterPassword/MPAppDelegate_Key.m b/MasterPassword/MPAppDelegate_Key.m index a6b075f7..abed621a 100644 --- a/MasterPassword/MPAppDelegate_Key.m +++ b/MasterPassword/MPAppDelegate_Key.m @@ -169,9 +169,4 @@ static NSDictionary *keyQuery(MPUserEntity *user) { return YES; } -- (NSData *)keyWithLength:(NSUInteger)keyLength { - - return [self.key subdataWithRange:NSMakeRange(0, MIN(keyLength, self.key.length))]; -} - @end diff --git a/MasterPassword/MPAppDelegate_Store.m b/MasterPassword/MPAppDelegate_Store.m index 0fa3ff91..150c2559 100644 --- a/MasterPassword/MPAppDelegate_Store.m +++ b/MasterPassword/MPAppDelegate_Store.m @@ -217,9 +217,10 @@ if (!headerPattern || !sitePattern) return MPImportResultInternalError; + NSData *key = nil; NSString *keyIDHex = nil, *userName = nil; MPUserEntity *user = nil; - BOOL headerStarted = NO, headerEnded = NO; + BOOL headerStarted = NO, headerEnded = NO, clearText = NO; NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; NSMutableSet *elementsToDelete = [NSMutableSet set]; NSMutableArray *importedSiteElements = [NSMutableArray arrayWithCapacity:[importedSiteLines count]]; @@ -246,19 +247,24 @@ } NSTextCheckingResult *headerElements = [[headerPattern matchesInString:importedSiteLine options:0 range:NSMakeRange(0, [importedSiteLine length])] lastObject]; - NSString *key = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]]; - NSString *value = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]]; - if ([key isEqualToString:@"User Name"]) { - userName = value; + NSString *headerName = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]]; + NSString *headerValue = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]]; + if ([headerName isEqualToString:@"User Name"]) { + userName = headerValue; + key = keyForPassword(password, userName); NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPUserEntity class])]; userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", userName]; user = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] lastObject]; } - if ([key isEqualToString:@"Key ID"]) { - if (![(keyIDHex = value) isEqualToString:[keyIDForPassword(password, userName) encodeHex]]) + if ([headerName isEqualToString:@"Key ID"]) { + if (![(keyIDHex = headerValue) isEqualToString:[keyIDForKey(key) encodeHex]]) return MPImportResultInvalidPassword; } + if ([headerName isEqualToString:@"Passwords"]) { + if ([headerValue isEqualToString:@"VISIBLE"]) + clearText = YES; + } continue; } @@ -337,7 +343,10 @@ element.uses = uses; element.lastUsed = lastUsed; if ([exportContent length]) - [element importContent:exportContent]; + if (clearText) + [element importClearTextContent:exportContent usingKey:key]; + else + [element importProtectedContent:exportContent]; } [self saveContext]; diff --git a/MasterPassword/MPEntities.h b/MasterPassword/MPEntities.h index f3a32dad..db5df3fa 100644 --- a/MasterPassword/MPEntities.h +++ b/MasterPassword/MPEntities.h @@ -21,7 +21,8 @@ - (NSUInteger)use; - (NSString *)exportContent; -- (void)importContent:(NSString *)content; +- (void)importProtectedContent:(NSString *)protectedContent; +- (void)importClearTextContent:(NSString *)clearContent usingKey:(NSData *)key; @end diff --git a/MasterPassword/MPEntities.m b/MasterPassword/MPEntities.m index cc964c69..6d62e0a1 100644 --- a/MasterPassword/MPEntities.m +++ b/MasterPassword/MPEntities.m @@ -50,10 +50,14 @@ return nil; } -- (void)importContent:(NSString *)content { +- (void)importProtectedContent:(NSString *)content { } +- (void)importClearTextContent:(NSString *)content usingKey:(NSData *)key { + +} + - (NSString *)description { return PearlString(@"%@:%@", [self class], [self name]); @@ -107,8 +111,19 @@ } - (id)content { + + return [self contentUsingKey:[MPAppDelegate get].key]; +} + +- (void)setContent:(id)content { + + [self setContent:content usingKey:[MPAppDelegate get].key]; +} + +- (id)contentUsingKey:(NSData *)key { assert(self.type & MPElementTypeClassStored); + assert([keyIDForKey(key) isEqualToData:self.user.keyID]); NSData *encryptedContent; if (self.type & MPElementFeatureDevicePrivate) @@ -116,15 +131,16 @@ else encryptedContent = self.contentObject; - NSData *decryptedContent = [encryptedContent decryptWithSymmetricKey:[[MPAppDelegate get] keyWithLength:PearlCryptKeySize] - padding:YES]; + NSData *decryptedContent = [encryptedContent decryptWithSymmetricKey:subkeyForKey(key, PearlCryptKeySize) padding:YES]; return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding]; } -- (void)setContent:(id)content { +- (void)setContent:(id)content usingKey:(NSData *)key { - NSData *encryptedContent = [[content description] encryptWithSymmetricKey:[[MPAppDelegate get] keyWithLength:PearlCryptKeySize] - padding:YES]; + assert(self.type & MPElementTypeClassStored); + assert([keyIDForKey(key) isEqualToData:self.user.keyID]); + + NSData *encryptedContent = [[content description] encryptWithSymmetricKey:subkeyForKey(key, PearlCryptKeySize) padding:YES]; if (self.type & MPElementFeatureDevicePrivate) { [PearlKeyChain addOrUpdateItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name] @@ -144,9 +160,14 @@ return [self.contentObject encodeBase64]; } -- (void)importContent:(NSString *)content { +- (void)importProtectedContent:(NSString *)protectedContent { - self.contentObject = [content decodeBase64]; + self.contentObject = [protectedContent decodeBase64]; +} + +- (void)importClearTextContent:(NSString *)clearContent usingKey:(NSData *)key { + + [self setContent:clearContent usingKey:key]; } @end diff --git a/MasterPassword/MPTypes.h b/MasterPassword/MPTypes.h index 6ebf79e5..70cd6728 100644 --- a/MasterPassword/MPTypes.h +++ b/MasterPassword/MPTypes.h @@ -78,6 +78,7 @@ typedef enum { #define MPNotificationElementUsed @"MPNotificationElementUsed" NSData *keyForPassword(NSString *password, NSString *username); +NSData *subkeyForKey(NSData *key, NSUInteger subkeyLength); NSData *keyIDForPassword(NSString *password, NSString *username); NSData *keyIDForKey(NSData *key); NSString *NSStringFromMPElementType(MPElementType type); diff --git a/MasterPassword/MPTypes.m b/MasterPassword/MPTypes.m index 263e7f51..f883ce11 100644 --- a/MasterPassword/MPTypes.m +++ b/MasterPassword/MPTypes.m @@ -30,6 +30,13 @@ NSData *keyForPassword(NSString *password, NSString *username) { return key; } + +NSData *subkeyForKey(NSData *key, NSUInteger subkeyLength) { + + return [key subdataWithRange:NSMakeRange(0, MIN(subkeyLength, key.length))]; +} + + NSData *keyIDForPassword(NSString *password, NSString *username) { return keyIDForKey(keyForPassword(password, username)); diff --git a/MasterPassword/iOS/MPAppDelegate.m b/MasterPassword/iOS/MPAppDelegate.m index ba9baf4b..9cf92186 100644 --- a/MasterPassword/iOS/MPAppDelegate.m +++ b/MasterPassword/iOS/MPAppDelegate.m @@ -202,8 +202,6 @@ cancelTitle:nil otherTitles:[PearlStrings get].commonButtonOkay, nil]; #endif - [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide]; - [super application:application didFinishLaunchingWithOptions:launchOptions]; inf(@"Started up with device identifier: %@", [PearlKeyChain deviceIdentifier]); @@ -419,26 +417,47 @@ // Safe Export [self exportShowPasswords:NO]; if (buttonIndex_ == [alert_ firstOtherButtonIndex] + 1) - // Safe Export + // Show Passwords [self exportShowPasswords:YES]; } cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Safe Export", @"Show Passwords", nil]; } otherTitles:nil]; } - (void)exportShowPasswords:(BOOL)showPasswords { + + if (![MFMailComposeViewController canSendMail]) { + [PearlAlert showAlertWithTitle:@"Cannot Send Mail" + message: + @"Your device is not yet set up for sending mail.\n" + @"Close Master Password, go into Settings and add a Mail account." + viewStyle:UIAlertViewStyleDefault + initAlert:nil tappedButtonBlock:nil + cancelTitle:[PearlStrings get].commonButtonOkay + otherTitles:nil]; + return; + } NSString *exportedSites = [self exportSitesShowingPasswords:showPasswords]; NSString *message; - if (showPasswords) - message = PearlString( - @"Export of %@'s Master Password sites with passwords visible.\n" - @"REMINDER: Make sure nobody else sees this file! All passwords are visible!\n", - self.activeUser.name); - else - message = PearlString( - @"Backup of %@'s Master Password sites.\n", - self.activeUser.name); + if (showPasswords) + message = PearlString(@"Export of Master Password sites with passwords included.\n" + @"REMINDER: Make sure nobody else sees this file! Passwords are visible!\n\n\n" + @"--\n" + @"%@\n" + @"Master Password %@, build %@", + self.activeUser.name, + [PearlInfoPlist get].CFBundleShortVersionString, + [PearlInfoPlist get].CFBundleVersion); + else + message = PearlString(@"Backup of Master Password sites.\n\n\n" + @"--\n" + @"%@\n" + @"Master Password %@, build %@", + self.activeUser.name, + [PearlInfoPlist get].CFBundleShortVersionString, + [PearlInfoPlist get].CFBundleVersion); + NSDateFormatter *exportDateFormatter = [NSDateFormatter new]; [exportDateFormatter setDateFormat:@"yyyy'-'MM'-'DD"]; diff --git a/MasterPassword/iOS/MainStoryboard_iPhone.storyboard b/MasterPassword/iOS/MainStoryboard_iPhone.storyboard index 1ec72c70..537efce4 100644 --- a/MasterPassword/iOS/MainStoryboard_iPhone.storyboard +++ b/MasterPassword/iOS/MainStoryboard_iPhone.storyboard @@ -1,7 +1,6 @@ - @@ -828,54 +827,6 @@ L4m3P4sSw0rD - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -904,6 +855,54 @@ L4m3P4sSw0rD + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +