diff --git a/External/Pearl b/External/Pearl index 3cca78c6..c4cd94ab 160000 --- a/External/Pearl +++ b/External/Pearl @@ -1 +1 @@ -Subproject commit 3cca78c6ebc874ef3168e674dbb7391f9a6dec52 +Subproject commit c4cd94ab7743a06a0173e3a93e6ea10ab7c7d7f5 diff --git a/OnePassword.sketch/Data b/OnePassword.sketch/Data new file mode 100644 index 00000000..d7da2f6a Binary files /dev/null and b/OnePassword.sketch/Data differ diff --git a/OnePassword.sketch/QuickLook/Preview.pdf b/OnePassword.sketch/QuickLook/Preview.pdf new file mode 100644 index 00000000..f57c2ed9 Binary files /dev/null and b/OnePassword.sketch/QuickLook/Preview.pdf differ diff --git a/OnePassword.sketch/QuickLook/Thumbnail.jpg b/OnePassword.sketch/QuickLook/Thumbnail.jpg new file mode 100644 index 00000000..99f26d5c Binary files /dev/null and b/OnePassword.sketch/QuickLook/Thumbnail.jpg differ diff --git a/OnePassword.xcodeproj/project.pbxproj b/OnePassword.xcodeproj/project.pbxproj index 9b6498a6..e434602a 100644 --- a/OnePassword.xcodeproj/project.pbxproj +++ b/OnePassword.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ DA007F5514B25EE100251337 /* ciphers.plist in Resources */ = {isa = PBXBuildFile; fileRef = DA007F5414B25EE100251337 /* ciphers.plist */; }; DA007F5614B26EFA00251337 /* Pearl.strings in Resources */ = {isa = PBXBuildFile; fileRef = DAC77CD41482AAD600BCF976 /* Pearl.strings */; }; DA04E33E14B1E70400ECA4F3 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA04E33D14B1E70400ECA4F3 /* MobileCoreServices.framework */; }; + DA0A848C14C4DFCB0090EA8E /* OPElementGeneratedEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0A848B14C4DFCB0090EA8E /* OPElementGeneratedEntity.m */; }; DA0B951114C2D69E001D4EB1 /* help.html in Resources */ = {isa = PBXBuildFile; fileRef = DA0B951014C2D69E001D4EB1 /* help.html */; }; DA0B966914C37487001D4EB1 /* addressbook-person@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0B951314C37486001D4EB1 /* addressbook-person@2x.png */; }; DA0B966A14C37487001D4EB1 /* addressbook.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0B951414C37486001D4EB1 /* addressbook.png */; }; @@ -372,7 +373,8 @@ DA5BFA67147E415C00F98B1E /* OPMainViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5BFA66147E415C00F98B1E /* OPMainViewController.m */; }; DA5DB7A614BE4B19002DD256 /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5DB7A514BE4B19002DD256 /* Default.png */; }; DA5DB7A814BE4B4B002DD256 /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5DB7A714BE4B4B002DD256 /* Default@2x.png */; }; - DABF7DA514BE54E4007F3557 /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = DABF7DA414BE54E4007F3557 /* Default.png */; }; + DA7659AF14C5E22E00769249 /* Square-bottom.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7659AD14C5E22E00769249 /* Square-bottom.png */; }; + DA7659B014C5E22E00769249 /* Square-top.png in Resources */ = {isa = PBXBuildFile; fileRef = DA7659AE14C5E22E00769249 /* Square-top.png */; }; DAC6325E1486805C0075AEA5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; }; DAC6326D148680650075AEA5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; }; DAC63277148680700075AEA5 /* libuicolor-utilities.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC6325D1486805C0075AEA5 /* libuicolor-utilities.a */; }; @@ -452,6 +454,7 @@ DAC781321482AAD800BCF976 /* WebViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = DAC780EF1482AAD700BCF976 /* WebViewController.h */; }; DAC781331482AAD800BCF976 /* WebViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAC780F01482AAD700BCF976 /* WebViewController.m */; }; DAC781361482E67300BCF976 /* OPRecentViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAC781351482E67300BCF976 /* OPRecentViewController.m */; }; + DADC3C4D14C62B350091CB4D /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = DADC3C4C14C62B350091CB4D /* Settings.bundle */; }; DAE2C648148247E500BA6B10 /* OPTypeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAE2C646148247E500BA6B10 /* OPTypeViewController.m */; }; DAE998D214C1D2A0002D7C22 /* Content-Backdrop.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE9987914C1D2A0002D7C22 /* Content-Backdrop.png */; }; DAE998D314C1D2A0002D7C22 /* Content-Backdrop@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE9987A14C1D2A0002D7C22 /* Content-Backdrop@2x.png */; }; @@ -616,6 +619,8 @@ DA007F5114B24DCD00251337 /* OPConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OPConfig.m; sourceTree = ""; }; DA007F5414B25EE100251337 /* ciphers.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = ciphers.plist; sourceTree = ""; }; DA04E33D14B1E70400ECA4F3 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; + DA0A848A14C4DFCB0090EA8E /* OPElementGeneratedEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OPElementGeneratedEntity.h; sourceTree = ""; }; + DA0A848B14C4DFCB0090EA8E /* OPElementGeneratedEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OPElementGeneratedEntity.m; sourceTree = ""; }; DA0B951014C2D69E001D4EB1 /* help.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = help.html; path = Resources/help.html; sourceTree = ""; }; DA0B951314C37486001D4EB1 /* addressbook-person@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "addressbook-person@2x.png"; sourceTree = ""; }; DA0B951414C37486001D4EB1 /* addressbook.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = addressbook.png; sourceTree = ""; }; @@ -987,7 +992,8 @@ DA5BFA66147E415C00F98B1E /* OPMainViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OPMainViewController.m; sourceTree = ""; }; DA5DB7A514BE4B19002DD256 /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Default.png; path = ../Default.png; sourceTree = ""; }; DA5DB7A714BE4B4B002DD256 /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default@2x.png"; path = "../Default@2x.png"; sourceTree = ""; }; - DABF7DA414BE54E4007F3557 /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Default.png; sourceTree = SOURCE_ROOT; }; + DA7659AD14C5E22E00769249 /* Square-bottom.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Square-bottom.png"; sourceTree = ""; }; + DA7659AE14C5E22E00769249 /* Square-top.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Square-top.png"; sourceTree = ""; }; DAC6325D1486805C0075AEA5 /* libuicolor-utilities.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libuicolor-utilities.a"; sourceTree = BUILT_PRODUCTS_DIR; }; DAC6326C148680650075AEA5 /* libjrswizzle.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libjrswizzle.a; sourceTree = BUILT_PRODUCTS_DIR; }; DAC632791486809A0075AEA5 /* JRSwizzle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = JRSwizzle.h; path = External/Pearl/External/jrswizzle/JRSwizzle.h; sourceTree = SOURCE_ROOT; }; @@ -2070,6 +2076,7 @@ DAC780F01482AAD700BCF976 /* WebViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WebViewController.m; sourceTree = ""; }; DAC781341482E67300BCF976 /* OPRecentViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OPRecentViewController.h; sourceTree = ""; }; DAC781351482E67300BCF976 /* OPRecentViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OPRecentViewController.m; sourceTree = ""; }; + DADC3C4C14C62B350091CB4D /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = ""; }; DAE2C645148247E500BA6B10 /* OPTypeViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OPTypeViewController.h; sourceTree = ""; }; DAE2C646148247E500BA6B10 /* OPTypeViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OPTypeViewController.m; sourceTree = ""; }; DAE9987914C1D2A0002D7C22 /* Content-Backdrop.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Content-Backdrop.png"; path = "Resources/Content-Backdrop.png"; sourceTree = ""; }; @@ -2658,6 +2665,7 @@ DA34DA1514B1BEA100F721C1 /* OPTypes.m */, DA007F5014B24DCC00251337 /* OPConfig.h */, DA007F5114B24DCD00251337 /* OPConfig.m */, + DADC3C4C14C62B350091CB4D /* Settings.bundle */, ); path = OnePassword; sourceTree = ""; @@ -2704,6 +2712,8 @@ children = ( DA34DA0F14B1BC7E00F721C1 /* OPElementEntity.h */, DA34DA1014B1BC7E00F721C1 /* OPElementEntity.m */, + DA0A848A14C4DFCB0090EA8E /* OPElementGeneratedEntity.h */, + DA0A848B14C4DFCB0090EA8E /* OPElementGeneratedEntity.m */, DA34DA0B14B1BC7D00F721C1 /* OPElementStoredEntity.h */, DA34DA0C14B1BC7D00F721C1 /* OPElementStoredEntity.m */, ); @@ -2789,7 +2799,6 @@ DAC77CD31482AAD600BCF976 /* Resources */ = { isa = PBXGroup; children = ( - DABF7DA414BE54E4007F3557 /* Default.png */, DAC77CD41482AAD600BCF976 /* Pearl.strings */, ); path = Resources; @@ -3878,6 +3887,8 @@ DAE9987B14C1D2A0002D7C22 /* Dividers */ = { isa = PBXGroup; children = ( + DA7659AD14C5E22E00769249 /* Square-bottom.png */, + DA7659AE14C5E22E00769249 /* Square-top.png */, DAE9987C14C1D2A0002D7C22 /* Bold_Lines.png */, DAE9987D14C1D2A0002D7C22 /* Box.png */, DAE9987E14C1D2A0002D7C22 /* Dashed_Divider.png */, @@ -4192,7 +4203,6 @@ DA007F5614B26EFA00251337 /* Pearl.strings in Resources */, DA5DB7A614BE4B19002DD256 /* Default.png in Resources */, DA5DB7A814BE4B4B002DD256 /* Default@2x.png in Resources */, - DABF7DA514BE54E4007F3557 /* Default.png in Resources */, DAE998D214C1D2A0002D7C22 /* Content-Backdrop.png in Resources */, DAE998D314C1D2A0002D7C22 /* Content-Backdrop@2x.png in Resources */, DAE998D414C1D2A0002D7C22 /* Bold_Lines.png in Resources */, @@ -4668,6 +4678,9 @@ DA0B97BC14C37487001D4EB1 /* volume-mute@2x.png in Resources */, DA0B97BD14C37487001D4EB1 /* wrench.png in Resources */, DA0B97BE14C37487001D4EB1 /* wrench@2x.png in Resources */, + DA7659AF14C5E22E00769249 /* Square-bottom.png in Resources */, + DA7659B014C5E22E00769249 /* Square-top.png in Resources */, + DADC3C4D14C62B350091CB4D /* Settings.bundle in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4690,6 +4703,7 @@ DA007F5214B24DCD00251337 /* OPConfig.m in Sources */, DA55B29E14B38272001131B7 /* OPContentViewController.m in Sources */, DA55B2A214B4EB47001131B7 /* OPSearchDelegate.m in Sources */, + DA0A848C14C4DFCB0090EA8E /* OPElementGeneratedEntity.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/OnePassword/OPAppDelegate.h b/OnePassword/OPAppDelegate.h index 54819b7b..97224499 100644 --- a/OnePassword/OPAppDelegate.h +++ b/OnePassword/OPAppDelegate.h @@ -16,6 +16,7 @@ @property (strong, nonatomic) NSString *keyPhrase; + (OPAppDelegate *)get; ++ (NSManagedObjectContext *)managedObjectContext; - (void)saveContext; - (NSURL *)applicationDocumentsDirectory; diff --git a/OnePassword/OPAppDelegate.m b/OnePassword/OPAppDelegate.m index 00fd8e0c..48a6cad6 100644 --- a/OnePassword/OPAppDelegate.m +++ b/OnePassword/OPAppDelegate.m @@ -10,6 +10,13 @@ #import "OPMainViewController.h" +@interface OPAppDelegate () + ++ (NSDictionary *)keyPhraseQuery; ++ (NSDictionary *)keyPhraseHashQuery; + +@end + @implementation OPAppDelegate @synthesize managedObjectContext = __managedObjectContext; @@ -22,26 +29,63 @@ [Logger get].autoprintLevel = LogLevelDebug; } -- (void)applicationWillResignActive:(UIApplication *)application { ++ (NSDictionary *)keyPhraseQuery { - if (![[OPConfig get].rememberKeyPhrase boolValue]) - self.keyPhrase = nil; + static NSDictionary *OPKeyPhraseQuery = nil; + if (!OPKeyPhraseQuery) + OPKeyPhraseQuery = [KeyChain createQueryForClass:kSecClassGenericPassword + attributes:[NSDictionary dictionaryWithObject:@"MasterKeyPhrase" + forKey:(__bridge id)kSecAttrService] + matches:nil]; + + return OPKeyPhraseQuery; +} + ++ (NSDictionary *)keyPhraseHashQuery { + + static NSDictionary *OPKeyPhraseHashQuery = nil; + if (!OPKeyPhraseHashQuery) + OPKeyPhraseHashQuery = [KeyChain createQueryForClass:kSecClassGenericPassword + attributes:[NSDictionary dictionaryWithObject:@"MasterKeyPhraseHash" + forKey:(__bridge id)kSecAttrService] + matches:nil]; + + return OPKeyPhraseHashQuery; } - (void)applicationDidBecomeActive:(UIApplication *)application { - if (!self.keyPhrase) + if ([[OPConfig get].storeKeyPhrase boolValue]) { + // Key phrase is stored in keychain. Load it. + dbg(@"Loading master key phrase from key chain."); + NSData *keyPhraseData = [KeyChain dataOfItemForQuery:[OPAppDelegate keyPhraseQuery]]; + dbg(@" -> Master key phrase %@.", keyPhraseData? @"found": @"NOT found"); + + self.keyPhrase = keyPhraseData? [[NSString alloc] initWithBytes:keyPhraseData.bytes length:keyPhraseData.length + encoding:NSUTF8StringEncoding]: nil; + } else { + // Key phrase should not be stored in keychain. Delete it. + dbg(@"Deleting master key phrase from key chain."); + [KeyChain deleteItemForQuery:[OPAppDelegate keyPhraseQuery]]; + } + + if (!self.keyPhrase) { + // Key phrase is not known. Ask user to set/specify it. + dbg(@"Key phrase not known. Will ask user."); + dispatch_async(dispatch_get_main_queue(), ^{ - NSString *keyPhraseHash = [OPConfig get].keyPhraseHash; + NSData *keyPhraseHash = [KeyChain dataOfItemForQuery:[OPAppDelegate keyPhraseHashQuery]]; + dbg(@"Key phrase hash %@.", keyPhraseHash? @"known": @"NOT known"); AlertViewController *keyPhraseAlert = [[AlertViewController alloc] initQuestionWithTitle:@"One Password" message:keyPhraseHash? @"Unlock with your master password:": @"Choose your master password:" tappedButtonBlock: ^(NSInteger buttonIndex, NSString *answer) { - if (buttonIndex == 0) + if (!buttonIndex) exit(0); if (![answer length]) { + // User didn't enter a key phrase. [AlertViewController showAlertWithTitle:[PearlStrings get].commonTitleError message:@"No master password entered." tappedButtonBlock: @@ -50,9 +94,13 @@ } cancelTitle:@"Quit" otherTitles:nil]; } - NSString *answerHash = [[answer hashWith:PearlDigestSHA1] encodeHex]; - if (keyPhraseHash) { - if (![keyPhraseHash isEqualToString:answerHash]) { + NSData *answerHash = [answer hashWith:PearlDigestSHA512]; + if (keyPhraseHash) + // A key phrase hash is known -> a key phrase is set. + // Make sure the user's entered key phrase matches it. + if (![keyPhraseHash isEqual:answerHash]) { + dbg(@"Key phrase hash mismatch. Expected: %@, answer: %@.", keyPhraseHash, answerHash); + [AlertViewController showAlertWithTitle:[PearlStrings get].commonTitleError message:@"Incorrect master password." tappedButtonBlock: @@ -62,16 +110,23 @@ return; } - } else - [OPConfig get].keyPhraseHash = answerHash; self.keyPhrase = answer; } cancelTitle:@"Quit" otherTitles:@"Unlock", nil]; keyPhraseAlert.alertField.autocapitalizationType = UITextAutocapitalizationTypeNone; keyPhraseAlert.alertField.autocorrectionType = UITextAutocorrectionTypeNo; + keyPhraseAlert.alertField.enablesReturnKeyAutomatically = YES; + keyPhraseAlert.alertField.returnKeyType = UIReturnKeyGo; keyPhraseAlert.alertField.secureTextEntry = YES; [keyPhraseAlert showAlert]; }); + } +} + +- (void)applicationWillResignActive:(UIApplication *)application { + + if (![[OPConfig get].rememberKeyPhrase boolValue]) + self.keyPhrase = nil; } - (void)applicationWillTerminate:(UIApplication *)application @@ -85,6 +140,11 @@ return (OPAppDelegate *)[super get]; } ++ (NSManagedObjectContext *)managedObjectContext{ + + return [(OPAppDelegate *)[UIApplication sharedApplication].delegate managedObjectContext]; +} + - (void)saveContext { NSError *error = nil; @@ -99,6 +159,25 @@ } } +- (void)setKeyPhrase:(NSString *)keyPhrase { + + _keyPhrase = keyPhrase; + + if (keyPhrase) { + NSData *keyPhraseHash = [keyPhrase hashWith:PearlDigestSHA512]; + dbg(@"Updating master key phrase hash to: %@.", keyPhraseHash); + [KeyChain addOrUpdateItemForQuery:[OPAppDelegate keyPhraseHashQuery] + withAttributes:[NSDictionary dictionaryWithObject:keyPhraseHash + forKey:(__bridge id)kSecValueData]]; + if ([[OPConfig get].storeKeyPhrase boolValue]) { + dbg(@"Storing master key phrase in key chain."); + [KeyChain addOrUpdateItemForQuery:[OPAppDelegate keyPhraseQuery] + withAttributes:[NSDictionary dictionaryWithObject:[keyPhrase dataUsingEncoding:NSUTF8StringEncoding] + forKey:(__bridge id)kSecValueData]]; + } + } +} + #pragma mark - Core Data stack /** diff --git a/OnePassword/OPConfig.h b/OnePassword/OPConfig.h index a36bda3e..4efc68c2 100644 --- a/OnePassword/OPConfig.h +++ b/OnePassword/OPConfig.h @@ -9,8 +9,9 @@ @interface OPConfig : Config @property (nonatomic, retain) NSNumber *dataStoreError; -@property (nonatomic, retain) NSString *keyPhraseHash; +@property (nonatomic, retain) NSNumber *storeKeyPhrase; @property (nonatomic, retain) NSNumber *rememberKeyPhrase; +@property (nonatomic, retain) NSNumber *helpHidden; + (OPConfig *)get; diff --git a/OnePassword/OPConfig.m b/OnePassword/OPConfig.m index fe1b47dd..c3710f85 100644 --- a/OnePassword/OPConfig.m +++ b/OnePassword/OPConfig.m @@ -10,18 +10,19 @@ @implementation OPConfig -@dynamic dataStoreError, keyPhraseHash, rememberKeyPhrase; +@dynamic dataStoreError, storeKeyPhrase, rememberKeyPhrase, helpHidden; --(id) init { +- (id)init { if(!(self = [super init])) return self; [self.defaults registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool:NO], NSStringFromSelector(@selector(dataStoreError)), + [NSNumber numberWithBool:NO], NSStringFromSelector(@selector(storeKeyPhrase)), [NSNumber numberWithBool:NO], NSStringFromSelector(@selector(rememberKeyPhrase)), - + [NSNumber numberWithBool:NO], NSStringFromSelector(@selector(helpHidden)), nil]]; return self; diff --git a/OnePassword/OPElementEntity.m b/OnePassword/OPElementEntity.m index 52355361..64e35364 100644 --- a/OnePassword/OPElementEntity.m +++ b/OnePassword/OPElementEntity.m @@ -7,7 +7,6 @@ // #import "OPElementEntity.h" -#import "OPAppDelegate.h" @implementation OPElementEntity @@ -27,14 +26,7 @@ - (id)content { - if (![self.name length]) - return nil; - - if (self.type & OPElementTypeCalculated) - return OPCalculateContent(self.type, self.name, [OPAppDelegate get].keyPhrase); - - @throw [NSException exceptionWithName:NSInternalInconsistencyException - reason:[NSString stringWithFormat:@"Unsupported type: %d", self.type] userInfo:nil]; + @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Content implementation missing." userInfo:nil]; } - (NSString *)contentDescription { diff --git a/OnePassword/OPElementGeneratedEntity.h b/OnePassword/OPElementGeneratedEntity.h new file mode 100644 index 00000000..d4652ffa --- /dev/null +++ b/OnePassword/OPElementGeneratedEntity.h @@ -0,0 +1,18 @@ +// +// OPElementGeneratedEntity.h +// OnePassword +// +// Created by Maarten Billemont on 16/01/12. +// Copyright (c) 2012 Lyndir. All rights reserved. +// + +#import +#import +#import "OPElementEntity.h" + + +@interface OPElementGeneratedEntity : OPElementEntity + +@property (nonatomic) int16_t counter; + +@end diff --git a/OnePassword/OPElementGeneratedEntity.m b/OnePassword/OPElementGeneratedEntity.m new file mode 100644 index 00000000..4380d50d --- /dev/null +++ b/OnePassword/OPElementGeneratedEntity.m @@ -0,0 +1,29 @@ +// +// OPElementGeneratedEntity.m +// OnePassword +// +// Created by Maarten Billemont on 16/01/12. +// Copyright (c) 2012 Lyndir. All rights reserved. +// + +#import "OPElementGeneratedEntity.h" +#import "OPAppDelegate.h" + + +@implementation OPElementGeneratedEntity + +@dynamic counter; + +- (id)content { + + if (![self.name length]) + return nil; + + if (self.type & OPElementTypeCalculated) + return OPCalculateContent(self.type, self.name, [OPAppDelegate get].keyPhrase, self.counter); + + @throw [NSException exceptionWithName:NSInternalInconsistencyException + reason:[NSString stringWithFormat:@"Unsupported type: %d", self.type] userInfo:nil]; +} + +@end diff --git a/OnePassword/OPMainViewController.h b/OnePassword/OPMainViewController.h index e5c94d4f..02acca25 100644 --- a/OnePassword/OPMainViewController.h +++ b/OnePassword/OPMainViewController.h @@ -20,9 +20,14 @@ @property (weak, nonatomic) IBOutlet UILabel *siteName; @property (weak, nonatomic) IBOutlet UILabel *passwordCounter; @property (weak, nonatomic) IBOutlet UIButton *passwordIncrementer; +@property (weak, nonatomic) IBOutlet UIButton *passwordEdit; +@property (weak, nonatomic) IBOutlet UIView *contentContainer; +@property (weak, nonatomic) IBOutlet UIView *helpContainer; -- (IBAction)didChangeContentType:(UISegmentedControl *)sender; -- (IBAction)didTriggerContent; -- (IBAction)didIncrementPasswordCounter; +- (IBAction)copyContent; +- (IBAction)incrementPasswordCounter; +- (IBAction)editPassword; +- (IBAction)toggleHelp; +- (void)toggleHelp:(BOOL)hidden; @end diff --git a/OnePassword/OPMainViewController.m b/OnePassword/OPMainViewController.m index fb4c7c2f..01371b6d 100644 --- a/OnePassword/OPMainViewController.m +++ b/OnePassword/OPMainViewController.m @@ -9,6 +9,8 @@ #import "OPMainViewController.h" #import "OPAppDelegate.h" #import "OPContentViewController.h" +#import "OPElementGeneratedEntity.h" +#import "OPElementStoredEntity.h" #import @@ -28,6 +30,9 @@ @synthesize siteName = _siteName; @synthesize passwordCounter = _passwordCounter; @synthesize passwordIncrementer = _passwordIncrementer; +@synthesize passwordEdit = _passwordEdit; +@synthesize contentContainer = _contentContainer; +@synthesize helpContainer = _helpContainer; @synthesize contentField = _contentField; #pragma mark - View lifecycle @@ -49,6 +54,7 @@ [super viewWillAppear:animated]; + [self toggleHelp:[[OPConfig get].helpHidden boolValue]]; [self updateAnimated:NO]; } @@ -56,7 +62,7 @@ // Because IB's edit button doesn't auto-toggle self.editable like editButtonItem does. self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"background"]]; - + [super viewDidLoad]; } @@ -69,6 +75,9 @@ [self setSiteName:nil]; [self setPasswordCounter:nil]; [self setPasswordIncrementer:nil]; + [self setPasswordEdit:nil]; + [self setContentContainer:nil]; + [self setHelpContainer:nil]; [super viewDidUnload]; } @@ -80,7 +89,7 @@ } - (void)updateAnimated:(BOOL)animated { - + [[OPAppDelegate get] saveContext]; if (animated) @@ -92,28 +101,27 @@ } - (void)updateWasAnimated:(BOOL)animated { - + NSUInteger chapter = self.activeElement? 2: 1; [self.helpView loadRequest: - [NSURLRequest requestWithURL: - [NSURL URLWithString:[NSString stringWithFormat:@"#%d", chapter] relativeToURL: - [[NSBundle mainBundle] URLForResource:@"help" withExtension:@"html"]]]]; - + [NSURLRequest requestWithURL: + [NSURL URLWithString:[NSString stringWithFormat:@"#%d", chapter] relativeToURL: + [[NSBundle mainBundle] URLForResource:@"help" withExtension:@"html"]]]]; + [self.navigationItem setRightBarButtonItem:self.activeElement.type & OPElementTypeStored? self.editButtonItem: nil animated:animated]; - - self.searchDisplayController.searchBar.placeholder = self.activeElement.name; + self.siteName.text = self.activeElement.name; - self.passwordCounter.alpha = self.activeElement.type & OPElementTypeCalculated? 1: 0; - self.passwordIncrementer.alpha = self.activeElement.type & OPElementTypeCalculated? 1: 0; - + self.passwordCounter.alpha = self.activeElement.type & OPElementTypeCalculated? 0.5f: 0; + self.passwordIncrementer.alpha = self.activeElement.type & OPElementTypeCalculated? 0.5f: 0; + self.passwordEdit.alpha = self.activeElement.type & OPElementTypeStored? 0.5f: 0; + [self.typeButton setTitle:NSStringFromOPElementType(self.activeElement.type) forState:UIControlStateNormal]; - - self.contentField.enabled = self.editing && self.activeElement.type & OPElementTypeStored; - self.contentField.clearButtonMode = self.contentField.enabled? UITextFieldViewModeAlways: UITextFieldViewModeNever; - self.contentField.text = @"..."; - + self.typeButton.alpha = NSStringFromOPElementType(self.activeElement.type).length? 1: 0; + + self.contentField.enabled = NO; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ NSString *contentDescription = self.activeElement.contentDescription; dispatch_async(dispatch_get_main_queue(), ^{ @@ -124,34 +132,103 @@ #pragma mark - Protocols -- (IBAction)didChangeContentType:(UISegmentedControl *)sender { - - [self updateAnimated:YES]; -} - -- (IBAction)didTriggerContent:(id)sender { +- (IBAction)copyContent { [[UIPasteboard generalPasteboard] setValue:self.activeElement.content forPasteboardType:self.activeElement.contentUTI]; } -- (IBAction)didIncrementPasswordCounter { +- (IBAction)incrementPasswordCounter { + + if ([self.activeElement isKindOfClass:[OPElementGeneratedEntity class]]) + [AlertViewController showAlertWithTitle:@"Change Password" + message:l(@"Setting a new password for %@.\n" + @"Don't forget to update your password on the site as well!", self.activeElement.name) + tappedButtonBlock:^(NSInteger buttonIndex) { + if (!buttonIndex) + return; + + // Update password counter. + if ([self.activeElement isKindOfClass:[OPElementGeneratedEntity class]]) { + ++((OPElementGeneratedEntity *) self.activeElement).counter; + [self updateAnimated:YES]; + } + } cancelTitle:[PearlStrings get].commonButtonAbort otherTitles:[PearlStrings get].commonButtonThanks, nil]; +} + +- (IBAction)editPassword { + + if (self.activeElement.type & OPElementTypeStored) { + self.contentField.enabled = YES; + [self.contentField becomeFirstResponder]; + } +} + +- (IBAction)toggleHelp { + + [UIView animateWithDuration:0.3f animations:^{ + if (self.helpContainer.frame.origin.y < 400) + [self toggleHelp:YES]; + else + [self toggleHelp:NO]; + }]; +} + +- (void)toggleHelp:(BOOL)hidden { + + if (hidden) { + self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, 373); + //self.helpContainer.frame = CGRectSetHeight(self.helpContainer.frame, 0); + self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, 414); + [OPConfig get].helpHidden = [NSNumber numberWithBool:YES]; + } else { + self.contentContainer.frame = CGRectSetHeight(self.contentContainer.frame, 155); + //self.helpContainer.frame = CGRectSetHeight(self.helpContainer.frame, 219); + self.helpContainer.frame = CGRectSetY(self.helpContainer.frame, 196); + [OPConfig get].helpHidden = [NSNumber numberWithBool:NO]; + } } - (void)didSelectType:(OPElementType)type { - self.activeElement.type = type; - [self updateAnimated:YES]; + [AlertViewController showAlertWithTitle:@"Change Password Type" + message:l(@"Changing the type of %@'s password.\n" + @"Don't forget to update your password on the site as well!", self.activeElement.name) + tappedButtonBlock:^(NSInteger buttonIndex) { + if (!buttonIndex) + return; + + // Update password type. + if (ClassForOPElementType(type) != ClassForOPElementType(self.activeElement.type)) { + // Type requires a different class of element. Recreate the element. + OPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass(ClassForOPElementType(type)) + inManagedObjectContext:[OPAppDelegate managedObjectContext]]; + + newElement.name = self.activeElement.name; + newElement.uses = self.activeElement.uses; + newElement.lastUsed = self.activeElement.lastUsed; + newElement.contentUTI = self.activeElement.contentUTI; + newElement.contentType = self.activeElement.contentType; + + [[OPAppDelegate managedObjectContext] deleteObject:self.activeElement]; + self.activeElement = newElement; + } + self.activeElement.type = type; + + // Redraw. + [self updateAnimated:YES]; + } cancelTitle:[PearlStrings get].commonButtonAbort otherTitles:[PearlStrings get].commonButtonThanks, nil]; } - (void)didSelectElement:(OPElementEntity *)element { self.activeElement = element; [self.activeElement use]; - [self updateAnimated:YES]; - - self.searchDisplayController.searchBar.text = @""; + [self.searchDisplayController setActive:NO animated:YES]; + self.searchDisplayController.searchBar.text = self.activeElement.name; + + [self updateAnimated:YES]; } - (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar { @@ -159,4 +236,32 @@ [self updateAnimated:YES]; } +- (BOOL)textFieldShouldReturn:(UITextField *)textField { + + if (textField == self.contentField) + [self.contentField resignFirstResponder]; + + return YES; +} + +- (void)textFieldDidEndEditing:(UITextField *)textField { + + if (textField == self.contentField) { + self.contentField.enabled = NO; + [AlertViewController showAlertWithTitle:@"Change Password" + message:l(@"Setting a new password for %@.\n" + @"Don't forget to update your password on the site as well!", self.activeElement.name) + tappedButtonBlock:^(NSInteger buttonIndex) { + if (buttonIndex) { + // Update password content. + if ([self.activeElement isKindOfClass:[OPElementStoredEntity class]]) + ((OPElementStoredEntity *) self.activeElement).contentObject = self.contentField.text; + } + + // Redraw. + [self updateAnimated:YES]; + } cancelTitle:[PearlStrings get].commonButtonAbort otherTitles:[PearlStrings get].commonButtonThanks, nil]; + } +} + @end diff --git a/OnePassword/OPSearchDelegate.m b/OnePassword/OPSearchDelegate.m index 45374f55..46d4afbf 100644 --- a/OnePassword/OPSearchDelegate.m +++ b/OnePassword/OPSearchDelegate.m @@ -8,10 +8,10 @@ #import "OPSearchDelegate.h" #import "OPAppDelegate.h" +#import "OPElementGeneratedEntity.h" @interface OPSearchDelegate (Private) -- (NSManagedObjectContext *)managedObjectContext; - (void)update; @end @@ -21,41 +21,47 @@ @synthesize delegate; @synthesize searchDisplayController; -- (NSManagedObjectContext *)managedObjectContext { - - return [(OPAppDelegate *)[UIApplication sharedApplication].delegate managedObjectContext]; +- (void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller { + + self.searchDisplayController.searchBar.text = @""; + self.searchDisplayController.searchBar.prompt = @"Enter the site's domain name (eg. apple.com):"; + [self update]; } -- (void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller { +- (void)searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller { + + self.searchDisplayController.searchBar.prompt = nil; +} - [self.searchDisplayController.searchResultsTableView setEditing:self.searchDisplayController.searchContentsController.editing animated:NO]; - [self update]; +- (void)searchDisplayController:(UISearchDisplayController *)controller willShowSearchResultsTableView:(UITableView *)tableView { + + [tableView setEditing:self.searchDisplayController.searchContentsController.editing animated:NO]; } - (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString { - + [self update]; - + return YES; } - (void)update { - + NSString *text = self.searchDisplayController.searchBar.text; if (!text) text = @""; - + NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:NSStringFromClass([OPElementEntity class])]; [fetchRequest setSortDescriptors: - [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses" ascending:NO]]]; + [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses" ascending:NO]]]; [fetchRequest setPredicate: - [NSPredicate predicateWithFormat:@"name BEGINSWITH[cd] %@", text]]; - + [NSPredicate predicateWithFormat:@"name BEGINSWITH[cd] %@", text]]; + self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest - managedObjectContext:[self managedObjectContext] + managedObjectContext:[OPAppDelegate managedObjectContext] sectionNameKeyPath:nil cacheName:nil]; self.fetchedResultsController.delegate = self; - + NSError *error; if (![self.fetchedResultsController performFetch:&error]) err(@"Couldn't fetch elements: %@", error); @@ -151,7 +157,7 @@ cell.detailTextLabel.text = @"New"; } else { OPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath]; - + cell.textLabel.text = element.name; cell.detailTextLabel.text = [NSString stringWithFormat:@"%d", element.uses]; } @@ -162,8 +168,8 @@ OPElementEntity *element; indexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section - 1]; if (indexPath.section == -1) { - element = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([OPElementEntity class]) - inManagedObjectContext:[self managedObjectContext]]; + element = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([OPElementGeneratedEntity class]) + inManagedObjectContext:[OPAppDelegate managedObjectContext]]; element.name = self.searchDisplayController.searchBar.text; } else element = [self.fetchedResultsController objectAtIndexPath:indexPath]; @@ -190,13 +196,13 @@ } - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { - + indexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section - 1]; - + if (editingStyle == UITableViewCellEditingStyleDelete) { OPElementEntity *element = [self.fetchedResultsController objectAtIndexPath:indexPath]; - - [[self managedObjectContext] deleteObject:element]; + + [[OPAppDelegate managedObjectContext] deleteObject:element]; } } diff --git a/OnePassword/OPTypes.h b/OnePassword/OPTypes.h index fba3949e..7ae7963a 100644 --- a/OnePassword/OPTypes.h +++ b/OnePassword/OPTypes.h @@ -31,4 +31,5 @@ typedef enum { } OPElementType; NSString *NSStringFromOPElementType(OPElementType type); -NSString *OPCalculateContent(OPElementType type, NSString *name, NSString *keyPhrase); \ No newline at end of file +Class ClassForOPElementType(OPElementType type); +NSString *OPCalculateContent(OPElementType type, NSString *name, NSString *keyPhrase, int counter); \ No newline at end of file diff --git a/OnePassword/OPTypes.m b/OnePassword/OPTypes.m index 4217d2e9..f4c76d4e 100644 --- a/OnePassword/OPTypes.m +++ b/OnePassword/OPTypes.m @@ -7,6 +7,8 @@ // #import "OPTypes.h" +#import "OPElementGeneratedEntity.h" +#import "OPElementStoredEntity.h" NSString *NSStringFromOPElementType(OPElementType type) { @@ -41,8 +43,40 @@ NSString *NSStringFromOPElementType(OPElementType type) { } } +Class ClassForOPElementType(OPElementType type) { + + if (!type) + return nil; + + switch (type) { + case OPElementTypeCalculatedLong: + return [OPElementGeneratedEntity class]; + + case OPElementTypeCalculatedMedium: + return [OPElementGeneratedEntity class]; + + case OPElementTypeCalculatedShort: + return [OPElementGeneratedEntity class]; + + case OPElementTypeCalculatedBasic: + return [OPElementGeneratedEntity class]; + + case OPElementTypeCalculatedPIN: + return [OPElementGeneratedEntity class]; + + case OPElementTypeStoredPersonal: + return [OPElementStoredEntity class]; + + case OPElementTypeStoredDevicePrivate: + return [OPElementStoredEntity class]; + + default: + [NSException raise:NSInternalInconsistencyException format:@"Type not supported: %d", type]; + } +} + static NSDictionary *OPTypes_ciphers = nil; -NSString *OPCalculateContent(OPElementType type, NSString *name, NSString *keyPhrase) { +NSString *OPCalculateContent(OPElementType type, NSString *name, NSString *keyPhrase, int counter) { assert(type & OPElementTypeCalculated); @@ -52,7 +86,7 @@ NSString *OPCalculateContent(OPElementType type, NSString *name, NSString *keyPh // Determine the hash whose bytes will be used for calculating a password: md4(name-keyPhrase) assert(name && keyPhrase); - NSData *keyHash = [[NSString stringWithFormat:@"%@-%@", name, keyPhrase] hashWith:PearlDigestMD4]; + NSData *keyHash = [[NSString stringWithFormat:@"%@-%@-%d", name, keyPhrase, counter] hashWith:PearlDigestMD4]; const char *keyBytes = keyHash.bytes; // Determine the cipher from the first hash byte. diff --git a/OnePassword/OnePassword.xcdatamodeld/OnePassword.xcdatamodel/contents b/OnePassword/OnePassword.xcdatamodeld/OnePassword.xcdatamodel/contents index ef43b69d..0a99d651 100644 --- a/OnePassword/OnePassword.xcdatamodeld/OnePassword.xcdatamodel/contents +++ b/OnePassword/OnePassword.xcdatamodeld/OnePassword.xcdatamodel/contents @@ -1,6 +1,6 @@ - + @@ -8,12 +8,16 @@ + + + - + - + + \ No newline at end of file diff --git a/OnePassword/Resources/Content-Backdrop.png b/OnePassword/Resources/Content-Backdrop.png index 1024ba1d..82742167 100644 Binary files a/OnePassword/Resources/Content-Backdrop.png and b/OnePassword/Resources/Content-Backdrop.png differ diff --git a/OnePassword/Resources/Content-Backdrop@2x.png b/OnePassword/Resources/Content-Backdrop@2x.png index b74f99d0..6901f5ca 100644 Binary files a/OnePassword/Resources/Content-Backdrop@2x.png and b/OnePassword/Resources/Content-Backdrop@2x.png differ diff --git a/OnePassword/Resources/Dividers/Square-bottom.png b/OnePassword/Resources/Dividers/Square-bottom.png new file mode 100644 index 00000000..c6d254ca Binary files /dev/null and b/OnePassword/Resources/Dividers/Square-bottom.png differ diff --git a/OnePassword/Resources/Dividers/Square-top.png b/OnePassword/Resources/Dividers/Square-top.png new file mode 100644 index 00000000..a9c9cfed Binary files /dev/null and b/OnePassword/Resources/Dividers/Square-top.png differ diff --git a/OnePassword/Resources/Icon-72.png b/OnePassword/Resources/Icon-72.png index 4277a5ce..f5d2f9b3 100644 Binary files a/OnePassword/Resources/Icon-72.png and b/OnePassword/Resources/Icon-72.png differ diff --git a/OnePassword/Resources/Icon-Small-50.png b/OnePassword/Resources/Icon-Small-50.png index 21c1b05d..c865c0ff 100644 Binary files a/OnePassword/Resources/Icon-Small-50.png and b/OnePassword/Resources/Icon-Small-50.png differ diff --git a/OnePassword/Resources/Icon-Small.png b/OnePassword/Resources/Icon-Small.png index fc6f76d7..9462256a 100644 Binary files a/OnePassword/Resources/Icon-Small.png and b/OnePassword/Resources/Icon-Small.png differ diff --git a/OnePassword/Resources/Icon-Small@2x.png b/OnePassword/Resources/Icon-Small@2x.png index 8732dafc..dbb45f20 100644 Binary files a/OnePassword/Resources/Icon-Small@2x.png and b/OnePassword/Resources/Icon-Small@2x.png differ diff --git a/OnePassword/Resources/Icon.png b/OnePassword/Resources/Icon.png index 86fa6bd4..9823eddd 100644 Binary files a/OnePassword/Resources/Icon.png and b/OnePassword/Resources/Icon.png differ diff --git a/OnePassword/Resources/Icon@2x.png b/OnePassword/Resources/Icon@2x.png index de102215..7a4dc1b3 100644 Binary files a/OnePassword/Resources/Icon@2x.png and b/OnePassword/Resources/Icon@2x.png differ diff --git a/OnePassword/Resources/help.html b/OnePassword/Resources/help.html index e891a9e1..e4085fbf 100644 --- a/OnePassword/Resources/help.html +++ b/OnePassword/Resources/help.html @@ -8,17 +8,22 @@ text-shadow: 0 1px black; font: 16px "Baskerville"; } - h1 { + h1, h2 { margin-top: 1em; padding-top: 1em; - - font: inherit; + font-family: inherit; font-weight: bold; } + h2 { + font-size: inherit; + } -

— 1 —

+

One Password

+

by Lyndir

+ +

— 1 —

Find the site that you need a password for by entering it into the search field.

@@ -27,19 +32,20 @@ The counter shows how many times you've generated a password for the site.

-

— 2 —

+

— 2 —

- Your site's password is now displayed. + The site's password is now displayed.

- Below it you can set the password type. Some types auto-generate passwords, + Below it you can set the site's password type. Some types auto-generate a password, others let you choose your own.

If your new password is not deemed valid by the site, try changing the password type.

- To generate a new password for the site, you can increment its password counter. + To generate a new password for the site, you can increment the site's password counter. + Do this when, for instance, you've had to share the password with somebody else.

diff --git a/OnePassword/Settings.bundle/Root.plist b/OnePassword/Settings.bundle/Root.plist new file mode 100644 index 00000000..31a60959 --- /dev/null +++ b/OnePassword/Settings.bundle/Root.plist @@ -0,0 +1,61 @@ + + + + + PreferenceSpecifiers + + + Type + PSTitleValueSpecifier + Title + Version + Key + version + + + Type + PSTitleValueSpecifier + Title + Build + Key + build + + + Type + PSTitleValueSpecifier + Title + Copyright + Key + copyright + + + Type + PSGroupSpecifier + Title + Security + + + DefaultValue + + Key + storeKeyPhrase + Title + Store master password + Type + PSToggleSwitchSpecifier + + + Type + PSToggleSwitchSpecifier + Title + Remember master password + Key + rememberKeyPhrase + DefaultValue + + + + StringsTable + Root + + diff --git a/OnePassword/Settings.bundle/en.lproj/Root.strings b/OnePassword/Settings.bundle/en.lproj/Root.strings new file mode 100644 index 00000000..8cd87b9d Binary files /dev/null and b/OnePassword/Settings.bundle/en.lproj/Root.strings differ diff --git a/OnePassword/en.lproj/MainStoryboard_iPhone.storyboard b/OnePassword/en.lproj/MainStoryboard_iPhone.storyboard index 7505961c..42c351f5 100644 --- a/OnePassword/en.lproj/MainStoryboard_iPhone.storyboard +++ b/OnePassword/en.lproj/MainStoryboard_iPhone.storyboard @@ -5,141 +5,182 @@ - + - - - + + + - + - + - - + + - + - - - + + - + - - - + + - + - - - + + - + - - - + + - + - @@ -148,55 +189,162 @@ - - Passwords of this type are encrypted and stored. They are not tied to a certain algorithm but if you loose the storage, you loose the passwords. + - - + + - + - - - + + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -207,11 +355,11 @@ - - + + - + @@ -224,30 +372,33 @@ - - - - - - - - - - - - + + + + + + - - - + + + - - - - + + + + + + - - - + + + + + + + + + + + - + - + + + + + + + + + + + - + + + + + + + + @@ -359,10 +568,9 @@ - + - @@ -374,12 +582,50 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +