Refactored touchID codebase to be more in line with the rest.

Fixed a lot of potential memory leaks
Fixed all issues reported analyzer
This commit is contained in:
Michael Starke
2022-08-30 13:13:12 +02:00
parent 0af2a2258f
commit d601d6ed3f
20 changed files with 329 additions and 229 deletions

View File

@@ -28,6 +28,7 @@
4C0F647B17B6BC9C00D9522A /* MPSavePanelAccessoryViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C0F647A17B6BC9C00D9522A /* MPSavePanelAccessoryViewController.m */; };
4C10207F1B750E2F00BFCD59 /* MPTestAutotype.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C10207E1B750E2F00BFCD59 /* MPTestAutotype.m */; };
4C10412C178CDD44001B5239 /* NSDate+Humanized.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C10412B178CDD44001B5239 /* NSDate+Humanized.m */; };
4C11BE6928B3B54900E2DAEA /* MPDocument+BiometricEncryptionSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C11BE6828B3B54900E2DAEA /* MPDocument+BiometricEncryptionSupport.m */; };
4C15B74618BCA3B1003F8008 /* MPDocument+Search.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C15B74518BCA3B1003F8008 /* MPDocument+Search.m */; };
4C17D11E2250EFBC00C650C4 /* SavePanelAccessoryView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4C17D1202250EFBC00C650C4 /* SavePanelAccessoryView.xib */; };
4C17D8E517A1C780006C8C1E /* MPDocumentWindowDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C17D8E417A1C780006C8C1E /* MPDocumentWindowDelegate.m */; };
@@ -401,6 +402,8 @@
4C10207E1B750E2F00BFCD59 /* MPTestAutotype.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPTestAutotype.m; sourceTree = "<group>"; };
4C10412A178CDD44001B5239 /* NSDate+Humanized.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDate+Humanized.h"; sourceTree = "<group>"; };
4C10412B178CDD44001B5239 /* NSDate+Humanized.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDate+Humanized.m"; sourceTree = "<group>"; };
4C11BE6728B3B54900E2DAEA /* MPDocument+BiometricEncryptionSupport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MPDocument+BiometricEncryptionSupport.h"; sourceTree = "<group>"; };
4C11BE6828B3B54900E2DAEA /* MPDocument+BiometricEncryptionSupport.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "MPDocument+BiometricEncryptionSupport.m"; sourceTree = "<group>"; };
4C15B74518BCA3B1003F8008 /* MPDocument+Search.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MPDocument+Search.m"; sourceTree = "<group>"; };
4C17D11F2250EFBC00C650C4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/SavePanelAccessoryView.xib; sourceTree = "<group>"; };
4C17D1222250EFBF00C650C4 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/SavePanelAccessoryView.strings; sourceTree = "<group>"; };
@@ -1423,6 +1426,8 @@
6E719715172058BA00E4C5FC /* MPDatabaseVersion.h */,
4CE5B548173AFBA700207B39 /* MPDocument.h */,
4CE5B549173AFBA700207B39 /* MPDocument.m */,
4C11BE6728B3B54900E2DAEA /* MPDocument+BiometricEncryptionSupport.h */,
4C11BE6828B3B54900E2DAEA /* MPDocument+BiometricEncryptionSupport.m */,
4C3666401787327E00B249F1 /* MPDocument+Attachments.m */,
4C1FA07A18231900003A3F8C /* MPDocument+Autotype.m */,
4C6B7C7C18BE7EB0001D5D77 /* MPDocument+History.m */,
@@ -2027,7 +2032,7 @@
isa = PBXProject;
attributes = {
CLASSPREFIX = MP;
LastUpgradeCheck = 1250;
LastUpgradeCheck = 1340;
ORGANIZATIONNAME = "HicknHack Software GmbH";
TargetAttributes = {
4C77E36115B84A240093A587 = {
@@ -2384,6 +2389,7 @@
4C4B7EE917A45EC6000234C7 /* MPDatePickingViewController.m in Sources */,
4C4B7EEE17A467E1000234C7 /* MPGroupInspectorViewController.m in Sources */,
4C71BCB72167B79C00B4CBDA /* MPPluginVersionComparator.m in Sources */,
4C11BE6928B3B54900E2DAEA /* MPDocument+BiometricEncryptionSupport.m in Sources */,
4C4B7EF317A467FC000234C7 /* MPEntryInspectorViewController.m in Sources */,
4C1BDF2B1E4392640012A3F0 /* MPPluginDataViewController.m in Sources */,
4C4B7EF817A4B335000234C7 /* MPUniqueCharactersFormatter.m in Sources */,

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1250"
LastUpgradeVersion = "1340"
version = "2.0">
<BuildAction
parallelizeBuildables = "YES"

View File

@@ -219,7 +219,8 @@ static MPAutotypeDaemon *_sharedInstance;
NSNotificationCenter * __weak nc = [NSNotificationCenter defaultCenter];
MPAutotypeDaemon * __weak welf = self;
NSTimeInterval requestTime = NSDate.date.timeIntervalSinceReferenceDate;
id __block unlockToken = [nc addObserverForName:MPDocumentDidUnlockDatabaseNotification
id __block unlockToken; // silence init value never read analyzer warning
unlockToken = [nc addObserverForName:MPDocumentDidUnlockDatabaseNotification
object:nil
queue:NSOperationQueue.mainQueue
usingBlock:^(NSNotification *notification) {
@@ -247,7 +248,8 @@ static MPAutotypeDaemon *_sharedInstance;
NSNotificationCenter * __weak nc = [NSNotificationCenter defaultCenter];
MPAutotypeDaemon * __weak welf = self;
NSTimeInterval requestTime = NSDate.date.timeIntervalSinceReferenceDate;
id __block unlockToken = [nc addObserverForName:MPDocumentDidUnlockDatabaseNotification
id __block unlockToken; // silence init value never read analyzer warning
unlockToken = [nc addObserverForName:MPDocumentDidUnlockDatabaseNotification
object:nil
queue:NSOperationQueue.mainQueue
usingBlock:^(NSNotification *notification) {
@@ -408,7 +410,8 @@ static MPAutotypeDaemon *_sharedInstance;
}
NSNotificationCenter * __weak nc = NSWorkspace.sharedWorkspace.notificationCenter;
id __block didActivateToken = [nc addObserverForName:NSWorkspaceDidActivateApplicationNotification
id __block didActivateToken; // silence init value never read analyzer warning
didActivateToken = [nc addObserverForName:NSWorkspaceDidActivateApplicationNotification
object:nil
queue:NSOperationQueue.mainQueue
usingBlock:^(NSNotification *notification) {

View File

@@ -44,7 +44,7 @@ FOUNDATION_EXPORT NSString *const MPPluginCompatibilityURLKey;
/**
Keychain Keys
*/
extern NSString *const TouchIdUnlockPublicKeyTag;
extern NSString *const TouchIdUnlockPrivateKeyTag;
extern NSString *const MPTouchIdUnlockPublicKeyTag;
extern NSString *const MPTouchIdUnlockPrivateKeyTag;
#endif

View File

@@ -31,6 +31,6 @@ NSString *const MPBundleHelpURLKey = @"MPHelpURL";
NSString *const MPBundlePluginRepositoryURLKey = @"MPPluginRepositoryURL";
NSString *const MPPluginCompatibilityURLKey = @"MPPluginCompatibilityURLKey";
NSString *const TouchIdUnlockPublicKeyTag = @"com.hicknhacksoftware.macpass.publickey";
NSString *const TouchIdUnlockPrivateKeyTag = @"com.hicknhacksoftware.macpass.privatekey";
NSString *const MPTouchIdUnlockPublicKeyTag = @"com.hicknhacksoftware.macpass.publickey";
NSString *const MPTouchIdUnlockPrivateKeyTag = @"com.hicknhacksoftware.macpass.privatekey";

View File

@@ -0,0 +1,19 @@
//
// MPDocument+BiometricEncryptionSupport.h
// MacPass
//
// Created by Michael Starke on 22.08.22.
// Copyright © 2022 HicknHack Software GmbH. All rights reserved.
//
#import "MPDocument.h"
NS_ASSUME_NONNULL_BEGIN
@interface MPDocument (BiometricEncryptionSupport)
@property (nonatomic, readonly, copy, nullable) NSString *biometricKey;
@property (nonatomic, readonly, copy, nullable) NSData *encryptedKeyData;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,32 @@
//
// MPDocument+BiometricEncryptionSupport.m
// MacPass
//
// Created by Michael Starke on 22.08.22.
// Copyright © 2022 HicknHack Software GmbH. All rights reserved.
//
#import "MPDocument+BiometricEncryptionSupport.h"
#import "MPSettingsHelper.h"
#import "MPTouchIdCompositeKeyStore.h"
@implementation MPDocument (BiometricEncryptionSupport)
@dynamic biometricKey;
- (NSString *)biometricKey {
if(nil == self.fileURL || nil == self.fileURL.lastPathComponent) {
return nil;
}
return [NSString stringWithFormat:kMPSettingsKeyEntryTouchIdDatabaseEncryptedKeyFormat, self.fileURL.lastPathComponent];
}
- (NSData *)encryptedKeyData {
NSString *documentKey = self.biometricKey;
if(nil == documentKey) {
return nil;
}
return [MPTouchIdCompositeKeyStore.defaultStore loadEncryptedCompositeKeyForDocumentKey:documentKey];
}
@end

View File

@@ -346,7 +346,6 @@ typedef NS_ENUM(NSUInteger, MPInpspectorEditorIndex) {
return YES;
}
}
#pragma mark -
#pragma mark QLPreviewPanelDelegate

View File

@@ -138,7 +138,7 @@
#pragma mark -
#pragma mark Keychain Actions
- (IBAction)RenewTouchIdKey:(id)sender {
NSData* publicKeyTag = [TouchIdUnlockPublicKeyTag dataUsingEncoding:NSUTF8StringEncoding];
NSData* publicKeyTag = [MPTouchIdUnlockPublicKeyTag dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *publicKeyQuery = @{
(id)kSecClass: (id)kSecClassKey,
(id)kSecAttrApplicationTag: publicKeyTag,
@@ -146,11 +146,11 @@
};
OSStatus status = SecItemDelete((__bridge CFDictionaryRef)publicKeyQuery);
if (status != errSecSuccess) {
NSString* description = (__bridge NSString*)SecCopyErrorMessageString(status, NULL);
NSString* description = CFBridgingRelease(SecCopyErrorMessageString(status, NULL));
NSLog(@"Error while trying to delete public key from Keychain: %@", description);
}
NSData* privateKeyTag = [TouchIdUnlockPrivateKeyTag dataUsingEncoding:NSUTF8StringEncoding];
NSData* privateKeyTag = [MPTouchIdUnlockPrivateKeyTag dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *privateKeyQuery = @{
(id)kSecClass: (id)kSecClassKey,
(id)kSecAttrApplicationTag: privateKeyTag,
@@ -158,7 +158,7 @@
};
status = SecItemDelete((__bridge CFDictionaryRef)privateKeyQuery);
if (status != errSecSuccess) {
NSString* description = (__bridge NSString*)SecCopyErrorMessageString(status, NULL);
NSString* description = CFBridgingRelease(SecCopyErrorMessageString(status, NULL));
NSLog(@"Error while trying to delete private key from Keychain: %@", description);
}
}

View File

@@ -88,6 +88,9 @@
sizeof(chars) / sizeof(chars[0]),
&realLength,
chars);
if(0 != success) {
NSLog(@"Unable to transpate modifiedKey:%@", MPStringFromModifiedKey(modifiedKey));
}
return CFBridgingRelease(CFStringCreateWithCharacters(kCFAllocatorDefault, chars, realLength));
}

View File

@@ -40,6 +40,10 @@ NS_INLINE BOOL MPIsValidModifiedKey(MPModifiedKey k) {
return (k.keyCode == kMPUnknownKeyCode);
}
NS_INLINE NSString *MPStringFromModifiedKey(MPModifiedKey key) {
return [NSString stringWithFormat:@"keyCode:%hu %llud", key.keyCode, key.modifier];
}
@interface NSValue(NSValueMPModifiedKeyExtensions)
@property (nonatomic, readonly, assign) MPModifiedKey modifiedKeyValue;
+ (instancetype)valueWithModifiedKey:(MPModifiedKey)key;

View File

@@ -151,7 +151,7 @@ typedef NS_ENUM(NSUInteger, MPPasswordEditKeyError) {
- (IBAction)generateKey:(id)sender {
MPDocument *document = self.document;
KPKFileVersion fileVersion = document.tree.minimumVersion;
NSArray *fileTypes = @[];
NSArray *fileTypes;
KPKKeyFileType keyFileType;
if(fileVersion.format == KPKDatabaseFormatUnknown) {

View File

@@ -24,6 +24,7 @@
#import "MPAppDelegate.h"
#import "MPDocumentWindowController.h"
#import "MPDocument.h"
#import "MPDocument+BiometricEncryptionSupport.h"
#import "MPSettingsHelper.h"
#import "MPPathControl.h"
#import "MPTouchBarButtonCreator.h"
@@ -88,9 +89,9 @@
[self.passwordTextField bind:NSEnabledBinding toObject:self withKeyPath:NSStringFromSelector(@selector(enablePassword)) options:nil];
NSUserDefaultsController *defaultsController = [NSUserDefaultsController sharedUserDefaultsController];
[self.touchIdEnabledButton bind:NSValueBinding toObject:defaultsController withKeyPath:[MPSettingsHelper defaultControllerPathForKey:kMPSettingsKeyEntryTouchIdEnabled] options:nil];
self.touchIdEnabledButton.hidden = true;
self.touchIdEnabledButton.hidden = YES;
if (@available(macOS 10.13.4, *)) {
self.touchIdEnabledButton.hidden = false;
self.touchIdEnabledButton.hidden = NO;
[self _touchIdUpdateToolTip];
}
[self _reset];
@@ -143,12 +144,11 @@
[compositeKey addKey:passwordKey];
[compositeKey addKey:fileKey];
/* After the completion handler finished we no longer have a windowController set */
NSString* documentKey = NULL;
bool documentKeyValid = [self _touchIdGetKeyForCurrentDocument:&documentKey];
NSString* documentKey = [self biometricKeyForCurrentDocument];
BOOL result = self.completionHandler(compositeKey, keyURL, cancel, &error);
if(result) {
if(documentKeyValid) {
[self _touchIdUpdateKeyForCurrentDocument:compositeKey forDocumentKey:documentKey];
if(nil != documentKey) {
[MPTouchIdCompositeKeyStore.defaultStore saveCompositeKey:compositeKey forDocumentKey:documentKey];
}
return;
}
@@ -161,185 +161,47 @@
[self.view.window shakeWindow:nil];
}
}
/*
- (KPKCompositeKey*)_touchIdDecryptCompositeKey:(NSData*)encryptedKey {
NSError *error;
return [MPTouchIdCompositeKeyStore.defaultStore compositeKeyForEncryptedKeyData:encryptedKey error:&error];
}*/
- (void) _touchIdUpdateKeyForCurrentDocument: (KPKCompositeKey*)compositeKey forDocumentKey: (NSString*) documentKey{
NSData* encryptedKey = [self _touchIdEncryptCompositeKey:compositeKey];
[MPTouchIdCompositeKeyStore.defaultStore save:encryptedKey forDocumentKey:documentKey];
}
- (void) _touchIdCreateAndAddRSAKeyPair {
CFErrorRef error = NULL;
NSString* publicKeyLabel = @"MacPass TouchID Feature Public Key";
NSString* privateKeyLabel = @"MacPass TouchID Feature Private Key";
NSData* publicKeyTag = [TouchIdUnlockPublicKeyTag dataUsingEncoding:NSUTF8StringEncoding];
NSData* privateKeyTag = [TouchIdUnlockPrivateKeyTag dataUsingEncoding:NSUTF8StringEncoding];
SecAccessControlRef access = NULL;
if (@available(macOS 10.13.4, *)) {
SecAccessControlCreateFlags flags = kSecAccessControlBiometryCurrentSet;
if (@available(macOS 10.15, *)) {
flags |= kSecAccessControlWatch | kSecAccessControlOr;
}
access = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
flags,
&error);
if(access == NULL) {
NSError *err = CFBridgingRelease(error);
NSLog(@"Error while trying to create AccessControl for TouchID unlock feature: %@", [err description]);
return;
}
NSDictionary* attributes = @{
(id)kSecAttrKeyType: (id)kSecAttrKeyTypeRSA,
(id)kSecAttrKeySizeInBits: @2048,
(id)kSecAttrSynchronizable: @NO,
(id)kSecPrivateKeyAttrs:
@{ (id)kSecAttrIsPermanent: @YES,
(id)kSecAttrApplicationTag: privateKeyTag,
(id)kSecAttrLabel: privateKeyLabel,
(id)kSecAttrAccessControl: (__bridge id)access
},
(id)kSecPublicKeyAttrs:
@{ (id)kSecAttrIsPermanent: @YES,
(id)kSecAttrApplicationTag: publicKeyTag,
(id)kSecAttrLabel: publicKeyLabel,
},
};
SecKeyRef result = SecKeyCreateRandomKey((__bridge CFDictionaryRef)attributes, &error);
if(result == NULL) {
NSError *err = CFBridgingRelease(error);
NSLog(@"Error while trying to create a RSA keypair for TouchID unlock feature: %@", [err description]);
}
else {
CFRelease(result);
}
}
else {
return;
}
}
- (NSData*) _touchIdEncryptCompositeKey: (KPKCompositeKey*) compositeKey {
NSData* encryptedKey = nil;
NSData* keyData = [NSKeyedArchiver archivedDataWithRootObject:compositeKey];
NSData* tag = [TouchIdUnlockPublicKeyTag dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *getquery = @{
(id)kSecClass: (id)kSecClassKey,
(id)kSecAttrApplicationTag: tag,
(id)kSecReturnRef: @YES,
};
SecKeyRef publicKey = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)getquery, (CFTypeRef *)&publicKey);
if (status != errSecSuccess) {
[self _touchIdCreateAndAddRSAKeyPair];
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)getquery, (CFTypeRef *)&publicKey);
if (status != errSecSuccess) {
NSString* description = (__bridge NSString*)SecCopyErrorMessageString(status, NULL);
NSLog(@"Error while trying to query public key from Keychain: %@", description);
return nil;
}
}
SecKeyAlgorithm algorithm = kSecKeyAlgorithmRSAEncryptionOAEPSHA256AESGCM;
BOOL canEncrypt = SecKeyIsAlgorithmSupported(publicKey, kSecKeyOperationTypeEncrypt, algorithm);
if(canEncrypt) {
CFErrorRef error = NULL;
encryptedKey = (NSData*)CFBridgingRelease(SecKeyCreateEncryptedData(publicKey, algorithm, (__bridge CFDataRef)keyData, &error));
if (!encryptedKey) {
NSError *err = CFBridgingRelease(error);
NSLog(@"Error while trying to decrypt the CompositeKey for TouchID unlock: %@", [err description]);
}
}
else {
NSLog(@"The key retreived from the Keychain is unable to encrypt data");
}
if (publicKey) {
CFRelease(publicKey);
}
return encryptedKey;
}
- (KPKCompositeKey*) _touchIdDecryptCompositeKey: (NSData*) encryptedKey {
KPKCompositeKey* result = nil;
if(encryptedKey != nil) {
NSData* tag = [TouchIdUnlockPrivateKeyTag dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *queryPrivateKey = @{
(id)kSecClass: (id)kSecClassKey,
(id)kSecAttrApplicationTag: tag,
(id)kSecAttrKeyType: (id)kSecAttrKeyTypeRSA,
(id)kSecReturnRef: @YES,
};
SecKeyRef privateKey = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)queryPrivateKey, (CFTypeRef *)&privateKey);
if (status == errSecSuccess) {
SecKeyAlgorithm algorithm = kSecKeyAlgorithmRSAEncryptionOAEPSHA256AESGCM;
BOOL canDecrypt = SecKeyIsAlgorithmSupported(privateKey, kSecKeyOperationTypeDecrypt, algorithm);
if(canDecrypt) {
CFErrorRef error = NULL;
NSData* clearText = (NSData*)CFBridgingRelease(SecKeyCreateDecryptedData(privateKey, algorithm, (__bridge CFDataRef)encryptedKey, &error));
if (clearText) {
result = [NSKeyedUnarchiver unarchiveObjectWithData:clearText];
}
else {
NSError *err = CFBridgingRelease(error);
NSLog(@"Error while trying to decrypt password for TouchID unlock: %@", [err description]);
}
}
else {
NSLog(@"Key does not support decryption");
}
}
else {
NSString* description = (__bridge NSString*)SecCopyErrorMessageString(status, NULL);
NSLog(@"Error while trying to retrive private key for decryption: %@", description);
}
if (privateKey) {
CFRelease(privateKey);
}
}
return result;
}
- (bool) _touchIdGetKeyForCurrentDocument: (NSString**) result {
*result = NULL;
NSDocument* currentDocument = self.windowController.document;
if(currentDocument != NULL && currentDocument.fileURL != NULL && currentDocument.fileURL.lastPathComponent != NULL) {
*result = [NSString stringWithFormat:kMPSettingsKeyEntryTouchIdDatabaseEncryptedKeyFormat, currentDocument.fileURL.lastPathComponent];
return true;
}
return false;
- (NSString *)biometricKeyForCurrentDocument {
MPDocument* currentDocument = (MPDocument *)self.windowController.document;
return currentDocument.biometricKey;
}
- (bool) _touchIdIsUnlockAvailable {
NSData* unused = NULL;
bool encryptedKeyAvailableForDocument = [self _touchIdGetEncrypedKeyMaterial:&unused];
return encryptedKeyAvailableForDocument;
MPDocument *currentDocument = (MPDocument *)self.windowController.document;
return (nil != currentDocument.encryptedKeyData);
}
- (bool) _touchIdGetEncrypedKeyMaterial: (NSData**) result {
NSString* documentKey = NULL;
*result = NULL;
if(![self _touchIdGetKeyForCurrentDocument:&documentKey]) {
return false;
- (NSData * _Nullable)_touchIdEncryptedCompositeKeyForCurrentDocutmen {
NSString* documentKey = [self biometricKeyForCurrentDocument];
if(nil == documentKey) {
return nil;
}
return [MPTouchIdCompositeKeyStore.defaultStore load:result forDocumentKey:documentKey];
return [MPTouchIdCompositeKeyStore.defaultStore loadEncryptedCompositeKeyForDocumentKey:documentKey];
}
- (IBAction)unlockWithTouchID:(id)sender {
NSData* encryptedKey = NULL;
if(![self _touchIdGetEncrypedKeyMaterial:&encryptedKey]) {
NSData* encryptedKey = [self _touchIdEncryptedCompositeKeyForCurrentDocutmen];
if(!encryptedKey) {
self.touchIdButton.enabled = NO;
return;
}
KPKCompositeKey* compositeKey = [self _touchIdDecryptCompositeKey:encryptedKey];
if(compositeKey == NULL) {
NSError *error;
KPKCompositeKey* compositeKey = [MPTouchIdCompositeKeyStore.defaultStore compositeKeyForEncryptedKeyData:encryptedKey error:&error];
if(!compositeKey) {
self.touchIdButton.enabled = NO;
return;
}
NSError* error;
bool success = self.completionHandler(compositeKey, NULL, false, &error);
if(success) {
return;
}
[self.touchIdButton setEnabled:false];
self.touchIdButton.enabled = NO;
[self _showError:error];
}
@@ -375,7 +237,7 @@
self.passwordTextField.stringValue = @"";
self.messageInfoTextField.hidden = (nil == self.message);
self.touchIdButton.hidden = ![self _touchIdIsUnlockAvailable];
[self.touchIdButton setEnabled:true];
self.touchIdButton.enabled = YES;
if(self.message) {
self.messageInfoTextField.stringValue = self.message;

View File

@@ -92,7 +92,9 @@ typedef NS_ENUM(NSUInteger, MPPickfieldTableColumn) {
view.textField.stringValue = rowItem.name;
break;
case MPPIckfieldValueTableColumn:
view.textField.stringValue = rowItem.isProtected ? @"•••" : rowItem.value;
view.textField.stringValue = (rowItem.isProtected
? NSLocalizedString(@"PROTECTED_PASSWORD_STRING", @"String to show an obfuscated password. Something like three dots.")
: rowItem.value);
break;
default:
break;

View File

@@ -67,8 +67,8 @@ NSString *const kMPSettingsKeyAutotypeMatchHost = @"Au
NSString *const kMPSettingsKeyAutotypeMatchTags = @"AutotypeMatchTags";
NSString *const kMPSettingsKeyGloablAutotypeAlwaysShowCandidateSelection = @"GloablAutotypeAlwaysShowCandidateSelection";
NSString *const kMPSettingsKeyEntryTouchIdEnabled = @"EnableSubsequentUnlocksWithTouchID";
NSString *const kMPSettingsKeyEntryTouchIdDatabaseEncryptedKeyFormat = @"EncryptedDatabaseKeyForTouchID-%@";
NSString *const kMPSettingsKeyEntryTouchIdEnabled = @"EnableSubsequentUnlocksWithTouchID";
NSString *const kMPSettingsKeyEntryTouchIdDatabaseEncryptedKeyFormat = @"EncryptedDatabaseKeyForTouchID-%@";
NSString *const kMPSettingsKeyEntrySearchFilterContext = @"EntrySearchFilterContext";

View File

@@ -315,11 +315,11 @@ NSString *const MPToolbarItemIdentifierAutotype = @"TOOLBAR_AUTOTYPE";
item.tag = NSSearchFieldRecentsTitleMenuItemTag;
[menu addItem:item];
item = [[NSMenuItem alloc] initWithTitle:@"Recents" action:NULL keyEquivalent:@""];
item = [[NSMenuItem alloc] initWithTitle:@"" action:NULL keyEquivalent:@""];
item.tag = NSSearchFieldRecentsMenuItemTag;
[menu addItem:item];
item = [[NSMenuItem alloc] initWithTitle:@"NoEntries" action:NULL keyEquivalent:@""];
item = [[NSMenuItem alloc] initWithTitle:@"" action:NULL keyEquivalent:@""];
item.tag = NSSearchFieldNoRecentsMenuItemTag;
[menu addItem:item];

View File

@@ -6,16 +6,27 @@
// Copyright © 2021 HicknHack Software GmbH. All rights reserved.
//
#ifndef MPTouchIdCompositeKeyStore_h
#define MPTouchIdCompositeKeyStore_h
NS_ASSUME_NONNULL_BEGIN
static NSMutableDictionary* touchIDSecuredPasswords;
@class KPKCompositeKey;
@interface MPTouchIdCompositeKeyStore : NSObject
@property (class, strong, readonly) MPTouchIdCompositeKeyStore *defaultStore;
- (void) save:(NSData*) encryptedCompositeKey forDocumentKey:(NSString*) documentKey;
- (bool) load:(NSData**) encryptedCompositeKey forDocumentKey:(NSString*) documentKey;
@property (class, strong, readonly) MPTouchIdCompositeKeyStore *defaultStore;
/// Securely stores the provided compoiste key in the key store.
/// The key is encrypted and then stored to the corresponding location (transient or permanent)
/// @param compositeKey the composite key to store
/// @param documentKey the document key to store the composite key for
- (void)saveCompositeKey:(KPKCompositeKey *)compositeKey forDocumentKey:(NSString*)documentKey;
/// Load the encrypted composite key for the given key if anyone is found
/// @param documentKey the key to identify the document. Normally you should use the file name
- (NSData * _Nullable)loadEncryptedCompositeKeyForDocumentKey:(NSString *)documentKey;
- (KPKCompositeKey * _Nullable)compositeKeyForEncryptedKeyData:(NSData *)data error:(NSError **)error;
- (NSData * _Nullable)encryptedDataForCompositeKey:(KPKCompositeKey *)compositeKey error:(NSError **)error;
@end
#endif /* MPTouchIdCompositeKeyStore_h */
NS_ASSUME_NONNULL_END

View File

@@ -7,6 +7,13 @@
//
#import "MPSettingsHelper.h"
#import "MPTouchIdCompositeKeyStore.h"
#import "MPConstants.h"
#import "NSError+Messages.h"
@interface MPTouchIdCompositeKeyStore ()
@property (readonly, strong) NSMutableDictionary* keys;
@end
@implementation MPTouchIdCompositeKeyStore
@@ -15,50 +22,201 @@
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[MPTouchIdCompositeKeyStore alloc] init];
if(touchIDSecuredPasswords == NULL) {
touchIDSecuredPasswords = [[NSMutableDictionary alloc]init];
}
});
return instance;
}
- (void) save: (NSData*) encryptedCompositeKey forDocumentKey:(NSString*) documentKey {
long touchIdMode = [NSUserDefaults.standardUserDefaults integerForKey:kMPSettingsKeyEntryTouchIdEnabled];
if (touchIdMode == NSControlStateValueMixed) {
[NSUserDefaults.standardUserDefaults removeObjectForKey:documentKey];
if(encryptedCompositeKey != NULL) {
[touchIDSecuredPasswords setObject:encryptedCompositeKey forKey:documentKey];
- (instancetype)init {
self = [super init];
if(self) {
_keys = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void)saveCompositeKey:(KPKCompositeKey *)compositeKey forDocumentKey:(NSString *)documentKey {
NSError *error;
NSData *encryptedCompositeKey = [self encryptedDataForCompositeKey:compositeKey error:&error];
if(!encryptedCompositeKey) {
NSLog(@"Unable ot encrypt composite key: %@", error);
return;
}
NSInteger touchIdMode = [NSUserDefaults.standardUserDefaults integerForKey:kMPSettingsKeyEntryTouchIdEnabled];
switch(touchIdMode) {
case NSControlStateValueMixed:
[NSUserDefaults.standardUserDefaults removeObjectForKey:documentKey];
if(nil != encryptedCompositeKey) {
self.keys[documentKey] = encryptedCompositeKey;
}
case NSControlStateValueOn:
self.keys[documentKey] = nil;
if(nil != encryptedCompositeKey) {
[NSUserDefaults.standardUserDefaults setObject:encryptedCompositeKey forKey:documentKey];
}
default:
[NSUserDefaults.standardUserDefaults removeObjectForKey:documentKey];
self.keys[documentKey] = nil;
}
}
- (NSData *)loadEncryptedCompositeKeyForDocumentKey:(NSString *)documentKey {
NSInteger touchIdMode = [NSUserDefaults.standardUserDefaults integerForKey:kMPSettingsKeyEntryTouchIdEnabled];
NSData* transientKey = self.keys[documentKey];
NSData* persistentKey =[NSUserDefaults.standardUserDefaults dataForKey:documentKey];
if(nil == transientKey && nil == persistentKey) {
return nil;
}
if(nil == transientKey || nil == persistentKey) {
return transientKey == nil ? persistentKey : transientKey;
}
if(touchIdMode == NSControlStateValueOn) {
return persistentKey;
}
return transientKey;
}
- (KPKCompositeKey *)compositeKeyForEncryptedKeyData:(NSData *)data error:(NSError *__autoreleasing _Nullable *)error {
if(nil == data) {
return nil;
}
NSData* tag = [MPTouchIdUnlockPrivateKeyTag dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *queryPrivateKey = @{
(id)kSecClass: (id)kSecClassKey,
(id)kSecAttrApplicationTag: tag,
(id)kSecAttrKeyType: (id)kSecAttrKeyTypeRSA,
(id)kSecReturnRef: @YES,
};
SecKeyRef privateKey = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)queryPrivateKey, (CFTypeRef *)&privateKey);
if(status != errSecSuccess) {
if(error != NULL) {
NSString* description = CFBridgingRelease(SecCopyErrorMessageString(status, NULL));
*error = [NSError errorWithCode:status description:description];
}
if(privateKey) {
CFRelease(privateKey);
}
return nil;
}
SecKeyAlgorithm algorithm = kSecKeyAlgorithmRSAEncryptionOAEPSHA256AESGCM;
BOOL canDecrypt = SecKeyIsAlgorithmSupported(privateKey, kSecKeyOperationTypeDecrypt, algorithm);
if(!canDecrypt) {
if(error != NULL) {
*error = [NSError errorWithCode:MPErrorTouchIdUnsupportedKeyForEncrpytion description:NSLocalizedString(@"ERROR_TOUCH_ID_UNSUPPORTED_KEY", @"The key stored for TouchID is not suitable for encrpytion")];
}
if(privateKey) {
CFRelease(privateKey);
}
return nil;
}
CFErrorRef errorRef = NULL; // FIXME: Release?
NSData* clearText = (NSData*)CFBridgingRelease(SecKeyCreateDecryptedData(privateKey, algorithm, (__bridge CFDataRef)data, &errorRef));
if(clearText) {
return [NSKeyedUnarchiver unarchiveObjectWithData:clearText];
}
if(error != NULL) {
*error = CFBridgingRelease(errorRef);
}
if(privateKey) {
CFRelease(privateKey);
}
return nil;
}
- (NSData *)encryptedDataForCompositeKey:(KPKCompositeKey *)compositeKey error:(NSError *__autoreleasing _Nullable *)error {
NSData* keyData = [NSKeyedArchiver archivedDataWithRootObject:compositeKey];
NSData* tag = [MPTouchIdUnlockPublicKeyTag dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *getquery = @{
(id)kSecClass: (id)kSecClassKey,
(id)kSecAttrApplicationTag: tag,
(id)kSecReturnRef: @YES,
};
SecKeyRef publicKey = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)getquery, (CFTypeRef *)&publicKey);
if (status != errSecSuccess) {
[self _createAndAddRSAKeyPair];
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)getquery, (CFTypeRef *)&publicKey);
if (status != errSecSuccess) {
NSString* description = CFBridgingRelease(SecCopyErrorMessageString(status, NULL));
NSLog(@"Error while trying to query public key from Keychain: %@", description);
return nil;
}
}
else if(touchIdMode == NSControlStateValueOn) {
[touchIDSecuredPasswords removeObjectForKey:documentKey];
if(encryptedCompositeKey != NULL) {
[NSUserDefaults.standardUserDefaults setObject:encryptedCompositeKey forKey:documentKey];
SecKeyAlgorithm algorithm = kSecKeyAlgorithmRSAEncryptionOAEPSHA256AESGCM;
BOOL canEncrypt = SecKeyIsAlgorithmSupported(publicKey, kSecKeyOperationTypeEncrypt, algorithm);
NSData *encryptedKey;
if(canEncrypt) {
CFErrorRef error = NULL;
encryptedKey = (NSData*)CFBridgingRelease(SecKeyCreateEncryptedData(publicKey, algorithm, (__bridge CFDataRef)keyData, &error));
if (!encryptedKey) {
NSError *err = CFBridgingRelease(error);
NSLog(@"Error while trying to decrypt the CompositeKey for TouchID unlock: %@", [err description]);
}
}
else {
[NSUserDefaults.standardUserDefaults removeObjectForKey:documentKey];
[touchIDSecuredPasswords removeObjectForKey:documentKey];
NSLog(@"The key retreived from the Keychain is unable to encrypt data");
}
if (publicKey) {
CFRelease(publicKey);
}
return encryptedKey;
}
- (void)_createAndAddRSAKeyPair {
CFErrorRef error = NULL;
NSString* publicKeyLabel = @"MacPass TouchID Feature Public Key";
NSString* privateKeyLabel = @"MacPass TouchID Feature Private Key";
NSData* publicKeyTag = [MPTouchIdUnlockPublicKeyTag dataUsingEncoding:NSUTF8StringEncoding];
NSData* privateKeyTag = [MPTouchIdUnlockPrivateKeyTag dataUsingEncoding:NSUTF8StringEncoding];
SecAccessControlRef access = NULL;
if (@available(macOS 10.13.4, *)) {
SecAccessControlCreateFlags flags = kSecAccessControlBiometryCurrentSet;
if (@available(macOS 10.15, *)) {
flags |= kSecAccessControlWatch | kSecAccessControlOr;
}
access = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
flags,
&error);
if(access == NULL) {
NSError *err = CFBridgingRelease(error);
NSLog(@"Error while trying to create AccessControl for TouchID unlock feature: %@", [err description]);
return;
}
NSDictionary* attributes = @{
(id)kSecAttrKeyType: (id)kSecAttrKeyTypeRSA,
(id)kSecAttrKeySizeInBits: @2048,
(id)kSecAttrSynchronizable: @NO,
(id)kSecPrivateKeyAttrs:
@{ (id)kSecAttrIsPermanent: @YES,
(id)kSecAttrApplicationTag: privateKeyTag,
(id)kSecAttrLabel: privateKeyLabel,
(id)kSecAttrAccessControl: (__bridge id)access
},
(id)kSecPublicKeyAttrs:
@{ (id)kSecAttrIsPermanent: @YES,
(id)kSecAttrApplicationTag: publicKeyTag,
(id)kSecAttrLabel: publicKeyLabel,
},
};
SecKeyRef result = SecKeyCreateRandomKey((__bridge CFDictionaryRef)attributes, &error);
if(result == NULL) {
NSError *err = CFBridgingRelease(error);
NSLog(@"Error while trying to create a RSA keypair for TouchID unlock feature: %@", [err description]);
}
else {
CFRelease(result);
}
CFRelease(access);
}
else {
return;
}
}
- (bool) load: (NSData**) encryptedCompositeKey forDocumentKey: (NSString*) documentKey {
long touchIdMode = [NSUserDefaults.standardUserDefaults integerForKey:kMPSettingsKeyEntryTouchIdEnabled];
NSData* transientKey = [touchIDSecuredPasswords valueForKey:documentKey];
NSData* persistentKey =[NSUserDefaults.standardUserDefaults dataForKey:documentKey];
if(transientKey == NULL && persistentKey == NULL) {
return false;
}
if(transientKey == NULL || persistentKey == NULL) {
*encryptedCompositeKey = transientKey == NULL ? persistentKey : transientKey;
return true;
}
if(touchIdMode == NSControlStateValueOn) {
*encryptedCompositeKey = persistentKey;
return true;
}
*encryptedCompositeKey = transientKey;
return true;
}
@end

View File

@@ -29,7 +29,8 @@ typedef NS_ENUM(NSInteger, MPErrorCodes) {
MPErrorNoPasswordOrKeyFile = 10000,
MPErrorInvalidPlugin,
MPErrorAutotypeIsMissingAccessibiltyPermissions,
MPErrorAutotypeIsMissingScreenRecordingPermissions
MPErrorAutotypeIsMissingScreenRecordingPermissions,
MPErrorTouchIdUnsupportedKeyForEncrpytion,
};
@interface NSError (Messages)

View File

@@ -14,8 +14,8 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, readonly, copy) NSString *QRCodeString;
+ (instancetype)QRCodeImageWithString:(NSString *)string;
- (instancetype)initWithCIImage:(CIImage *)ciImage;
+ (instancetype _Nullable)QRCodeImageWithString:(NSString *)string;
- (instancetype _Nullable)initWithCIImage:(CIImage *)ciImage;
@end