2
0

Fix login/result state import/export, keyID; Volto export; Pearl log sink.

This commit is contained in:
Maarten Billemont 2020-02-28 17:33:38 -05:00
parent b15417aa31
commit 131720eb8d
9 changed files with 159 additions and 93 deletions

View File

@ -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;

View File

@ -73,7 +73,7 @@ static NSOperationQueue *_mpwQueue = nil;
return [(id<MPAlgorithm>)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))

View File

@ -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

View File

@ -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),
}] );

View File

@ -28,41 +28,11 @@ MP_LIBS_BEGIN
#include <libgen.h>
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"

View File

@ -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;

View File

@ -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 {

View File

@ -9,10 +9,6 @@
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>mpsites</string>
</array>
<key>CFBundleTypeIconFiles</key>
<array>
<string>Icon-Small</string>
@ -20,7 +16,7 @@
<key>CFBundleTypeName</key>
<string>Master Password sites</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<string>Alternate</string>
<key>LSItemContentTypes</key>
<array>
<string>com.lyndir.masterpassword.sites</string>

View File

@ -69,7 +69,7 @@
</collectionViewFlowLayout>
<cells>
<collectionViewCell opaque="NO" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="MPAvatarCell" id="Zab-uQ-uk9" customClass="MPAvatarCell">
<rect key="frame" x="80" y="115" width="215" height="667"/>
<rect key="frame" x="80" y="114.5" width="215" height="667"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="215" height="667"/>