diff --git a/OnePassword.sketch/Data b/OnePassword.sketch/Data
index 57a8e034..4b1d13b3 100644
Binary files a/OnePassword.sketch/Data and b/OnePassword.sketch/Data differ
diff --git a/OnePassword.sketch/QuickLook/Preview.pdf b/OnePassword.sketch/QuickLook/Preview.pdf
index 80518b51..e4589f06 100644
Binary files a/OnePassword.sketch/QuickLook/Preview.pdf and b/OnePassword.sketch/QuickLook/Preview.pdf differ
diff --git a/OnePassword.sketch/QuickLook/Thumbnail.jpg b/OnePassword.sketch/QuickLook/Thumbnail.jpg
index 6de1afb8..1ecd367c 100644
Binary files a/OnePassword.sketch/QuickLook/Thumbnail.jpg and b/OnePassword.sketch/QuickLook/Thumbnail.jpg differ
diff --git a/OnePassword.xcodeproj/project.pbxproj b/OnePassword.xcodeproj/project.pbxproj
index ada8f985..9a357f8c 100644
--- a/OnePassword.xcodeproj/project.pbxproj
+++ b/OnePassword.xcodeproj/project.pbxproj
@@ -6,6 +6,20 @@
objectVersion = 46;
objects = {
+/* Begin PBXAggregateTarget section */
+ DA6556DC14D55C1500841C99 /* InfoPlist */ = {
+ isa = PBXAggregateTarget;
+ buildConfigurationList = DA6556DD14D55C1600841C99 /* Build configuration list for PBXAggregateTarget "InfoPlist" */;
+ buildPhases = (
+ DA6556E014D55C2700841C99 /* ShellScript */,
+ );
+ dependencies = (
+ );
+ name = InfoPlist;
+ productName = InfoPlist;
+ };
+/* End PBXAggregateTarget section */
+
/* Begin PBXBuildFile section */
DA007F5214B24DCD00251337 /* OPConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = DA007F5114B24DCD00251337 /* OPConfig.m */; };
DA007F5514B25EE100251337 /* ciphers.plist in Resources */ = {isa = PBXBuildFile; fileRef = DA007F5414B25EE100251337 /* ciphers.plist */; };
@@ -656,6 +670,13 @@
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
+ DA6556E114D55C4400841C99 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = DA5BFA3B147E415C00F98B1E /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = DA6556DC14D55C1500841C99;
+ remoteInfo = InfoPlist;
+ };
DAC63281148681190075AEA5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = DA5BFA3B147E415C00F98B1E /* Project object */;
@@ -1738,8 +1759,6 @@
DA5BFA39147E415C00F98B1E = {
isa = PBXGroup;
children = (
- DA04E33D14B1E70400ECA4F3 /* MobileCoreServices.framework */,
- DAC632871486D95D0075AEA5 /* Security.framework */,
DA5BFA50147E415C00F98B1E /* OnePassword */,
DAC77CAF148291A600BCF976 /* Pearl */,
DAC6325F1486805C0075AEA5 /* uicolor-utilities */,
@@ -1763,6 +1782,8 @@
DA5BFA47147E415C00F98B1E /* Frameworks */ = {
isa = PBXGroup;
children = (
+ DA04E33D14B1E70400ECA4F3 /* MobileCoreServices.framework */,
+ DAC632871486D95D0075AEA5 /* Security.framework */,
DA5BFA48147E415C00F98B1E /* UIKit.framework */,
DA5BFA4A147E415C00F98B1E /* Foundation.framework */,
DA5BFA4C147E415C00F98B1E /* CoreGraphics.framework */,
@@ -2271,6 +2292,7 @@
isa = PBXNativeTarget;
buildConfigurationList = DA5BFA6D147E415C00F98B1E /* Build configuration list for PBXNativeTarget "OnePassword" */;
buildPhases = (
+ DA6556E314D55F3000841C99 /* ShellScript */,
DA5BFA40147E415C00F98B1E /* Sources */,
DA5BFA41147E415C00F98B1E /* Frameworks */,
DA5BFA42147E415C00F98B1E /* Resources */,
@@ -2278,6 +2300,7 @@
buildRules = (
);
dependencies = (
+ DA6556E214D55C4400841C99 /* PBXTargetDependency */,
DAC63282148681190075AEA5 /* PBXTargetDependency */,
);
name = OnePassword;
@@ -2365,6 +2388,7 @@
DAC77CAC148291A600BCF976 /* Pearl */,
DAC6325C1486805C0075AEA5 /* uicolor-utilities */,
DAC6326B148680650075AEA5 /* jrswizzle */,
+ DA6556DC14D55C1500841C99 /* InfoPlist */,
);
};
/* End PBXProject section */
@@ -2926,6 +2950,37 @@
};
/* End PBXResourcesBuildPhase section */
+/* Begin PBXShellScriptBuildPhase section */
+ DA6556E014D55C2700841C99 /* ShellScript */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ outputPaths = (
+ InfoPlist.h,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = "/usr/bin/env bash";
+ shellScript = "cat > \"$SCRIPT_OUTPUT_FILE_0\" <<. \n#define GIT_COMMIT $(git describe --tags --always --dirty --long)\n#define GIT_TAG $(git describe --tags | sed 's/-[^-]*-[^-]*$//')h\n#define GIT_COMMIT_YEAR $(git log --format=format:%ci HEAD^.. | sed 's/-.*//')\n.\ntouch /Users/lhunath/Documents/workspace/lyndir/OnePassword/OnePassword/OnePassword-Info.plist";
+ };
+ DA6556E314D55F3000841C99 /* ShellScript */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "PATH+=:/usr/libexec\n\nPlistBuddy -c \"Set :CFBundleVersion $(git describe --tags --always --dirty --long)\" \"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"\nPlistBuddy -c \"Set :CFBundleShortVersionString $(git describe --tags | sed 's/-[^-]*-[^-]*$//')\" \"$BUILT_PRODUCTS_DIR/$INFOPLIST_PATH\"\n\nPlistBuddy -c \"Set :CFBundleShortVersionString $(git describe --tags | sed 's/-[^-]*-[^-]*$//')\" \"OnePassword/Settings.bundle/Root.plist\"\n";
+ showEnvVarsInLog = 0;
+ };
+/* End PBXShellScriptBuildPhase section */
+
/* Begin PBXSourcesBuildPhase section */
DA5BFA40147E415C00F98B1E /* Sources */ = {
isa = PBXSourcesBuildPhase;
@@ -3006,6 +3061,11 @@
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
+ DA6556E214D55C4400841C99 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = DA6556DC14D55C1500841C99 /* InfoPlist */;
+ targetProxy = DA6556E114D55C4400841C99 /* PBXContainerItemProxy */;
+ };
DAC63282148681190075AEA5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = DAC77CAC148291A600BCF976 /* Pearl */;
@@ -3114,6 +3174,20 @@
};
name = Release;
};
+ DA6556DE14D55C1600841C99 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ };
+ name = Debug;
+ };
+ DA6556DF14D55C1600841C99 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ };
+ name = Release;
+ };
DAC632661486805C0075AEA5 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -3209,6 +3283,14 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
+ DA6556DD14D55C1600841C99 /* Build configuration list for PBXAggregateTarget "InfoPlist" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DA6556DE14D55C1600841C99 /* Debug */,
+ DA6556DF14D55C1600841C99 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ };
DAC632651486805C0075AEA5 /* Build configuration list for PBXNativeTarget "uicolor-utilities" */ = {
isa = XCConfigurationList;
buildConfigurations = (
diff --git a/OnePassword/MainStoryboard_iPhone.storyboard b/OnePassword/MainStoryboard_iPhone.storyboard
index 38b4f233..7df0030e 100644
--- a/OnePassword/MainStoryboard_iPhone.storyboard
+++ b/OnePassword/MainStoryboard_iPhone.storyboard
@@ -306,7 +306,7 @@ The passwords aren't saved anywhere. This is a major advantage: if you loose yo
-
+
@@ -479,21 +479,24 @@ The passwords aren't saved anywhere. This is a major advantage: if you loose yo
-
+
+
+
+
-
+
-
-
+
+
-
-
+
+
The password for apple.com has changed.
@@ -567,7 +570,7 @@ L4m3P4sSw0rD
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -632,6 +659,7 @@ L4m3P4sSw0rD
+
@@ -645,46 +673,6 @@ L4m3P4sSw0rD
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/OnePassword/OPAppDelegate.h b/OnePassword/OPAppDelegate.h
index fb250185..657a5271 100644
--- a/OnePassword/OPAppDelegate.h
+++ b/OnePassword/OPAppDelegate.h
@@ -10,14 +10,16 @@
@interface OPAppDelegate : AbstractAppDelegate
-@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
-@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
-@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
-@property (strong, nonatomic) NSString *keyPhrase;
+@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
+@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
+@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
+@property (strong, nonatomic) NSString *keyPhrase;
+@property (strong, nonatomic) NSData *keyPhraseHash;
+@property (strong, nonatomic) NSString *keyPhraseHashHex;
+ (OPAppDelegate *)get;
-+ (NSManagedObjectContext *)managedObjectContext;
+ (NSManagedObjectModel *)managedObjectModel;
++ (NSManagedObjectContext *)managedObjectContext;
- (void)saveContext;
- (NSURL *)applicationDocumentsDirectory;
diff --git a/OnePassword/OPAppDelegate.m b/OnePassword/OPAppDelegate.m
index 83513143..22fd8d96 100644
--- a/OnePassword/OPAppDelegate.m
+++ b/OnePassword/OPAppDelegate.m
@@ -15,19 +15,27 @@
+ (NSDictionary *)keyPhraseQuery;
+ (NSDictionary *)keyPhraseHashQuery;
+- (void)loadKeyPhrase;
+- (void)forgetKeyPhrase;
+- (void)loadStoredKeyPhrase;
+- (void)askKeyPhrase;
+
@end
@implementation OPAppDelegate
-@synthesize managedObjectContext = __managedObjectContext;
@synthesize managedObjectModel = __managedObjectModel;
+@synthesize managedObjectContext = __managedObjectContext;
@synthesize persistentStoreCoordinator = __persistentStoreCoordinator;
+
@synthesize keyPhrase = _keyPhrase;
+@synthesize keyPhraseHash = _keyPhraseHash;
+@synthesize keyPhraseHashHex = _keyPhraseHashHex;
+ (void)initialize {
-
+
#ifdef DEBUG
- [Logger get].autoprintLevel = LogLevelDebug;
+ [Logger get].autoprintLevel = LogLevelTrace;
[NSClassFromString(@"WebView") performSelector:@selector(_enableRemoteInspector)];
#endif
}
@@ -37,7 +45,7 @@
static NSDictionary *OPKeyPhraseQuery = nil;
if (!OPKeyPhraseQuery)
OPKeyPhraseQuery = [KeyChain createQueryForClass:kSecClassGenericPassword
- attributes:[NSDictionary dictionaryWithObject:@"MasterKeyPhrase"
+ attributes:[NSDictionary dictionaryWithObject:@"MasterPassword"
forKey:(__bridge id)kSecAttrService]
matches:nil];
@@ -49,7 +57,7 @@
static NSDictionary *OPKeyPhraseHashQuery = nil;
if (!OPKeyPhraseHashQuery)
OPKeyPhraseHashQuery = [KeyChain createQueryForClass:kSecClassGenericPassword
- attributes:[NSDictionary dictionaryWithObject:@"MasterKeyPhraseHash"
+ attributes:[NSDictionary dictionaryWithObject:@"MasterPasswordHash"
forKey:(__bridge id)kSecAttrService]
matches:nil];
@@ -83,37 +91,83 @@
[UIFont fontWithName:@"Helvetica-Neue" size:0.0], UITextAttributeFont,
nil]
forState:UIControlStateNormal];
-
+
UIImage *toolBarImage = [[UIImage imageNamed:@"ui_toolbar_container"] resizableImageWithCapInsets:UIEdgeInsetsMake(25, 5, 5, 5)];
[[UISearchBar appearance] setBackgroundImage:toolBarImage];
[[UIToolbar appearance] setBackgroundImage:toolBarImage forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsDefault];
/*
- UIImage *minImage = [[UIImage imageNamed:@"slider-minimum.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 0)];
- UIImage *maxImage = [[UIImage imageNamed:@"slider-maximum.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 0)];
- UIImage *thumbImage = [UIImage imageNamed:@"slider-handle.png"];
-
- [[UISlider appearance] setMaximumTrackImage:maxImage forState:UIControlStateNormal];
- [[UISlider appearance] setMinimumTrackImage:minImage forState:UIControlStateNormal];
- [[UISlider appearance] setThumbImage:thumbImage forState:UIControlStateNormal];
-
- UIImage *segmentSelected = [[UIImage imageNamed:@"segcontrol_sel.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 4, 0, 4)];
- UIImage *segmentUnselected = [[UIImage imageNamed:@"segcontrol_uns.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 15, 0, 15)];
- UIImage *segmentSelectedUnselected = [UIImage imageNamed:@"segcontrol_sel-uns.png"];
- UIImage *segUnselectedSelected = [UIImage imageNamed:@"segcontrol_uns-sel.png"];
- UIImage *segmentUnselectedUnselected = [UIImage imageNamed:@"segcontrol_uns-uns.png"];
-
- [[UISegmentedControl appearance] setBackgroundImage:segmentUnselected forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
- [[UISegmentedControl appearance] setBackgroundImage:segmentSelected forState:UIControlStateSelected barMetrics:UIBarMetricsDefault];
-
- [[UISegmentedControl appearance] setDividerImage:segmentUnselectedUnselected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
- [[UISegmentedControl appearance] setDividerImage:segmentSelectedUnselected forLeftSegmentState:UIControlStateSelected rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
- [[UISegmentedControl appearance] setDividerImage:segUnselectedSelected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateSelected barMetrics:UIBarMetricsDefault];*/
+ UIImage *minImage = [[UIImage imageNamed:@"slider-minimum.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 0)];
+ UIImage *maxImage = [[UIImage imageNamed:@"slider-maximum.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 0)];
+ UIImage *thumbImage = [UIImage imageNamed:@"slider-handle.png"];
+
+ [[UISlider appearance] setMaximumTrackImage:maxImage forState:UIControlStateNormal];
+ [[UISlider appearance] setMinimumTrackImage:minImage forState:UIControlStateNormal];
+ [[UISlider appearance] setThumbImage:thumbImage forState:UIControlStateNormal];
+
+ UIImage *segmentSelected = [[UIImage imageNamed:@"segcontrol_sel.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 4, 0, 4)];
+ UIImage *segmentUnselected = [[UIImage imageNamed:@"segcontrol_uns.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 15, 0, 15)];
+ UIImage *segmentSelectedUnselected = [UIImage imageNamed:@"segcontrol_sel-uns.png"];
+ UIImage *segUnselectedSelected = [UIImage imageNamed:@"segcontrol_uns-sel.png"];
+ UIImage *segmentUnselectedUnselected = [UIImage imageNamed:@"segcontrol_uns-uns.png"];
+
+ [[UISegmentedControl appearance] setBackgroundImage:segmentUnselected forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
+ [[UISegmentedControl appearance] setBackgroundImage:segmentSelected forState:UIControlStateSelected barMetrics:UIBarMetricsDefault];
+
+ [[UISegmentedControl appearance] setDividerImage:segmentUnselectedUnselected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
+ [[UISegmentedControl appearance] setDividerImage:segmentSelectedUnselected forLeftSegmentState:UIControlStateSelected rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
+ [[UISegmentedControl appearance] setDividerImage:segUnselectedSelected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateSelected barMetrics:UIBarMetricsDefault];*/
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
+ [self loadKeyPhrase];
+}
+
+- (void)loadKeyPhrase {
+
+ if ([[OPConfig get].forgetKeyPhrase boolValue]) {
+ [self forgetKeyPhrase];
+ return;
+ }
+
+ [self loadStoredKeyPhrase];
+ if (!self.keyPhrase) {
+ // Key phrase is not known. Ask user to set/specify it.
+ dbg(@"Key phrase not known. Will ask user.");
+ [self askKeyPhrase];
+ return;
+ }
+}
+
+- (void)forgetKeyPhrase {
+
+ dbg(@"Forgetting key phrase.");
+ [AlertViewController showAlertWithTitle:@"Changing Master Password"
+ message:
+ @"You've requested to change your master password.\n\n"
+ @"If you continue, your current sites and passwords will become unavailable.\n\n"
+ @"You can always change back to the old master password later.\n"
+ @"Your old sites and passwords will then become available again."
+ viewStyle:UIAlertViewStyleDefault
+ tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
+ if (buttonIndex == [alert firstOtherButtonIndex]) {
+ // Key phrase reset. Delete it.
+ dbg(@"Deleting master key phrase and hash from key chain.");
+ [KeyChain deleteItemForQuery:[OPAppDelegate keyPhraseQuery]];
+ [KeyChain deleteItemForQuery:[OPAppDelegate keyPhraseHashQuery]];
+ }
+
+ [self loadKeyPhrase];
+ }
+ cancelTitle:[PearlStrings get].commonButtonAbort
+ otherTitles:[PearlStrings get].commonButtonContinue, nil];
+ [OPConfig get].forgetKeyPhrase = [NSNumber numberWithBool:NO];
+}
+
+- (void)loadStoredKeyPhrase {
+
if ([[OPConfig get].storeKeyPhrase boolValue]) {
// Key phrase is stored in keychain. Load it.
dbg(@"Loading master key phrase from key chain.");
@@ -127,59 +181,55 @@
dbg(@"Deleting master key phrase from key chain.");
[KeyChain deleteItemForQuery:[OPAppDelegate keyPhraseQuery]];
}
+}
+
+- (void)askKeyPhrase {
- 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(), ^{
+ NSData *keyPhraseHash = [KeyChain dataOfItemForQuery:[OPAppDelegate keyPhraseHashQuery]];
+ dbg(@"Key phrase hash %@.", keyPhraseHash? @"known": @"NOT known");
- dispatch_async(dispatch_get_main_queue(), ^{
- 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)
- exit(0);
-
- if (![answer length]) {
- // User didn't enter a key phrase.
- [AlertViewController showAlertWithTitle:[PearlStrings get].commonTitleError
- message:@"No master password entered."
- tappedButtonBlock:
- ^(NSInteger buttonIndex) {
- exit(0);
- } cancelTitle:@"Quit" otherTitles:nil];
- }
-
- 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:
- ^(NSInteger buttonIndex) {
- exit(0);
- } cancelTitle:@"Quit" otherTitles:nil];
-
- return;
- }
-
- 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];
- });
- }
+ [AlertViewController showAlertWithTitle:@"One Password"
+ message:keyPhraseHash? @"Unlock with your master password:": @"Choose your master password:"
+ viewStyle:UIAlertViewStyleSecureTextInput
+ tappedButtonBlock:
+ ^(UIAlertView *alert, NSInteger buttonIndex) {
+ if (buttonIndex == [alert cancelButtonIndex])
+ exit(0);
+
+ NSString *answer = [alert textFieldAtIndex:0].text;
+ if (![answer length]) {
+ // User didn't enter a key phrase.
+ [AlertViewController showAlertWithTitle:[PearlStrings get].commonTitleError
+ message:@"No master password entered."
+ viewStyle:UIAlertViewStyleDefault
+ tappedButtonBlock:
+ ^(UIAlertView *alert, NSInteger buttonIndex) {
+ exit(0);
+ } cancelTitle:@"Quit" otherTitles:nil];
+ }
+
+ 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."
+ viewStyle:UIAlertViewStyleDefault
+ tappedButtonBlock:
+ ^(UIAlertView *alert, NSInteger buttonIndex) {
+ exit(0);
+ } cancelTitle:@"Quit" otherTitles:nil];
+
+ return;
+ }
+
+ self.keyPhrase = answer;
+ } cancelTitle:@"Quit" otherTitles:@"Unlock", nil];
+ });
}
- (void)applicationWillResignActive:(UIApplication *)application {
@@ -228,10 +278,12 @@
_keyPhrase = keyPhrase;
if (keyPhrase) {
- NSData *keyPhraseHash = [keyPhrase hashWith:PearlDigestSHA512];
- dbg(@"Updating master key phrase hash to: %@.", keyPhraseHash);
+ self.keyPhraseHash = [keyPhrase hashWith:PearlDigestSHA512];
+ self.keyPhraseHashHex = [self.keyPhraseHash encodeHex];
+
+ dbg(@"Updating master key phrase hash to: %@.", self.keyPhraseHashHex);
[KeyChain addOrUpdateItemForQuery:[OPAppDelegate keyPhraseHashQuery]
- withAttributes:[NSDictionary dictionaryWithObject:keyPhraseHash
+ withAttributes:[NSDictionary dictionaryWithObject:self.keyPhraseHash
forKey:(__bridge id)kSecValueData]];
if ([[OPConfig get].storeKeyPhrase boolValue]) {
dbg(@"Storing master key phrase in key chain.");
diff --git a/OnePassword/OPConfig.h b/OnePassword/OPConfig.h
index 4efc68c2..6f26f70f 100644
--- a/OnePassword/OPConfig.h
+++ b/OnePassword/OPConfig.h
@@ -11,6 +11,7 @@
@property (nonatomic, retain) NSNumber *dataStoreError;
@property (nonatomic, retain) NSNumber *storeKeyPhrase;
@property (nonatomic, retain) NSNumber *rememberKeyPhrase;
+@property (nonatomic, retain) NSNumber *forgetKeyPhrase;
@property (nonatomic, retain) NSNumber *helpHidden;
+ (OPConfig *)get;
diff --git a/OnePassword/OPConfig.m b/OnePassword/OPConfig.m
index c3710f85..60ee3efe 100644
--- a/OnePassword/OPConfig.m
+++ b/OnePassword/OPConfig.m
@@ -10,7 +10,7 @@
@implementation OPConfig
-@dynamic dataStoreError, storeKeyPhrase, rememberKeyPhrase, helpHidden;
+@dynamic dataStoreError, storeKeyPhrase, rememberKeyPhrase, forgetKeyPhrase, helpHidden;
- (id)init {
@@ -22,6 +22,7 @@
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(dataStoreError)),
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(storeKeyPhrase)),
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(rememberKeyPhrase)),
+ [NSNumber numberWithBool:NO], NSStringFromSelector(@selector(forgetKeyPhrase)),
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(helpHidden)),
nil]];
diff --git a/OnePassword/OPElementEntity.h b/OnePassword/OPElementEntity.h
index ff477240..f678c8aa 100644
--- a/OnePassword/OPElementEntity.h
+++ b/OnePassword/OPElementEntity.h
@@ -13,14 +13,12 @@
@interface OPElementEntity : NSManagedObject
@property (nonatomic, retain) NSString *name;
+@property (nonatomic, retain) NSString *mpHashHex;
@property (nonatomic) int16_t type;
@property (nonatomic) int16_t uses;
@property (nonatomic) NSTimeInterval lastUsed;
-@property (nonatomic, retain) NSString *contentUTI;
-@property (nonatomic) int16_t contentType;
- (void)use;
- (id)content;
-- (NSString *)contentDescription;
@end
diff --git a/OnePassword/OPElementEntity.m b/OnePassword/OPElementEntity.m
index 64e35364..fdb0f76b 100644
--- a/OnePassword/OPElementEntity.m
+++ b/OnePassword/OPElementEntity.m
@@ -12,11 +12,10 @@
@implementation OPElementEntity
@dynamic name;
+@dynamic mpHashHex;
@dynamic type;
@dynamic uses;
@dynamic lastUsed;
-@dynamic contentUTI;
-@dynamic contentType;
- (void)use {
@@ -29,9 +28,15 @@
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Content implementation missing." userInfo:nil];
}
-- (NSString *)contentDescription {
+- (NSString *)description {
return [[self content] description];
}
+- (NSString *)debugDescription {
+
+ return [NSString stringWithFormat:@"{%@: name=%@, mpHashHex=%@, type=%d, uses=%d, lastUsed=%@}",
+ NSStringFromClass([self class]), self.name, self.mpHashHex, self.type, self.uses, self.lastUsed];
+}
+
@end
diff --git a/OnePassword/OPElementGeneratedEntity.m b/OnePassword/OPElementGeneratedEntity.m
index d171f764..3f5d443f 100644
--- a/OnePassword/OPElementGeneratedEntity.m
+++ b/OnePassword/OPElementGeneratedEntity.m
@@ -16,6 +16,8 @@
- (id)content {
+ assert(self.type & OPElementTypeClassCalculated);
+
if (![self.name length])
return nil;
diff --git a/OnePassword/OPMainViewController.h b/OnePassword/OPMainViewController.h
index 0039d6d8..ca17c449 100644
--- a/OnePassword/OPMainViewController.h
+++ b/OnePassword/OPMainViewController.h
@@ -10,7 +10,7 @@
#import "OPElementEntity.h"
#import "OPSearchDelegate.h"
-@interface OPMainViewController : UIViewController
+@interface OPMainViewController : UIViewController
@property (strong, nonatomic) OPElementEntity *activeElement;
@property (strong, nonatomic) IBOutlet OPSearchDelegate *searchResultsController;
diff --git a/OnePassword/OPMainViewController.m b/OnePassword/OPMainViewController.m
index 86545ccb..a3f09c38 100644
--- a/OnePassword/OPMainViewController.m
+++ b/OnePassword/OPMainViewController.m
@@ -84,7 +84,7 @@
// Because IB's edit button doesn't auto-toggle self.editable like editButtonItem does.
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"ui_background"]];
- [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification object:nil queue:[NSOperationQueue mainQueue]
+ [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:nil queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note) {
if (![OPAppDelegate get].keyPhrase) {
self.activeElement = nil;
@@ -171,9 +171,10 @@
self.passwordCounter.text = [NSString stringWithFormat:@"%d", ((OPElementGeneratedEntity *) self.activeElement).counter];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
- NSString *contentDescription = self.activeElement.contentDescription;
+ NSString *description = self.activeElement.description;
+
dispatch_async(dispatch_get_main_queue(), ^{
- self.contentField.text = contentDescription;
+ self.contentField.text = description;
});
});
}
@@ -223,7 +224,7 @@
return;
[[UIPasteboard generalPasteboard] setValue:self.activeElement.content
- forPasteboardType:self.activeElement.contentUTI];
+ forPasteboardType:(id)kUTTypeUTF8PlainText];
[self showContentTip:@"Copied!" withIcon:nil];
}
@@ -242,9 +243,9 @@
- (void)updateElement:(void (^)(void))updateElement {
// Update password counter.
- NSString *oldPassword = self.activeElement.contentDescription;
+ NSString *oldPassword = self.activeElement.description;
updateElement();
- NSString *newPassword = self.activeElement.contentDescription;
+ NSString *newPassword = self.activeElement.description;
[self updateAnimated:YES];
// Show new and old password.
@@ -304,10 +305,9 @@
OPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:ClassNameFromOPElementType(type)
inManagedObjectContext:[OPAppDelegate managedObjectContext]];
newElement.name = self.activeElement.name;
+ newElement.mpHashHex = self.activeElement.mpHashHex;
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;
@@ -315,7 +315,7 @@
self.activeElement.type = type;
- if (type & OPElementTypeClassStored && !self.activeElement.contentDescription)
+ if (type & OPElementTypeClassStored && ![self.activeElement.description length])
[self showContentTip:@"Tap to set a password." withIcon:self.contentTipEditIcon];
}];
}
@@ -362,4 +362,14 @@
}
}
+- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
+ navigationType:(UIWebViewNavigationType)navigationType {
+ if (navigationType == UIWebViewNavigationTypeLinkClicked) {
+ [[UIApplication sharedApplication] openURL:[request URL]];
+ return NO;
+ }
+
+ return YES;
+}
+
@end
diff --git a/OnePassword/OPSearchDelegate.m b/OnePassword/OPSearchDelegate.m
index d7ef682d..0c401df3 100644
--- a/OnePassword/OPSearchDelegate.m
+++ b/OnePassword/OPSearchDelegate.m
@@ -61,15 +61,18 @@
- (void)update {
- NSString *text = self.searchDisplayController.searchBar.text;
- if (!text)
- text = @"";
+ NSString *query = self.searchDisplayController.searchBar.text;
+ if (!query)
+ query = @"";
- NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:NSStringFromClass([OPElementEntity class])];
+ NSFetchRequest *fetchRequest = [[OPAppDelegate get].managedObjectModel
+ fetchRequestFromTemplateWithName:@"OPSearchElement"
+ substitutionVariables:[NSDictionary dictionaryWithObjectsAndKeys:
+ query, @"query",
+ [OPAppDelegate get].keyPhraseHashHex, @"mpHashHex",
+ nil]];
[fetchRequest setSortDescriptors:
[NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses" ascending:NO]]];
- [fetchRequest setPredicate:
- [NSPredicate predicateWithFormat:@"name BEGINSWITH[cd] %@", text]];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:[OPAppDelegate managedObjectContext]
@@ -196,7 +199,10 @@
// "New" section.
element = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([OPElementGeneratedEntity class])
inManagedObjectContext:[OPAppDelegate managedObjectContext]];
+ assert([element isKindOfClass:ClassFromOPElementType(element.type)]);
+
element.name = self.searchDisplayController.searchBar.text;
+ element.mpHashHex = [OPAppDelegate get].keyPhraseHashHex;
}
[self.delegate didSelectElement:element];
diff --git a/OnePassword/OnePassword-Info.plist b/OnePassword/OnePassword-Info.plist
index b22f8f8a..b8be5dc9 100644
--- a/OnePassword/OnePassword-Info.plist
+++ b/OnePassword/OnePassword-Info.plist
@@ -37,13 +37,15 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.0
+ [auto]
CFBundleSignature
????
CFBundleVersion
- 1.0
+ [auto]
LSRequiresIPhoneOS
+ NSHumanReadableCopyright
+ Copyright ©2011-2012, Lyndir
UIMainStoryboardFile
MainStoryboard_iPhone
UIMainStoryboardFile~ipad
diff --git a/OnePassword/OnePassword.xcdatamodeld/OnePassword.xcdatamodel/contents b/OnePassword/OnePassword.xcdatamodeld/OnePassword.xcdatamodel/contents
index 0a99d651..fb751c5c 100644
--- a/OnePassword/OnePassword.xcdatamodeld/OnePassword.xcdatamodel/contents
+++ b/OnePassword/OnePassword.xcdatamodeld/OnePassword.xcdatamodel/contents
@@ -1,9 +1,8 @@
-
-
+
@@ -14,7 +13,7 @@
-
+
diff --git a/OnePassword/Resources/Content-Backdrop.png b/OnePassword/Resources/Content-Backdrop.png
index 055be357..835c54ed 100644
Binary files a/OnePassword/Resources/Content-Backdrop.png and b/OnePassword/Resources/Content-Backdrop.png differ
diff --git a/OnePassword/Resources/Icon-72.png b/OnePassword/Resources/Icon-72.png
index f5d2f9b3..1e5db852 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 c865c0ff..1ebf0fcd 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 9462256a..09971b0b 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 dbb45f20..d3ed98c1 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 9823eddd..f4412399 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 7a4dc1b3..95d593d9 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 4af31dc9..033289a3 100644
--- a/OnePassword/Resources/help.html
+++ b/OnePassword/Resources/help.html
@@ -9,7 +9,7 @@
font: 16px "Baskerville";
}
h1, h2 {
- margin-top: 1em;
+ margin-top: 1.5em;
padding-top: 1em;
font-family: inherit;
font-weight: bold;
@@ -20,12 +20,19 @@
i {
font-weight: bold;
}
+ q {
+ font-style: italic;
+ }
img {
display: inline-block;
height: 1.4em;
margin: -0.2em 0;
vertical-align: middle;
}
+ a, a:link {
+ color: inherit;
+ font-weight: bold;
+ }