Skip to content

Instantly share code, notes, and snippets.

@paulmelnikow
Last active March 27, 2021 23:31
Show Gist options
  • Save paulmelnikow/4645764 to your computer and use it in GitHub Desktop.
Save paulmelnikow/4645764 to your computer and use it in GitHub Desktop.
Working Objective-C keychain library
NSString *const kKeychainErrorDomain = @"kKeychainErrorDomain"
NSString * ServiceName = @"My Service";
+ (NSError *) errorWithStatus:(OSStatus) status {
if (errSecSuccess == status) return nil;
#if __has_feature(objc_arc)
NSString *message = (__bridge_transfer NSString *)SecCopyErrorMessageString(status, NULL);
#else
NSString *message = [(id) SecCopyErrorMessageString(status, NULL) autorelease];
#endif
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:message
forKey:NSLocalizedDescriptionKey];
return [NSError errorWithDomain:kKeychainErrorDomain
code:status
userInfo:userInfo];
}
+ (NSString *) keychainAccountNameForUserName:(NSString *) userName serverName:(NSString *) serverName {
return [NSString stringWithFormat:@"%@@%@", userName, serverName];
}
+ (NSString *) passwordForUserName:(NSString *) userName serverName:(NSString *) serverName error:(NSError * __autoreleasing *) error {
NSString *accountName = [self.class keychainAccountNameForUserName:userName serverName:serverName];
void * resultBytes = NULL; UInt32 resultLength = 0;
OSStatus status = SecKeychainFindGenericPassword(NULL,
(UInt32)ServiceName.length,
[ServiceName cStringUsingEncoding:NSUTF8StringEncoding],
(UInt32)accountName.length,
[accountName cStringUsingEncoding:NSUTF8StringEncoding],
&resultLength,
&resultBytes,
NULL);
if (errSecItemNotFound == status) {
return nil;
} else if (errSecSuccess != status) {
if (error) *error = [self errorWithStatus:status];
return nil;
}
NSString *result = [[NSString alloc] initWithBytes:resultBytes
length:resultLength
encoding:NSUTF8StringEncoding];
SecKeychainItemFreeContent(NULL, resultBytes);
return result;
}
+ (BOOL) savePassword:(NSString *) password forUserName:(NSString *) userName serverName:(NSString *) serverName error:(NSError * __autoreleasing *) error {
NSString *accountName = [self.class keychainAccountNameForUserName:userName serverName:serverName];
if (!password || !accountName) return NO;
OSStatus status = SecKeychainAddGenericPassword(NULL,
(UInt32)ServiceName.length,
[ServiceName cStringUsingEncoding:NSUTF8StringEncoding],
(UInt32)accountName.length,
[accountName cStringUsingEncoding:NSUTF8StringEncoding],
(UInt32)password.length,
[password cStringUsingEncoding:NSUTF8StringEncoding],
NULL);
if (errSecSuccess == status) return YES;
if (errSecDuplicateItem == status) {
SecKeychainItemRef keyChainItem;
status = SecKeychainFindGenericPassword(NULL,
(UInt32)ServiceName.length,
[ServiceName cStringUsingEncoding:NSUTF8StringEncoding],
(UInt32)accountName.length,
[accountName cStringUsingEncoding:NSUTF8StringEncoding],
NULL,
NULL,
&keyChainItem);
if (errSecSuccess == status) {
status = SecKeychainItemModifyAttributesAndData(keyChainItem,
NULL,
(UInt32)password.length,
[password cStringUsingEncoding:NSUTF8StringEncoding]);
CFRelease(keyChainItem);
if (errSecSuccess == status) return YES;
}
}
if (error) *error = [self errorWithStatus:status];
return NO;
}
+ (BOOL) clearPasswordForUserName:(NSString *) userName serverName:(NSString *) serverName error:(NSError * __autoreleasing *) error {
NSString *accountName = [self.class keychainAccountNameForUserName:userName serverName:serverName];
if (!accountName) return NO;
SecKeychainItemRef keyChainItem;
OSStatus status = SecKeychainFindGenericPassword(NULL,
(UInt32)ServiceName.length,
[ServiceName cStringUsingEncoding:NSUTF8StringEncoding],
(UInt32)accountName.length,
[accountName cStringUsingEncoding:NSUTF8StringEncoding],
NULL,
NULL,
&keyChainItem);
if (errSecSuccess == status) {
status = SecKeychainItemDelete(keyChainItem);
CFRelease(keyChainItem);
if (errSecSuccess == status) return YES;
}
if (error) *error = [self errorWithStatus:status];
return NO;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment