diff --git a/platform-darwin/Source/MPAlgorithm.h b/platform-darwin/Source/MPAlgorithm.h index db8e674c..a2ea9378 100644 --- a/platform-darwin/Source/MPAlgorithm.h +++ b/platform-darwin/Source/MPAlgorithm.h @@ -87,7 +87,10 @@ NSString *NSStringFromTimeToCrack(TimeToCrack timeToCrack); - (void)importPassword:(NSString *)protectedPassword protectedByKey:(MPKey *)importKey intoSite:(MPSiteEntity *)site usingKey:(MPKey *)key; +- (void)importLogin:(NSString *)protectedLogin protectedByKey:(MPKey *)importKey + intoSite:(MPSiteEntity *)site usingKey:(MPKey *)key; - (NSString *)exportPasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)key; +- (NSString *)exportLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)key; - (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPResultType)type byAttacker:(MPAttacker)attacker; - (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordString:(NSString *)password byAttacker:(MPAttacker)attacker; diff --git a/platform-darwin/Source/MPAlgorithmV0.m b/platform-darwin/Source/MPAlgorithmV0.m index c850a7b5..e2e1bf52 100644 --- a/platform-darwin/Source/MPAlgorithmV0.m +++ b/platform-darwin/Source/MPAlgorithmV0.m @@ -73,7 +73,7 @@ static NSOperationQueue *_mpwQueue = nil; return [(id)other version] == [self version]; } -- (void)mpw_perform:(void ( ^ )(void))operationBlock { +- (void)mpw_await:(void ( ^ )(void))operationBlock { if ([NSOperationQueue currentQueue] == _mpwQueue) { operationBlock(); @@ -126,7 +126,7 @@ static NSOperationQueue *_mpwQueue = nil; - (NSData *)keyDataForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword { __block NSData *keyData; - [self mpw_perform:^{ + [self mpw_await:^{ NSDate *start = [NSDate date]; MPMasterKey masterKey = mpw_master_key( fullName.UTF8String, masterPassword.UTF8String, [self version] ); if (masterKey) { @@ -363,7 +363,7 @@ static NSOperationQueue *_mpwQueue = nil; usingKey:(MPKey *)key { __block NSString *result = nil; - [self mpw_perform:^{ + [self mpw_await:^{ NSData *masterKey = [key keyForAlgorithm:self]; char const *resultBytes = mpw_site_result( masterKey.bytes, name.UTF8String, counter, purpose, context.UTF8String, type, parameter.UTF8String, [self version] ); @@ -392,7 +392,7 @@ static NSOperationQueue *_mpwQueue = nil; __block NSData *state = nil; if (plainText) - [self mpw_perform:^{ + [self mpw_await:^{ NSData *masterKey = [key keyForAlgorithm:self]; char const *stateBytes = mpw_site_state( masterKey.bytes, site.name.UTF8String, MPCounterValueInitial, MPKeyPurposeAuthentication, NULL, site.type, plainText.UTF8String, [self version] ); @@ -589,6 +589,19 @@ static NSOperationQueue *_mpwQueue = nil; } } +- (void)importLogin:(NSString *)cipherText protectedByKey:(MPKey *)importKey + intoSite:(MPSiteEntity *)site usingKey:(MPKey *)key { + + NSAssert( [[key keyIDForAlgorithm:site.user.algorithm] isEqualToData:site.user.keyID], @"Site does not belong to current user." ); + if (cipherText && cipherText.length) { + NSString *plainText = [self mpwResultForSiteNamed:site.name ofType:MPResultTypeStatefulPersonal parameter:cipherText + withCounter:MPCounterValueInitial variant:MPKeyPurposeIdentification context:nil + usingKey:importKey]; + if (plainText) + site.loginName = plainText; + } +} + - (NSDictionary *)queryForSite:(MPSiteEntity *)site { return [PearlKeyChain createQueryForClass:kSecClassGenericPassword attributes:@{ @@ -607,6 +620,26 @@ static NSOperationQueue *_mpwQueue = nil; return [state?: ((MPStoredSiteEntity *)site).contentObject encodeBase64]; } +- (NSString *)exportLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)key { + + if (!(site.type & MPSiteFeatureExportContent) || site.loginGenerated || ![site.loginName length]) + return nil; + + __block NSData *state = nil; + [self mpw_await:^{ + NSData *masterKey = [key keyForAlgorithm:self]; + char const *stateBytes = mpw_site_state( masterKey.bytes, site.name.UTF8String, + MPCounterValueInitial, MPKeyPurposeIdentification, NULL, MPResultTypeStatefulPersonal, + site.loginName.UTF8String, [self version] ); + if (stateBytes) { + state = [[NSString stringWithCString:stateBytes encoding:NSUTF8StringEncoding] decodeBase64]; + mpw_free_string( &stateBytes ); + } + }]; + + return [state encodeBase64]; +} + - (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPResultType)type byAttacker:(MPAttacker)attacker { if (!(type & MPResultTypeClassTemplate)) diff --git a/platform-darwin/Source/MPAppDelegate_Store.h b/platform-darwin/Source/MPAppDelegate_Store.h index 408cc6ff..a2147596 100644 --- a/platform-darwin/Source/MPAppDelegate_Store.h +++ b/platform-darwin/Source/MPAppDelegate_Store.h @@ -41,6 +41,6 @@ result:(void ( ^ )(NSError *error))resultBlock; - (void)exportSitesRevealPasswords:(BOOL)revealPasswords askExportPassword:(NSString *( ^ )(NSString *userName))askImportPassword - result:(void ( ^ )(NSString *mpsites, NSError *error))resultBlock; + result:(void ( ^ )(NSString *exportedUser, NSError *error))resultBlock; @end diff --git a/platform-darwin/Source/MPAppDelegate_Store.m b/platform-darwin/Source/MPAppDelegate_Store.m index 74a780c9..c01a29cc 100644 --- a/platform-darwin/Source/MPAppDelegate_Store.m +++ b/platform-darwin/Source/MPAppDelegate_Store.m @@ -700,14 +700,16 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted ); usingKey:(MPKey *)userKey { site.name = @(importSite->siteName); - if (importSite->resultState) - [site.algorithm importPassword:@(importSite->resultState) protectedByKey:importKey intoSite:site usingKey:userKey]; - site.type = importSite->resultType; + site.algorithm = MPAlgorithmForVersion( importSite->algorithm ); if ([site isKindOfClass:[MPGeneratedSiteEntity class]]) ((MPGeneratedSiteEntity *)site).counter = importSite->counter; - site.algorithm = MPAlgorithmForVersion( importSite->algorithm ); - site.loginName = importSite->loginState? @(importSite->loginState): nil; - site.loginGenerated = importSite->loginType & MPResultTypeClassTemplate; + site.type = importSite->resultType; + if (importSite->resultState) + [site.algorithm importPassword:@(importSite->resultState) protectedByKey:importKey intoSite:site usingKey:userKey]; + site.loginGenerated = (importSite->loginType & MPResultTypeClassTemplate) == MPResultTypeClassTemplate; + site.loginName = nil; + if (importSite->loginState) + [site.algorithm importLogin:@(importSite->loginState) protectedByKey:importKey intoSite:site usingKey:userKey]; site.url = importSite->url? @(importSite->url): nil; site.uses = importSite->uses; site.lastUsed = [NSDate dateWithTimeIntervalSince1970:importSite->lastUsed]; @@ -715,7 +717,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted ); - (void)exportSitesRevealPasswords:(BOOL)revealPasswords askExportPassword:(NSString *( ^ )(NSString *userName))askImportPassword - result:(void ( ^ )(NSString *mpsites, NSError *error))resultBlock { + result:(void ( ^ )(NSString *exportedUser, NSError *error))resultBlock { [MPAppDelegate_Shared managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { MPUserEntity *user = [self activeUserInContext:context]; @@ -725,6 +727,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted ); mpw_masterKeyProvider_str( askImportPassword( user.name ).UTF8String ), user.algorithm.version ); exportUser->redacted = !revealPasswords; exportUser->avatar = (unsigned int)user.avatar; + exportUser->keyID = mpw_strdup( [user.keyID encodeHex].UTF8String ); exportUser->defaultType = user.defaultType; exportUser->lastUsed = (time_t)user.lastUsed.timeIntervalSince1970; @@ -732,16 +735,12 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted ); MPCounterValue counter = MPCounterValueInitial; if ([site isKindOfClass:[MPGeneratedSiteEntity class]]) counter = ((MPGeneratedSiteEntity *)site).counter; - NSString *content = revealPasswords - ? [site.algorithm exportPasswordForSite:site usingKey:self.key] - : [site.algorithm resolvePasswordForSite:site usingKey:self.key]; - MPMarshalledSite *exportSite = mpw_marshal_site( exportUser, site.name.UTF8String, site.type, counter, site.algorithm.version ); - exportSite->resultState = content.UTF8String; - exportSite->loginState = site.loginName.UTF8String; + exportSite->resultState = mpw_strdup( [site.algorithm exportPasswordForSite:site usingKey:self.key].UTF8String ); + exportSite->loginState = mpw_strdup( [site.algorithm exportLoginForSite:site usingKey:self.key].UTF8String ); exportSite->loginType = site.loginGenerated? MPResultTypeTemplateName: MPResultTypeStatefulPersonal; - exportSite->url = site.url.UTF8String; + exportSite->url = mpw_strdup( site.url.UTF8String ); exportSite->uses = (unsigned int)site.uses; exportSite->lastUsed = (time_t)site.lastUsed.timeIntervalSince1970; @@ -750,14 +749,14 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted ); } MPMarshalledFile *exportFile = NULL; - const char *export = mpw_marshal_write( MPMarshalFormatFlat, &exportFile, exportUser ); - NSString *mpsites = nil; + const char *export = mpw_marshal_write( MPMarshalFormatDefault, &exportFile, exportUser ); + NSString *exportedUser = nil; if (export && exportFile && exportFile->error.type == MPMarshalSuccess) - mpsites = [NSString stringWithCString:export encoding:NSUTF8StringEncoding]; + exportedUser = [NSString stringWithCString:export encoding:NSUTF8StringEncoding]; mpw_free_string( &export ); - resultBlock( mpsites, exportFile && exportFile->error.type == MPMarshalSuccess? nil: - [NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{ + resultBlock( exportedUser, exportFile && exportFile->error.type == MPMarshalSuccess? nil: + [NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{ @"type" : @(exportFile? exportFile->error.type: MPMarshalErrorInternal), NSLocalizedDescriptionKey: @(exportFile? exportFile->error.message: nil), }] ); diff --git a/platform-darwin/Source/MasterPassword-Prefix.pch b/platform-darwin/Source/MasterPassword-Prefix.pch index e58497e2..fa516e31 100644 --- a/platform-darwin/Source/MasterPassword-Prefix.pch +++ b/platform-darwin/Source/MasterPassword-Prefix.pch @@ -28,41 +28,11 @@ MP_LIBS_BEGIN #include MP_LIBS_END -#define mpw_log_os(level, file, line, function, format, ...) \ - do { \ - if (mpw_verbosity < level) { \ - break; \ - } \ - \ - switch (level) { \ - case LogLevelTrace: \ - os_log_debug( OS_LOG_DEFAULT, "%30s:%-3ld TRC | " format, basename( (char *)file ), line, ##__VA_ARGS__ ); \ - break; \ - case LogLevelDebug: \ - os_log_debug( OS_LOG_DEFAULT, "%30s:%-3ld DBG | " format, basename( (char *)file ), line, ##__VA_ARGS__ ); \ - break; \ - case LogLevelInfo: \ - os_log_info( OS_LOG_DEFAULT, "%30s:%-3ld INF | " format, basename( (char *)file ), line, ##__VA_ARGS__ ); \ - break; \ - case LogLevelWarning: \ - os_log( OS_LOG_DEFAULT, "%30s:%-3ld WRN | " format, basename( (char *)file ), line, ##__VA_ARGS__ ); \ - break; \ - case LogLevelError: \ - os_log_error( OS_LOG_DEFAULT, "%30s:%-3ld ERR | " format, basename( (char *)file ), line, ##__VA_ARGS__ ); \ - break; \ - case LogLevelFatal: \ - os_log_fault( OS_LOG_DEFAULT, "%30s:%-3ld FTL | " format, basename( (char *)file ), line, ##__VA_ARGS__ ); \ - break; \ - } \ - \ - mpw_log_sink( level, file, line, function, format, ##__VA_ARGS__ ); \ - } while (0) - #ifdef __OBJC__ #import "Pearl-Prefix.pch" -#define MPW_LOG(level, file, line, function, format, ...) mpw_log_os(level, file, line, function, strf(format, ##__VA_ARGS__)) +#define MPW_LOG(level, file, line, function, format, ...) mpw_log_sink(level, file, line, function, strf(format, ##__VA_ARGS__)) #if TARGET_OS_IOS #import "MPTypes.h" @@ -72,10 +42,6 @@ MP_LIBS_END #import "MPMacConfig.h" #endif -#else - -#define MPW_LOG mpw_log_os - #endif #include "mpw-util.h" diff --git a/platform-darwin/Source/iOS/MPSitesViewController.m b/platform-darwin/Source/iOS/MPSitesViewController.m index 0c146dd6..b79f5baa 100644 --- a/platform-darwin/Source/iOS/MPSitesViewController.m +++ b/platform-darwin/Source/iOS/MPSitesViewController.m @@ -479,8 +479,43 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) { - (IBAction)upgradeVolto:(UIButton *)sender { - if ([UIApp canOpenURL:[[NSURL alloc] initWithString:@"volto://"]]) - [UIApp openURL:[[NSURL alloc] initWithString:@"volto://"]]; + if ([UIApp canOpenURL:[[NSURL alloc] initWithString:@"volto:"]]) { + [[MPiOSAppDelegate get] exportSitesRevealPasswords:NO askExportPassword:^NSString *(NSString *userName) { + return PearlAwait( ^(void (^setResult)(id)) { + PearlMainQueue( ^{ + UIAlertController *alert = [UIAlertController alertControllerWithTitle:strf( @"Master Password For:\n%@", userName ) + message:@"Enter your master password to export the user." + preferredStyle:UIAlertControllerStyleAlert]; + [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) { + textField.secureTextEntry = YES; + }]; + [alert addAction:[UIAlertAction actionWithTitle:@"Export" style:UIAlertActionStyleDefault handler: + ^(UIAlertAction *action) { setResult( alert.textFields.firstObject.text ); }]]; + [alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler: + ^(UIAlertAction *action) { setResult( nil ); }]]; + [self.navigationController presentViewController:alert animated:YES completion:nil]; + } ); + } ); + } result:^(NSString *exportedUser, NSError *error) { + if (!exportedUser || error) { + MPError( error, @"Failed to export user." ); + PearlMainQueue( ^{ + UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Export Error" + message:[error localizedDescription] + preferredStyle:UIAlertControllerStyleAlert]; + [alert addAction:[UIAlertAction actionWithTitle:@"Okay" style:UIAlertActionStyleCancel handler:nil]]; + [self.navigationController presentViewController:alert animated:YES completion:nil]; + } ); + return; + } + + NSURLComponents *components = [NSURLComponents new]; + components.scheme = @"volto"; + components.path = @"import"; + components.queryItems = @[ [[NSURLQueryItem alloc] initWithName:@"data" value:exportedUser] ]; + [UIApp openURL:components.URL]; + }]; + } else if (self.voltoViewController) [self presentViewController:self.voltoViewController animated:YES completion:nil]; } @@ -489,7 +524,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) { - (void)updateVoltoAlerts { - BOOL voltoInstalled = [UIApp canOpenURL:[[NSURL alloc] initWithString:@"volto://"]]; + BOOL voltoInstalled = [UIApp canOpenURL:[[NSURL alloc] initWithString:@"volto:"]]; if (voltoInstalled) { self.voltoInstallAlert.visible = NO; self.voltoMigrateAlert.visible = YES; diff --git a/platform-darwin/Source/iOS/MPiOSAppDelegate.m b/platform-darwin/Source/iOS/MPiOSAppDelegate.m index 00778505..9d948016 100644 --- a/platform-darwin/Source/iOS/MPiOSAppDelegate.m +++ b/platform-darwin/Source/iOS/MPiOSAppDelegate.m @@ -33,6 +33,35 @@ @end +MPLogSink mpw_log_sink_pearl; +void mpw_log_sink_pearl(const MPLogEvent *record) { + + PearlLogLevel level = PearlLogLevelInfo; + switch (record->level) { + case LogLevelTrace: + level = PearlLogLevelDebug; + break; + case LogLevelDebug: + level = PearlLogLevelDebug; + break; + case LogLevelInfo: + level = PearlLogLevelInfo; + break; + case LogLevelWarning: + level = PearlLogLevelWarn; + break; + case LogLevelError: + level = PearlLogLevelError; + break; + case LogLevelFatal: + level = PearlLogLevelFatal; + break; + } + + [[PearlLogger get] inFile:[@(record->file) lastPathComponent] atLine:record->line fromFunction:@(record->function) + withLevel:level text:@(record->message)]; +} + @implementation MPiOSAppDelegate + (void)initialize { @@ -44,6 +73,9 @@ #ifdef DEBUG [PearlLogger get].printLevel = PearlLogLevelDebug; #endif + + mpw_verbosity = LogLevelTrace; + mpw_log_sink_register( &mpw_log_sink_pearl ); } ); } @@ -503,27 +535,31 @@ [self exportSitesRevealPasswords:revealPasswords askExportPassword:^NSString *(NSString *userName) { return PearlAwait( ^(void (^setResult)(id)) { - UIAlertController *alert = [UIAlertController alertControllerWithTitle:strf( @"Master Password For:\n%@", userName ) message: - @"Enter the user's master password to create an export file." - preferredStyle:UIAlertControllerStyleAlert]; - [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) { - textField.secureTextEntry = YES; - }]; - [alert addAction:[UIAlertAction actionWithTitle:@"Export" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - setResult( alert.textFields.firstObject.text ); - }]]; - [alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { - setResult( nil ); - }]]; - [self.navigationController presentViewController:alert animated:YES completion:nil]; + PearlMainQueue( ^{ + UIAlertController *alert = [UIAlertController alertControllerWithTitle:strf( @"Master Password For:\n%@", userName ) + message:@"Enter your master password to export the user." + preferredStyle:UIAlertControllerStyleAlert]; + [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) { + textField.secureTextEntry = YES; + }]; + [alert addAction:[UIAlertAction actionWithTitle:@"Export" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + setResult( alert.textFields.firstObject.text ); + }]]; + [alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { + setResult( nil ); + }]]; + [self.navigationController presentViewController:alert animated:YES completion:nil]; + } ); } ); - } result:^(NSString *mpsites, NSError *error) { - if (!mpsites || error) { + } result:^(NSString *exportedUser, NSError *error) { + if (!exportedUser || error) { MPError( error, @"Failed to export mpsites." ); - UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Export Error" message:[error localizedDescription] - preferredStyle:UIAlertControllerStyleAlert]; - [alert addAction:[UIAlertAction actionWithTitle:@"Okay" style:UIAlertActionStyleCancel handler:nil]]; - [self.navigationController presentViewController:alert animated:YES completion:nil]; + PearlMainQueue( ^{ + UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Export Error" message:[error localizedDescription] + preferredStyle:UIAlertControllerStyleAlert]; + [alert addAction:[UIAlertAction actionWithTitle:@"Okay" style:UIAlertActionStyleCancel handler:nil]]; + [self.navigationController presentViewController:alert animated:YES completion:nil]; + } ); return; } @@ -555,11 +591,9 @@ [PearlInfoPlist get].CFBundleVersion ); [PearlEMail sendEMailTo:nil fromVC:viewController subject:@"Master Password Export" body:message - attachments:[[PearlEMailAttachment alloc] - initWithContent:[mpsites dataUsingEncoding:NSUTF8StringEncoding] - mimeType:@"text/plain" - fileName:exportFileName], - nil]; + attachments:[[PearlEMailAttachment alloc] initWithContent:[exportedUser dataUsingEncoding:NSUTF8StringEncoding] + mimeType:@"text/plain" + fileName:exportFileName], nil]; return; }]]; [alert addAction:[UIAlertAction actionWithTitle:@"Share / Export" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { @@ -569,7 +603,7 @@ URLByAppendingPathComponent:[NSBundle mainBundle].bundleIdentifier isDirectory:YES] URLByAppendingPathComponent:exportFileName isDirectory:NO]; NSError *writeError = nil; - if (![[mpsites dataUsingEncoding:NSUTF8StringEncoding] + if (![[exportedUser dataUsingEncoding:NSUTF8StringEncoding] writeToURL:exportURL options:NSDataWritingFileProtectionComplete error:&writeError]) MPError( writeError, @"Failed to write export data to URL %@.", exportURL ); else { diff --git a/platform-darwin/Source/iOS/MasterPassword-Info.plist b/platform-darwin/Source/iOS/MasterPassword-Info.plist index dd768bed..ff5b00fb 100644 --- a/platform-darwin/Source/iOS/MasterPassword-Info.plist +++ b/platform-darwin/Source/iOS/MasterPassword-Info.plist @@ -9,10 +9,6 @@ CFBundleDocumentTypes - CFBundleTypeExtensions - - mpsites - CFBundleTypeIconFiles Icon-Small @@ -20,7 +16,7 @@ CFBundleTypeName Master Password sites LSHandlerRank - Owner + Alternate LSItemContentTypes com.lyndir.masterpassword.sites diff --git a/platform-darwin/Source/iOS/Storyboard.storyboard b/platform-darwin/Source/iOS/Storyboard.storyboard index 9b722a54..58128f88 100644 --- a/platform-darwin/Source/iOS/Storyboard.storyboard +++ b/platform-darwin/Source/iOS/Storyboard.storyboard @@ -69,7 +69,7 @@ - +