2017-04-05 20:56:22 +00:00
|
|
|
//==============================================================================
|
|
|
|
// This file is part of Master Password.
|
|
|
|
// Copyright (c) 2011-2017, Maarten Billemont.
|
2012-03-05 21:19:05 +00:00
|
|
|
//
|
2017-04-05 20:56:22 +00:00
|
|
|
// Master Password is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
2012-03-05 21:19:05 +00:00
|
|
|
//
|
2017-04-05 20:56:22 +00:00
|
|
|
// Master Password is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
2012-03-05 21:19:05 +00:00
|
|
|
//
|
2017-04-05 20:56:22 +00:00
|
|
|
// You can find a copy of the GNU General Public License in the
|
|
|
|
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
|
|
|
//==============================================================================
|
2012-03-05 21:19:05 +00:00
|
|
|
|
2012-05-11 20:45:05 +00:00
|
|
|
#import "MPAppDelegate_Key.h"
|
2012-06-05 22:59:09 +00:00
|
|
|
#import "MPAppDelegate_Store.h"
|
2012-03-05 21:19:05 +00:00
|
|
|
|
2014-09-21 14:29:18 +00:00
|
|
|
@interface MPAppDelegate_Shared()
|
|
|
|
|
|
|
|
@property(strong, nonatomic) MPKey *key;
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
2013-04-20 18:11:19 +00:00
|
|
|
@implementation MPAppDelegate_Shared(Key)
|
2012-03-05 21:19:05 +00:00
|
|
|
|
2016-01-14 14:58:04 +00:00
|
|
|
static NSDictionary *createKeyQuery(MPUserEntity *user, BOOL newItem, MPKeyOrigin *keyOrigin) {
|
|
|
|
|
2016-01-28 02:38:36 +00:00
|
|
|
#if TARGET_OS_IPHONE
|
2016-08-17 22:34:32 +00:00
|
|
|
if (user.touchID && kSecUseAuthenticationUI) {
|
2016-01-14 14:58:04 +00:00
|
|
|
if (keyOrigin)
|
|
|
|
*keyOrigin = MPKeyOriginKeyChainBiometric;
|
2016-01-14 07:14:36 +00:00
|
|
|
|
|
|
|
CFErrorRef acError = NULL;
|
2016-07-19 16:00:19 +00:00
|
|
|
id accessControl = (__bridge_transfer id)SecAccessControlCreateWithFlags( kCFAllocatorDefault,
|
2016-01-14 07:14:36 +00:00
|
|
|
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, kSecAccessControlTouchIDCurrentSet, &acError );
|
|
|
|
if (!accessControl || acError)
|
|
|
|
err( @"Could not use TouchID on this device: %@", acError );
|
|
|
|
|
2016-01-14 14:58:04 +00:00
|
|
|
else
|
|
|
|
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
|
|
|
attributes:@{
|
|
|
|
(__bridge id)kSecAttrService : @"Saved Master Password",
|
|
|
|
(__bridge id)kSecAttrAccount : user.name?: @"",
|
2016-07-19 16:00:19 +00:00
|
|
|
(__bridge id)kSecAttrAccessControl : accessControl,
|
2016-01-14 14:58:04 +00:00
|
|
|
(__bridge id)kSecUseAuthenticationUI : (__bridge id)kSecUseAuthenticationUIAllow,
|
|
|
|
(__bridge id)kSecUseOperationPrompt :
|
|
|
|
strf( @"Access %@'s master password.", user.name ),
|
|
|
|
}
|
|
|
|
matches:nil];
|
2016-01-14 07:14:36 +00:00
|
|
|
}
|
2016-01-28 02:38:36 +00:00
|
|
|
#endif
|
2012-06-07 22:40:30 +00:00
|
|
|
|
2016-01-14 14:58:04 +00:00
|
|
|
if (keyOrigin)
|
|
|
|
*keyOrigin = MPKeyOriginKeyChain;
|
|
|
|
|
2012-06-04 09:27:02 +00:00
|
|
|
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
2012-09-20 19:16:34 +00:00
|
|
|
attributes:@{
|
2017-04-01 04:30:25 +00:00
|
|
|
(__bridge id)kSecAttrService: @"Saved Master Password",
|
|
|
|
(__bridge id)kSecAttrAccount: user.name?: @"",
|
2016-01-14 14:58:04 +00:00
|
|
|
#if TARGET_OS_IPHONE
|
|
|
|
(__bridge id)kSecAttrAccessible : (__bridge id)(kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly?: kSecAttrAccessibleWhenUnlockedThisDeviceOnly),
|
|
|
|
#endif
|
2012-09-20 19:16:34 +00:00
|
|
|
}
|
|
|
|
matches:nil];
|
2012-03-05 21:19:05 +00:00
|
|
|
}
|
|
|
|
|
2012-07-17 20:57:11 +00:00
|
|
|
- (MPKey *)loadSavedKeyFor:(MPUserEntity *)user {
|
2012-06-07 22:40:30 +00:00
|
|
|
|
2016-01-14 14:58:04 +00:00
|
|
|
MPKeyOrigin keyOrigin;
|
|
|
|
NSDictionary *keyQuery = createKeyQuery( user, NO, &keyOrigin );
|
2017-04-15 06:28:11 +00:00
|
|
|
id<MPAlgorithm> keyAlgorithm = user.algorithm;
|
|
|
|
MPKey *key = [[MPKey alloc] initForFullName:user.name withKeyResolver:^NSData *(id<MPAlgorithm> algorithm) {
|
|
|
|
return ![algorithm isEqual:keyAlgorithm]? nil:
|
|
|
|
PearlMainQueueAwait( (id)^{
|
|
|
|
return [PearlKeyChain dataOfItemForQuery:keyQuery];
|
|
|
|
} );
|
|
|
|
} keyOrigin:keyOrigin];
|
|
|
|
|
|
|
|
if ([key keyIDForAlgorithm:user.algorithm])
|
|
|
|
inf( @"Found key in keychain for user: %@", user.userID );
|
2017-04-18 02:27:36 +00:00
|
|
|
|
2017-04-18 02:13:01 +00:00
|
|
|
else {
|
2015-02-28 15:01:41 +00:00
|
|
|
inf( @"No key found in keychain for user: %@", user.userID );
|
2017-04-18 02:13:01 +00:00
|
|
|
key = nil;
|
|
|
|
}
|
2012-06-07 22:40:30 +00:00
|
|
|
|
2017-04-15 06:28:11 +00:00
|
|
|
return key;
|
2012-03-05 21:19:05 +00:00
|
|
|
}
|
|
|
|
|
2012-06-07 22:40:30 +00:00
|
|
|
- (void)storeSavedKeyFor:(MPUserEntity *)user {
|
2012-05-13 17:50:40 +00:00
|
|
|
|
2012-06-07 22:40:30 +00:00
|
|
|
if (user.saveKey) {
|
2016-07-23 15:44:17 +00:00
|
|
|
NSData *keyData = [self.key keyDataForAlgorithm:user.algorithm];
|
|
|
|
if (keyData) {
|
|
|
|
[self forgetSavedKeyFor:user];
|
|
|
|
|
|
|
|
inf( @"Saving key in keychain for user: %@", user.userID );
|
|
|
|
[PearlKeyChain addOrUpdateItemForQuery:createKeyQuery( user, YES, nil )
|
2017-04-01 04:30:25 +00:00
|
|
|
withAttributes:@{ (__bridge id)kSecValueData: keyData }];
|
2016-07-23 15:44:17 +00:00
|
|
|
}
|
2012-03-05 21:19:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-06-07 22:40:30 +00:00
|
|
|
- (void)forgetSavedKeyFor:(MPUserEntity *)user {
|
|
|
|
|
2016-01-14 14:58:04 +00:00
|
|
|
OSStatus result = [PearlKeyChain deleteItemForQuery:createKeyQuery( user, NO, nil )];
|
2013-05-16 02:42:21 +00:00
|
|
|
if (result == noErr) {
|
2015-02-28 15:01:41 +00:00
|
|
|
inf( @"Removed key from keychain for user: %@", user.userID );
|
2012-06-07 22:40:30 +00:00
|
|
|
|
2013-05-16 02:42:21 +00:00
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:MPKeyForgottenNotification object:self];
|
2012-06-05 22:59:09 +00:00
|
|
|
}
|
2012-06-07 22:40:30 +00:00
|
|
|
}
|
|
|
|
|
2012-06-27 22:00:41 +00:00
|
|
|
- (void)signOutAnimated:(BOOL)animated {
|
2012-06-07 22:40:30 +00:00
|
|
|
|
2012-06-15 09:16:02 +00:00
|
|
|
if (self.key)
|
|
|
|
self.key = nil;
|
2012-06-07 22:40:30 +00:00
|
|
|
|
2016-07-06 00:15:18 +00:00
|
|
|
self.activeUser = nil;
|
2017-04-01 04:30:25 +00:00
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:MPSignedOutNotification object:self userInfo:@{ @"animated": @(animated) }];
|
2012-06-07 22:40:30 +00:00
|
|
|
}
|
|
|
|
|
2013-04-30 05:49:53 +00:00
|
|
|
- (BOOL)signInAsUser:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc usingMasterPassword:(NSString *)password {
|
2013-04-28 04:33:28 +00:00
|
|
|
|
2016-01-14 14:58:04 +00:00
|
|
|
NSAssert( ![NSThread isMainThread], @"Authentication should not happen on the main thread." );
|
2014-02-22 23:27:14 +00:00
|
|
|
if (!user)
|
|
|
|
return NO;
|
2012-06-07 22:40:30 +00:00
|
|
|
|
2012-07-17 20:57:11 +00:00
|
|
|
MPKey *tryKey = nil;
|
2012-06-07 22:40:30 +00:00
|
|
|
|
|
|
|
// Method 1: When the user has no keyID set, set a new key from the given master password.
|
|
|
|
if (!user.keyID) {
|
2015-02-28 15:01:41 +00:00
|
|
|
if ([password length] && (tryKey = [[MPKey alloc] initForFullName:user.name withMasterPassword:password])) {
|
|
|
|
user.keyID = [tryKey keyIDForAlgorithm:MPAlgorithmDefault];
|
2012-09-01 20:14:57 +00:00
|
|
|
|
2014-09-21 15:47:53 +00:00
|
|
|
// Migrate existing sites.
|
|
|
|
[self migrateSitesForUser:user saveInContext:moc toKey:tryKey];
|
2013-04-20 18:11:19 +00:00
|
|
|
}
|
2012-06-07 22:40:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Method 2: Depending on the user's saveKey, load or remove the key from the keychain.
|
|
|
|
if (!user.saveKey)
|
2014-04-26 18:03:44 +00:00
|
|
|
// Key should not be stored in keychain. Delete it.
|
2012-06-07 22:40:30 +00:00
|
|
|
[self forgetSavedKeyFor:user];
|
|
|
|
|
2013-04-20 18:11:19 +00:00
|
|
|
else if (!tryKey) {
|
|
|
|
// Key should be saved in keychain. Load it.
|
2015-02-28 15:01:41 +00:00
|
|
|
if ((tryKey = [self loadSavedKeyFor:user]) && ![user.keyID isEqual:[tryKey keyIDForAlgorithm:user.algorithm]]) {
|
2013-04-20 18:11:19 +00:00
|
|
|
// Loaded password doesn't match user's keyID. Forget saved password: it is incorrect.
|
2015-02-28 15:01:41 +00:00
|
|
|
inf( @"Saved password doesn't match keyID for user: %@", user.userID );
|
|
|
|
trc( @"user keyID: %@ (version: %d) != authentication keyID: %@",
|
|
|
|
user.keyID, user.algorithm.version, [tryKey keyIDForAlgorithm:user.algorithm] );
|
2013-04-20 18:11:19 +00:00
|
|
|
|
|
|
|
tryKey = nil;
|
|
|
|
[self forgetSavedKeyFor:user];
|
2012-06-08 21:46:13 +00:00
|
|
|
}
|
2013-04-20 18:11:19 +00:00
|
|
|
}
|
2012-06-07 22:40:30 +00:00
|
|
|
|
|
|
|
// Method 3: Check the given master password string.
|
2015-02-28 15:01:41 +00:00
|
|
|
if (!tryKey && [password length] && (tryKey = [[MPKey alloc] initForFullName:user.name withMasterPassword:password]) &&
|
|
|
|
![user.keyID isEqual:[tryKey keyIDForAlgorithm:user.algorithm]]) {
|
|
|
|
inf( @"Key derived from password doesn't match keyID for user: %@", user.userID );
|
|
|
|
trc( @"user keyID: %@ (version: %u) != authentication keyID: %@",
|
|
|
|
user.keyID, user.algorithm.version, [tryKey keyIDForAlgorithm:user.algorithm] );
|
2012-06-07 22:40:30 +00:00
|
|
|
|
2014-06-02 02:45:29 +00:00
|
|
|
tryKey = nil;
|
2012-06-07 22:40:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// No more methods left, fail if key still not known.
|
2012-06-14 19:56:54 +00:00
|
|
|
if (!tryKey) {
|
2014-10-23 03:00:20 +00:00
|
|
|
if (password)
|
2015-02-28 15:01:41 +00:00
|
|
|
inf( @"Password login failed for user: %@", user.userID );
|
|
|
|
else
|
|
|
|
dbg( @"Automatic login failed for user: %@", user.userID );
|
2012-06-14 19:56:54 +00:00
|
|
|
|
2012-06-07 22:40:30 +00:00
|
|
|
return NO;
|
2012-06-14 19:56:54 +00:00
|
|
|
}
|
2015-02-28 15:01:41 +00:00
|
|
|
inf( @"Logged in user: %@", user.userID );
|
2012-06-07 22:40:30 +00:00
|
|
|
|
2012-07-17 20:57:11 +00:00
|
|
|
if (![self.key isEqualToKey:tryKey]) {
|
2015-02-28 15:01:41 +00:00
|
|
|
// Upgrade the user's keyID if not at the default version yet.
|
|
|
|
if (user.algorithm.version != MPAlgorithmDefaultVersion) {
|
|
|
|
user.algorithm = MPAlgorithmDefault;
|
|
|
|
user.keyID = [tryKey keyIDForAlgorithm:user.algorithm];
|
|
|
|
inf( @"Upgraded keyID to version %u for user: %@", user.algorithm.version, user.userID );
|
|
|
|
}
|
|
|
|
|
2012-06-04 09:27:02 +00:00
|
|
|
self.key = tryKey;
|
2016-01-14 14:58:04 +00:00
|
|
|
|
|
|
|
// Update the key chain if necessary.
|
2016-07-06 00:15:18 +00:00
|
|
|
[self storeSavedKeyFor:user];
|
2012-06-04 09:27:02 +00:00
|
|
|
}
|
2012-06-07 22:40:30 +00:00
|
|
|
|
2012-06-10 06:21:41 +00:00
|
|
|
@try {
|
2012-10-31 02:54:34 +00:00
|
|
|
if ([[MPConfig get].sendInfo boolValue]) {
|
|
|
|
#ifdef CRASHLYTICS
|
2015-10-22 18:31:39 +00:00
|
|
|
[[Crashlytics sharedInstance] setObjectValue:user.userID forKey:@"username"];
|
|
|
|
[[Crashlytics sharedInstance] setUserName:user.userID];
|
2012-10-31 02:54:34 +00:00
|
|
|
#endif
|
2012-06-10 06:21:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
@catch (id exception) {
|
2014-04-26 18:03:44 +00:00
|
|
|
err( @"While setting username: %@", exception );
|
2012-06-10 06:21:41 +00:00
|
|
|
}
|
|
|
|
|
2013-01-31 05:42:32 +00:00
|
|
|
user.lastUsed = [NSDate date];
|
|
|
|
self.activeUser = user;
|
2014-09-08 04:15:18 +00:00
|
|
|
[moc saveToStore];
|
2012-06-10 06:21:41 +00:00
|
|
|
|
2014-04-26 18:03:44 +00:00
|
|
|
// Perform a data sanity check now that we're logged in as the user to allow fixes that require the user's key.
|
|
|
|
if ([[MPConfig get].checkInconsistency boolValue])
|
|
|
|
[MPAppDelegate_Shared managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
|
|
|
|
[self findAndFixInconsistenciesSaveInContext:context];
|
|
|
|
}];
|
|
|
|
|
2013-01-31 05:42:32 +00:00
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:MPSignedInNotification object:self];
|
2012-03-05 21:19:05 +00:00
|
|
|
|
2012-06-07 22:40:30 +00:00
|
|
|
return YES;
|
2012-06-04 09:27:02 +00:00
|
|
|
}
|
|
|
|
|
2014-09-21 15:47:53 +00:00
|
|
|
- (void)migrateSitesForUser:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc toKey:(MPKey *)newKey {
|
2013-04-13 18:03:50 +00:00
|
|
|
|
2014-09-21 15:47:53 +00:00
|
|
|
if (![user.sites count])
|
2014-04-26 18:03:44 +00:00
|
|
|
// Nothing to migrate.
|
2013-04-13 18:03:50 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
MPKey *recoverKey = newKey;
|
|
|
|
#ifdef PEARL_UIKIT
|
2014-04-26 18:03:44 +00:00
|
|
|
PearlOverlay *activityOverlay = [PearlOverlay showProgressOverlayWithTitle:PearlString( @"Migrating %ld sites...",
|
2014-09-21 15:47:53 +00:00
|
|
|
(long)[user.sites count] )];
|
2013-04-13 18:03:50 +00:00
|
|
|
#endif
|
|
|
|
|
2014-09-21 15:47:53 +00:00
|
|
|
for (MPSiteEntity *site in user.sites) {
|
|
|
|
if (site.type & MPSiteTypeClassStored) {
|
2013-09-13 12:14:58 +00:00
|
|
|
NSString *content;
|
2014-09-21 15:47:53 +00:00
|
|
|
while (!(content = [site.algorithm storedPasswordForSite:(MPStoredSiteEntity *)site usingKey:recoverKey])) {
|
|
|
|
// Failed to decrypt site with the current recoveryKey. Ask user for a new one to use.
|
2017-04-15 06:28:11 +00:00
|
|
|
NSString *masterPassword = nil;
|
2013-04-13 18:03:50 +00:00
|
|
|
|
|
|
|
#ifdef PEARL_UIKIT
|
2017-04-15 06:28:11 +00:00
|
|
|
masterPassword = PearlAwait( ^(void (^setResult)(id)) {
|
|
|
|
[PearlAlert showAlertWithTitle:@"Enter Old Master Password"
|
|
|
|
message:PearlString(
|
|
|
|
@"Your old master password is required to migrate the stored password for %@",
|
|
|
|
site.name )
|
|
|
|
viewStyle:UIAlertViewStyleSecureTextInput
|
|
|
|
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
|
2016-01-14 07:14:36 +00:00
|
|
|
if (buttonIndex_ == [alert_ cancelButtonIndex])
|
2017-04-15 06:28:11 +00:00
|
|
|
setResult( nil );
|
|
|
|
else
|
|
|
|
setResult( [alert_ textFieldAtIndex:0].text );
|
|
|
|
} cancelTitle:@"Don't Migrate" otherTitles:@"Migrate", nil];
|
|
|
|
} );
|
2013-04-13 18:03:50 +00:00
|
|
|
#endif
|
|
|
|
if (!masterPassword)
|
2014-04-26 18:03:44 +00:00
|
|
|
// Don't Migrate
|
2013-04-13 18:03:50 +00:00
|
|
|
break;
|
|
|
|
|
2015-02-28 15:01:41 +00:00
|
|
|
recoverKey = [[MPKey alloc] initForFullName:user.name withMasterPassword:masterPassword];
|
2013-04-13 18:03:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!content)
|
2014-04-26 18:03:44 +00:00
|
|
|
// Don't Migrate
|
2013-04-13 18:03:50 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
if (![recoverKey isEqualToKey:newKey])
|
2014-09-21 15:47:53 +00:00
|
|
|
[site.algorithm savePassword:content toSite:site usingKey:newKey];
|
2013-04-13 18:03:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
[moc saveToStore];
|
|
|
|
|
|
|
|
#ifdef PEARL_UIKIT
|
2013-10-31 03:37:12 +00:00
|
|
|
[activityOverlay cancelOverlayAnimated:YES];
|
2013-04-13 18:03:50 +00:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2012-03-05 21:19:05 +00:00
|
|
|
@end
|