2
0

Migration of saved passwords.

[ADDED]     Migrate saved passwords when master password changes.
[IMPROVED]  UI improvements to apps.
This commit is contained in:
Maarten Billemont 2012-09-01 22:14:57 +02:00
parent d75ec5c689
commit e2bf8cefa2
11 changed files with 112 additions and 53 deletions

2
External/Pearl vendored

@ -1 +1 @@
Subproject commit ff1e29fc3a4c2ee7b08eb686d06f29a11466bbe5 Subproject commit 552cf5ffc9445362f8f91357f4f7a034b2c390a3

View File

@ -28,7 +28,9 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
inf(@"Found key in keychain for: %@", user.userID); inf(@"Found key in keychain for: %@", user.userID);
else { else {
user.saveKey = NO; [user.managedObjectContext performBlockAndWait:^{
user.saveKey = NO;
}];
inf(@"No key found in keychain for: %@", user.userID); inf(@"No key found in keychain for: %@", user.userID);
} }
@ -58,7 +60,9 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
OSStatus result = [PearlKeyChain deleteItemForQuery:keyQuery(user)]; OSStatus result = [PearlKeyChain deleteItemForQuery:keyQuery(user)];
if (result == noErr || result == errSecItemNotFound) { if (result == noErr || result == errSecItemNotFound) {
user.saveKey = NO; [user.managedObjectContext performBlockAndWait:^{
user.saveKey = NO;
}];
if (result == noErr) { if (result == noErr) {
inf(@"Removed key from keychain for: %@", user.userID); inf(@"Removed key from keychain for: %@", user.userID);
@ -82,14 +86,68 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
- (BOOL)signInAsUser:(MPUserEntity *)user usingMasterPassword:(NSString *)password { - (BOOL)signInAsUser:(MPUserEntity *)user usingMasterPassword:(NSString *)password {
assert(!password || ![NSThread isMainThread]); // If we need to computing a key, this operation shouldn't be on the main thread.
MPKey *tryKey = nil; MPKey *tryKey = nil;
// Method 1: When the user has no keyID set, set a new key from the given master password. // Method 1: When the user has no keyID set, set a new key from the given master password.
if (!user.keyID) { if (!user.keyID) {
if ([password length]) if ([password length])
if ((tryKey = [MPAlgorithmDefault keyForPassword:password ofUserNamed:user.name])) { if ((tryKey = [MPAlgorithmDefault keyForPassword:password ofUserNamed:user.name])) {
user.keyID = tryKey.keyID; [user.managedObjectContext performBlockAndWait:^{
user.keyID = tryKey.keyID;
}];
// Migrate existing elements.
MPKey *recoverKey = nil;
PearlAlert *activityAlert = [PearlAlert showActivityWithTitle:PearlString(@"Migrating %d sites...", [user.elements count])];
for (MPElementEntity *element in user.elements) {
if (element.type & MPElementTypeClassStored && ![element contentUsingKey:tryKey]) {
id content = nil;
if (recoverKey)
content = [element contentUsingKey:recoverKey];
while (!content) {
__block NSString *masterPassword = nil;
dispatch_group_t recoverPasswordGroup = dispatch_group_create();
dispatch_group_enter(recoverPasswordGroup);
[PearlAlert showAlertWithTitle:@"Enter Old Master Password"
message:PearlString(@"Your old master password is required to migrate the stored password for %@", element.name)
viewStyle:UIAlertViewStyleSecureTextInput
initAlert:nil
tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
@try {
if (buttonIndex_ == [alert_ cancelButtonIndex])
// Don't Migrate
return;
masterPassword = [alert_ textFieldAtIndex:0].text;
}
@finally {
dispatch_group_leave(recoverPasswordGroup);
}
} cancelTitle:@"Don't Migrate" otherTitles:@"Migrate", nil];
dispatch_group_wait(recoverPasswordGroup, DISPATCH_TIME_FOREVER);
if (!masterPassword)
// Don't Migrate
break;
recoverKey = [element.algorithm keyForPassword:masterPassword ofUserNamed:user.name];
content = [element contentUsingKey:recoverKey];
}
if (!content)
// Don't Migrate
break;
[element.managedObjectContext performBlockAndWait:^{
[element setContent:content usingKey:tryKey];
}];
}
}
[[MPAppDelegate_Shared get] saveContext]; [[MPAppDelegate_Shared get] saveContext];
[activityAlert dismissAlert];
} }
} }
@ -152,9 +210,11 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
} }
user.lastUsed = [NSDate date]; [user.managedObjectContext performBlockAndWait:^{
self.activeUser = user; user.lastUsed = [NSDate date];
self.activeUser.requiresExplicitMigration = NO; self.activeUser = user;
self.activeUser.requiresExplicitMigration = NO;
}];
[[MPAppDelegate_Shared get] saveContext]; [[MPAppDelegate_Shared get] saveContext];
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationSignedIn object:self]; [[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationSignedIn object:self];

View File

@ -27,6 +27,9 @@
@property (assign) BOOL requiresExplicitMigration; @property (assign) BOOL requiresExplicitMigration;
@property (readonly) id<MPAlgorithm> algorithm; @property (readonly) id<MPAlgorithm> algorithm;
- (id)contentUsingKey:(MPKey *)key;
- (void)setContent:(id)content usingKey:(MPKey *)key;
- (NSUInteger)use; - (NSUInteger)use;
- (NSString *)exportContent; - (NSString *)exportContent;
- (void)importProtectedContent:(NSString *)protectedContent protectedByKey:(MPKey *)contentProtectionKey usingKey:(MPKey *)key2; - (void)importProtectedContent:(NSString *)protectedContent protectedByKey:(MPKey *)contentProtectionKey usingKey:(MPKey *)key2;

View File

@ -84,7 +84,32 @@
- (id)content { - (id)content {
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Content implementation missing." userInfo:nil]; MPKey *key = [MPAppDelegate get].key;
if (!key)
return nil;
assert([key.keyID isEqualToData:self.user.keyID]);
return [self contentUsingKey:key];
}
- (void)setContent:(id)content {
MPKey *key = [MPAppDelegate get].key;
if (!key)
return;
assert([key.keyID isEqualToData:self.user.keyID]);
[self setContent:content usingKey:key];
}
- (id)contentUsingKey:(MPKey *)key {
Throw(@"Content retrieval implementation missing for: %@", [self class]);
}
- (void)setContent:(id)content usingKey:(MPKey *)key {
Throw(@"Content assignment implementation missing for: %@", [self class]);
} }
- (NSString *)exportContent { - (NSString *)exportContent {
@ -139,11 +164,7 @@
self.counter_ = @(aCounter); self.counter_ = @(aCounter);
} }
- (id)content { - (id)contentUsingKey:(MPKey *)key {
MPKey *key = [MPAppDelegate get].key;
if (!key)
return nil;
if (!(self.type & MPElementTypeClassGenerated)) { if (!(self.type & MPElementTypeClassGenerated)) {
err(@"Corrupt element: %@, type: %d is not in MPElementTypeClassGenerated", self.name, self.type); err(@"Corrupt element: %@, type: %d is not in MPElementTypeClassGenerated", self.name, self.type);
@ -156,6 +177,7 @@
return [self.algorithm generateContentForElement:self usingKey:key]; return [self.algorithm generateContentForElement:self usingKey:key];
} }
@end @end
@implementation MPElementStoredEntity (MP) @implementation MPElementStoredEntity (MP)
@ -168,28 +190,9 @@
matches:nil]; matches:nil];
} }
- (id)content {
MPKey *key = [MPAppDelegate get].key;
if (!key)
return nil;
return [self contentUsingKey:key];
}
- (void)setContent:(id)content {
MPKey *key = [MPAppDelegate get].key;
if (!key)
return;
[self setContent:content usingKey:key];
}
- (id)contentUsingKey:(MPKey *)key { - (id)contentUsingKey:(MPKey *)key {
assert(self.type & MPElementTypeClassStored); assert(self.type & MPElementTypeClassStored);
assert([key.keyID isEqualToData:self.user.keyID]);
NSData *encryptedContent; NSData *encryptedContent;
if (self.type & MPElementFeatureDevicePrivate) if (self.type & MPElementFeatureDevicePrivate)
@ -201,6 +204,9 @@
if ([encryptedContent length]) if ([encryptedContent length])
decryptedContent = [self decryptContent:encryptedContent usingKey:key]; decryptedContent = [self decryptContent:encryptedContent usingKey:key];
if (!decryptedContent)
return nil;
return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding]; return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
} }

View File

@ -241,15 +241,7 @@
if (!importedSitesData) if (!importedSitesData)
return; return;
PearlAlert *activityAlert = [PearlAlert showAlertWithTitle:@"Importing" message:@"\n\n" PearlAlert *activityAlert = [PearlAlert showActivityWithTitle:@"Importing"];
viewStyle:UIAlertViewStyleDefault initAlert:
^(UIAlertView *alert, UITextField *firstField) {
UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
activityIndicator.center = CGPointMake(140, 90);
[activityIndicator startAnimating];
[alert addSubview:activityIndicator];
}
tappedButtonBlock:nil cancelTitle:nil otherTitles:nil];
NSString *importedSitesString = [[NSString alloc] initWithData:importedSitesData encoding:NSUTF8StringEncoding]; NSString *importedSitesString = [[NSString alloc] initWithData:importedSitesData encoding:NSUTF8StringEncoding];
MPImportResult result = [self importSites:importedSitesString askImportPassword:^NSString *(NSString *userName) { MPImportResult result = [self importSites:importedSitesString askImportPassword:^NSString *(NSString *userName) {

View File

@ -96,9 +96,7 @@
viewControllerAfterViewController:(UIViewController *)viewController { viewControllerAfterViewController:(UIViewController *)viewController {
NSUInteger vcIndex = [self.pageVCs indexOfObject:viewController]; NSUInteger vcIndex = [self.pageVCs indexOfObject:viewController];
UIPageViewController *vc = [self.pageVCs objectAtIndex:(vcIndex + 1) % self.pageVCs.count]; return [self.pageVCs objectAtIndex:(vcIndex + 1) % self.pageVCs.count];
return vc;
} }
- (void)viewDidUnload { - (void)viewDidUnload {

View File

@ -1167,11 +1167,11 @@ Pink fluffy door frame.</string>
<placeholder placeholderIdentifier="IBFirstResponder" id="hkm-U7-Dm7" userLabel="First Responder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="hkm-U7-Dm7" userLabel="First Responder" sceneMemberID="firstResponder"/>
<viewController storyboardIdentifier="MPAppViewController_0" id="yIx-9U-bOF" customClass="MPAppViewController" sceneMemberID="viewController"> <viewController storyboardIdentifier="MPAppViewController_0" id="yIx-9U-bOF" customClass="MPAppViewController" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Yea-s2-C8N"> <view key="view" contentMode="scaleToFill" id="Yea-s2-C8N">
<rect key="frame" x="0.0" y="0.0" width="305" height="399"/> <rect key="frame" x="0.0" y="0.0" width="305" height="400"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="Wrz-tq-o1S"> <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="Wrz-tq-o1S">
<rect key="frame" x="0.0" y="0.0" width="305" height="399"/> <rect key="frame" x="0.0" y="0.0" width="305" height="400"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<state key="normal" image="page-gorillas.png"> <state key="normal" image="page-gorillas.png">
@ -1186,7 +1186,7 @@ Pink fluffy door frame.</string>
</connections> </connections>
</button> </button>
<view contentMode="scaleToFill" id="JN1-cA-6yZ"> <view contentMode="scaleToFill" id="JN1-cA-6yZ">
<rect key="frame" x="0.0" y="330" width="305" height="60"/> <rect key="frame" x="0.0" y="331" width="305" height="60"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<subviews> <subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Gorillas At Large On Metropolis Rooftops" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" id="Ypg-Yc-UK3"> <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Gorillas At Large On Metropolis Rooftops" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" id="Ypg-Yc-UK3">
@ -1632,7 +1632,7 @@ You could use the word wall for inspiration in finding a memorable master passw
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</imageView> </imageView>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="page-gorillas.png" id="QQT-37-azo"> <imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="page-gorillas.png" id="QQT-37-azo">
<rect key="frame" x="0.0" y="38" width="305" height="399"/> <rect key="frame" x="0.0" y="38" width="305" height="400"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</imageView> </imageView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="drq-47-KK9"> <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="drq-47-KK9">
@ -2021,11 +2021,11 @@ You could use the word wall for inspiration in finding a memorable master passw
<placeholder placeholderIdentifier="IBFirstResponder" id="LHv-Mk-8Kp" userLabel="First Responder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="LHv-Mk-8Kp" userLabel="First Responder" sceneMemberID="firstResponder"/>
<viewController storyboardIdentifier="MPAppViewController_1" id="vOg-Xq-hKm" customClass="MPAppViewController" sceneMemberID="viewController"> <viewController storyboardIdentifier="MPAppViewController_1" id="vOg-Xq-hKm" customClass="MPAppViewController" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="jzQ-Oa-Gdj"> <view key="view" contentMode="scaleToFill" id="jzQ-Oa-Gdj">
<rect key="frame" x="0.0" y="0.0" width="305" height="399"/> <rect key="frame" x="0.0" y="0.0" width="305" height="400"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="x6i-3e-0Rb"> <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="x6i-3e-0Rb">
<rect key="frame" x="0.0" y="0.0" width="305" height="399"/> <rect key="frame" x="0.0" y="0.0" width="305" height="400"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<state key="normal" image="page-deblock.png"> <state key="normal" image="page-deblock.png">
@ -2040,7 +2040,7 @@ You could use the word wall for inspiration in finding a memorable master passw
</connections> </connections>
</button> </button>
<view contentMode="scaleToFill" id="AC5-4y-ftd"> <view contentMode="scaleToFill" id="AC5-4y-ftd">
<rect key="frame" x="0.0" y="330" width="305" height="60"/> <rect key="frame" x="0.0" y="331" width="305" height="60"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<subviews> <subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Block Destruction Puzzle" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" id="11H-1K-20G"> <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Block Destruction Puzzle" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" id="11H-1K-20G">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 410 KiB

After

Width:  |  Height:  |  Size: 411 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 597 KiB

After

Width:  |  Height:  |  Size: 598 KiB