Skip to content
Permalink
Browse files

Update password in the keychain instead of delete and add.

Restructured the code, so less keychain operations are required.
[#730]
  • Loading branch information...
Mento committed Jun 5, 2019
1 parent c3f5edf commit 0d29c31574239bc5f5ae41f3e6f6345f634fd3bb
Showing with 102 additions and 80 deletions.
  1. +60 −52 macosx/AppDelegate.m
  2. +1 −1 macosx/KeychainSupport.h
  3. +41 −27 macosx/KeychainSupport.m
@@ -146,49 +146,47 @@ - (void)applicationDidFinishLaunching:(NSNotification *)notification {

static int mac_cmd_handler (pinentry_t pe) {
@autoreleasepool {
int returnValue = -1;


// cacheId is used to save the passphrase in the macOS keychain.
NSString *cacheId = nil;
if (pe->keyinfo) {
cacheId = [NSString gpgStringWithCString:pe->keyinfo];
if (cacheId.length > 2) {
NSString *keyinfo = [NSString gpgStringWithCString:pe->keyinfo];
if (keyinfo.length > 2) {
// keyinfo has the form x/fingerprint. x is the cache mode it's one of u (user), s (ssh) or n (normal).
// Ignore cache_mode at the moment.
cacheId = [cacheId substringFromIndex:2];
} else {
cacheId = nil;
cacheId = [keyinfo substringFromIndex:2];
}
}

// cacheId is used to save the passphrase in the Mac OS X keychain.
if (cacheId && pe->pin) {
if (pe->error) {
storePassphraseInKeychain(cacheId, nil, nil);
} else {
const char *passphrase;
passphrase = [getPassphraseFromKeychain(cacheId) UTF8String];
if (passphrase) {
int len = strlen(passphrase);
pinentry_setbufferlen(pe, len + 1);
if (pe->pin) {
strcpy(pe->pin, passphrase);
return len;
} else {
return -1;
}
if (cacheId && pe->pin && !pe->error) {
// Search for a stored password in the macOS keychain.
const char *passphrase = [getPassphraseFromKeychain(cacheId) UTF8String];
if (passphrase) { // A password was found.
int len = strlen(passphrase);
pinentry_setbufferlen(pe, len + 1);
if (pe->pin) {
// Write the password into pe->pin and return its length.
strcpy(pe->pin, passphrase);
return len;
} else {
// That is bad! Could not allocate enough memory for the password.
return -1;
}
}
}


NSDictionary *dict = parseUserData(pe);
NSString *description = dict[@"description"];
NSString *keychainLabel = dict[@"keychainLabel"];
PinentryMac *pinentry = [[PinentryMac alloc] init];

NSDictionary *userData = parseUserData(pe);
NSString *description = userData[@"description"];

PinentryMac *pinentry = [[PinentryMac alloc] init];

pinentry.grab = pe->grab;
if (dict[@"icon"]) {
pinentry.icon = dict[@"icon"];
if (userData[@"icon"]) {
pinentry.icon = userData[@"icon"];
}
if (description) {
pinentry.descriptionText = [description stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
@@ -213,7 +211,7 @@ static int mac_cmd_handler (pinentry_t pe) {
if (pe->error) {
pinentry.errorText = [NSString gpgStringWithCString:pe->error];
}
if (pe->keyinfo) {
if (cacheId) {
pinentry.canUseKeychain = YES;
}
if (pe->repeat_passphrase) {
@@ -229,33 +227,36 @@ static int mac_cmd_handler (pinentry_t pe) {
};
}

if ([pinentry runModal] != 1) {
return -1;
}
if ([pinentry runModal] == 1) {
const char *passphrase = [pinentry.pin ? pinentry.pin : @"" UTF8String];
if (passphrase) {
int len = strlen(passphrase);
pinentry_setbufferlen(pe, len + 1);
if (pe->pin) {
// Write the password into pe->pin and return its length at the end of this method.
strcpy(pe->pin, passphrase);

const char *passphrase = [pinentry.pin ? pinentry.pin : @"" UTF8String];
if (!passphrase) {
return -1;
}
if (pe->repeat_passphrase) {
pe->repeat_okay = YES;
}

if (pinentry.saveInKeychain && cacheId) {
storePassphraseInKeychain(cacheId, pinentry.pin, keychainLabel);
returnValue = len;
}
}
}

int len = strlen(passphrase);
pinentry_setbufferlen(pe, len + 1);
if (pe->pin) {
strcpy(pe->pin, passphrase);

if (pe->repeat_passphrase) {
pe->repeat_okay = YES;
if (cacheId) { // Having a cacheId means, we can use the keychain.
NSString *keychainLabel = userData[@"keychainLabel"];
if (pinentry.saveInKeychain) {
// The user wants the password to be stored, do so.
storePassphraseInKeychain(cacheId, pinentry.pin, keychainLabel);
} else if (pe->error) {
// The last password return from pinentry was wrong.
// Remove a possible stored wrong password from the keychain.
storePassphraseInKeychain(cacheId, nil, keychainLabel);
}


return len;
}

return -1;
} else {
pinentry.confirmMode = YES;
pinentry.oneButton = pe->one_button;
@@ -265,13 +266,20 @@ static int mac_cmd_handler (pinentry_t pe) {

switch ([pinentry runModal]) {
case 1:
return 1;
returnValue = 1;
break;
case 2:
return 0;
returnValue = 0;
break;
default:
pe->canceled = 1;
returnValue = 0;
break;
}
pe->canceled = 1;
return 0;
}


return returnValue;
}
}

@@ -21,5 +21,5 @@

#import <Cocoa/Cocoa.h>

void storePassphraseInKeychain(NSString *fingerprint, NSString *passphrase, NSString *label);
BOOL storePassphraseInKeychain(NSString *fingerprint, NSString *passphrase, NSString *label);
NSString *getPassphraseFromKeychain(NSString *fingerprint);
@@ -24,50 +24,64 @@

#define GPG_SERVICE_NAME "GnuPG"

void storePassphraseInKeychain(NSString *fingerprint, NSString *passphrase, NSString *label) {

BOOL storePassphraseInKeychain(NSString *fingerprint, NSString *passphrase, NSString *label) {
OSStatus status;
SecKeychainItemRef itemRef = nil;
SecKeychainRef keychainRef = nil;

NSString *keychainPath = [[NSUserDefaults standardUserDefaults] valueForKey:@"KeychainPath"];
const char *path = keychainPath.UTF8String;


if (keychainPath.length) {
if (SecKeychainOpen(path, &keychainRef) != 0) {
return;
return NO;
}
} else if (SecKeychainCopyDefault(&keychainRef) != 0) {
return;
return NO;
}

NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
kSecClassGenericPassword, kSecClass,
@GPG_SERVICE_NAME, kSecAttrService,
fingerprint, kSecAttrAccount,
kCFBooleanTrue, kSecReturnRef,
keychainRef, kSecUseKeychain,
nil];

int status = SecItemCopyMatching((__bridge CFDictionaryRef)attributes, (CFTypeRef *)&itemRef);
if (status == 0) {
SecKeychainItemDelete(itemRef);
CFRelease(itemRef);
if (!label) {
label = @GPG_SERVICE_NAME;
}


if (passphrase) {
attributes = [NSDictionary dictionaryWithObjectsAndKeys:
kSecClassGenericPassword, kSecClass,
@GPG_SERVICE_NAME, kSecAttrService,
fingerprint, kSecAttrAccount,
[passphrase dataUsingEncoding:NSUTF8StringEncoding], kSecValueData,
label ? label : @GPG_SERVICE_NAME, kSecAttrLabel,
keychainRef, kSecUseKeychain,
nil];

SecItemAdd((__bridge CFDictionaryRef)attributes, nil);
NSData *encodedPassphrase = [passphrase dataUsingEncoding:NSUTF8StringEncoding];


NSDictionary *queryDict = @{(NSString *)kSecClass: (NSString *)kSecClassGenericPassword,
(NSString *)kSecAttrService: @GPG_SERVICE_NAME,
(NSString *)kSecAttrAccount: fingerprint,
(NSString *)kSecReturnRef: @YES,
(NSString *)kSecUseKeychain: (__bridge id)keychainRef};
CFDictionaryRef query = (__bridge CFDictionaryRef)queryDict;


if (encodedPassphrase) {
NSDictionary *attributesDict = @{(NSString *)kSecClass: (NSString *)kSecClassGenericPassword,
(NSString *)kSecAttrService: @GPG_SERVICE_NAME,
(NSString *)kSecAttrAccount: fingerprint,
(NSString *)kSecValueData: encodedPassphrase,
(NSString *)kSecAttrLabel: label,
(NSString *)kSecUseKeychain: (__bridge id)keychainRef};
CFDictionaryRef attributes = (__bridge CFDictionaryRef)attributesDict;


status = SecItemUpdate(query, attributes);
if (status == errSecItemNotFound) {
status = SecItemAdd(attributes, nil);
}
} else {
status = SecItemCopyMatching(query, (CFTypeRef *)&itemRef);
if (status == errSecSuccess) {
status = SecKeychainItemDelete(itemRef);
CFRelease(itemRef);
}
}

CFRelease(keychainRef);

return status == errSecSuccess;
}

NSString *getPassphraseFromKeychain(NSString *fingerprint) {

0 comments on commit 0d29c31

Please sign in to comment.
You can’t perform that action at this time.