//
// MyKeychain.m
// KCSync
//
// Created by John Hurley on 10/3/12.
// Copyright (c) 2012 john. All rights reserved.
//
#import "MyKeychain.h"
#import <Security/Security.h>
#import <Security/SecItemPriv.h>
#import <utilities/debugging.h>
const NSString *kItemPasswordKey = @"ItemPasswordKey";
const NSString *kItemAccountKey = @"ItemAccountKey";
const NSString *kItemNameKey = @"ItemNameKey";
#define KCSCOPE "mykeychain"
// secdebug(KCSCOPE,
@implementation MyKeychain
static MyKeychain *sharedInstance = nil;
+ (MyKeychain *) sharedInstance
{
if (!sharedInstance)
sharedInstance = [[self alloc] init];
return sharedInstance;
}
// Translate status messages into return strings
- (NSString *) fetchStatus : (OSStatus) status
{
switch (status)
{
case 0:
return(@"Success!");
case errSecNotAvailable:
return(@"No trust results are available.!");
case errSecItemNotFound:
return(@"The item cannot be found.");
case errSecParam:
return(@"Parameter error.");
case errSecAllocate:
return(@"Memory allocation error. Failed to allocate memory.");
case errSecInteractionNotAllowed:
return(@"User interaction is not allowed.");
case errSecUnimplemented:
return(@"Function is not implemented");
case errSecDuplicateItem:
return(@"The item already exists.");
case errSecDecode:
return(@"Unable to decode the provided data.");
default:
return([NSString stringWithFormat:@"Function returned: break;
}
return @"can't happen...";
}
#define ACCOUNT @"Keychain Sync Test Account"
#define SERVICE @"Keychain Sync Test Service"
#define PWKEY @"Keychain Sync Test Password Data"
// Return a base dictionary
- (NSMutableDictionary *) baseDictionary
{
NSMutableDictionary *md = [[NSMutableDictionary alloc] init];
// Password identification keys
NSData *identifier = [PWKEY dataUsingEncoding:NSUTF8StringEncoding];
[md setObject:identifier forKey:(__bridge id)kSecAttrGeneric];
[md setObject:ACCOUNT forKey:(__bridge id)kSecAttrAccount];
[md setObject:SERVICE forKey:(__bridge id)kSecAttrService];
[md setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
[md setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecAttrSynchronizable];
return md;
}
// Return a keychain-style dictionary populated with the password
- (NSMutableDictionary *) buildDictForPassword:(NSString *) password
{
NSMutableDictionary *passwordDict = [self baseDictionary];
// Add the password
NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
[passwordDict setObject:passwordData forKey:(__bridge id)kSecValueData]; // password
return passwordDict;
}
// Build a search query based
- (NSMutableDictionary *) buildSearchQuery
{
NSMutableDictionary *genericPasswordQuery = [self baseDictionary];
// Add the search constraints
[genericPasswordQuery setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
[genericPasswordQuery setObject:(__bridge id)kCFBooleanTrue
forKey:(__bridge id)kSecReturnAttributes];
[genericPasswordQuery setObject:(__bridge id)kCFBooleanTrue
forKey:(__bridge id)kSecReturnData];
return genericPasswordQuery;
}
// retrieve data dictionary from the keychain
- (NSMutableArray *) fetchDictionaryWithQuery:(NSMutableDictionary *)query
{
NSMutableDictionary *genericPasswordQuery = query;
// secerror("Query: CFTypeRef cfresult = nil;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)genericPasswordQuery, &cfresult);
// secerror( "FETCH:
if (status == errSecItemNotFound)
return NULL;
if (CFGetTypeID(cfresult) == CFArrayGetTypeID())
{
NSMutableArray *result = [NSMutableArray arrayWithCapacity:0];
[result addObjectsFromArray:CFBridgingRelease(cfresult)];
return result;
}
// If it is a single result, embed it in an array because callers expect it
if (CFGetTypeID(cfresult) == CFDictionaryGetTypeID())
return [NSMutableArray arrayWithObject:CFBridgingRelease(cfresult)];
return NULL;
}
- (NSMutableArray *)fetchDictionary
{
return [self fetchDictionaryWithQuery:[self buildSearchQuery]];
}
// create a new keychain entry
- (BOOL) createKeychainValue:(NSString *) password
{
NSMutableDictionary *md = [self buildDictForPassword:password];
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)md, NULL);
secerror( "CREATE:
if (status == errSecSuccess) return YES; else return NO;
}
// remove a keychain entry
- (void) clearKeychain
{
NSMutableDictionary *genericPasswordQuery = [self baseDictionary];
OSStatus status = SecItemDelete((__bridge CFDictionaryRef) genericPasswordQuery);
secerror( "DELETE: }
// update a keychain entry
- (BOOL) updateKeychainValue:(NSString *)password
{
NSMutableDictionary *genericPasswordQuery = [self baseDictionary];
NSMutableDictionary *attributesToUpdate = [[NSMutableDictionary alloc] init];
NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
[attributesToUpdate setObject:passwordData forKey:(__bridge id)kSecValueData];
OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)genericPasswordQuery, (__bridge CFDictionaryRef)attributesToUpdate);
secerror( "UPDATE:
if (status == 0) return YES; else return NO;
}
// fetch a keychain value
- (NSString *) fetchPassword
{
NSMutableArray *allItems = [self fetchDictionary];
if (!allItems)
return NULL;
// This is used for a single item, so take first item in array
NSData *passData = [allItems[0] objectForKey:(__bridge id)kSecValueData];
if (passData)
return [[NSString alloc] initWithData:passData encoding:NSUTF8StringEncoding];
return NULL;
}
- (void)setPassword: (NSString *) thePassword
{
if (![self createKeychainValue:thePassword])
[self updateKeychainValue:thePassword];
}
- (void)setItem:(NSDictionary *)newItem
{
OSStatus status = errSecSuccess;
NSMutableDictionary *passwordDict = [self baseDictionary];
// Add the password
NSData *passwordData = [[newItem objectForKey:kItemPasswordKey] dataUsingEncoding:NSUTF8StringEncoding];
[passwordDict setObject:passwordData forKey:(__bridge id)kSecValueData]; // password
[passwordDict setObject:[newItem objectForKey:kItemAccountKey] forKey:(__bridge id)kSecAttrAccount];
[passwordDict setObject:[newItem objectForKey:kItemNameKey] forKey:(__bridge id)kSecAttrService];
// Try to add first; if error, then try to update
status = SecItemAdd((__bridge CFDictionaryRef)passwordDict, NULL);
secerror( "SecItemAdd result: NSLog(@"SecItemAdd result:
if (status)
{
// Try update
// We only want to update the password data, so delete the other keys
// NSArray *keysToRemove = [NSArray arrayWithObjects:kItemAccountKey, kItemNameKey, nil];
[passwordDict removeObjectsForKeys:@[(__bridge id)kSecValueData]];
NSDictionary *itemsToUpdate = @{ (__bridge id)kSecValueData : passwordData };
// [genericPasswordQuery setObject:[newItem objectForKey:kItemAccountKey] forKey:(__bridge id)kSecAttrAccount];
// [genericPasswordQuery setObject:[newItem objectForKey:kItemNameKey] forKey:(__bridge id)kSecAttrService];
status = SecItemUpdate((__bridge CFDictionaryRef)passwordDict, (__bridge CFDictionaryRef)(itemsToUpdate));
secerror( "SecItemUpdate result: NSLog(@"SecItemUpdate result: }
}
- (NSMutableArray *)fetchDictionaryAll
{
NSMutableDictionary *query = [[NSMutableDictionary alloc] init];
[query setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
[query setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecAttrSynchronizable];
// Add the search constraints
[query setObject:(__bridge id)kSecMatchLimitAll forKey:(__bridge id)kSecMatchLimit];
[query setObject:(__bridge id)kCFBooleanTrue
forKey:(__bridge id)kSecReturnAttributes];
[query setObject:(__bridge id)kCFBooleanTrue
forKey:(__bridge id)kSecReturnData];
NSMutableArray *genericItems = [self fetchDictionaryWithQuery:query];
// Now look for internet items
[query setObject:(__bridge id)kSecClassInternetPassword forKey:(__bridge id)kSecClass];
NSMutableArray *internetItems = [self fetchDictionaryWithQuery:query];
if (internetItems)
[genericItems addObjectsFromArray:internetItems];
return genericItems;
}
// MARK: ----- Full routines -----
// Return a base dictionary
- (NSMutableDictionary *) baseDictionaryFull:(NSString *)account service:(NSString *)service
{
NSMutableDictionary *md = [[NSMutableDictionary alloc] init];
// Password identification keys
NSData *identifier = [PWKEY dataUsingEncoding:NSUTF8StringEncoding];
[md setObject:identifier forKey:(__bridge id)kSecAttrGeneric];
[md setObject:account forKey:(__bridge id)kSecAttrAccount];
[md setObject:service forKey:(__bridge id)kSecAttrService];
[md setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
[md setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecAttrSynchronizable];
// return [md autorelease];
return md;
}
- (BOOL) createKeychainValueFull:(NSString *)account service:(NSString *)service password:(NSString *)password
{
NSMutableDictionary *md = [self buildDictForPasswordFull:account service:service password:password];
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)md, NULL);
secerror( "CREATE:
if (status == errSecSuccess) return YES; else return NO;
}
- (BOOL) updateKeychainValueFull:(NSString *)account service:(NSString *)service password:(NSString *)password
{
NSMutableDictionary *genericPasswordQuery = [self baseDictionaryFull:account service:service];
NSMutableDictionary *attributesToUpdate = [[NSMutableDictionary alloc] init];
NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
[attributesToUpdate setObject:passwordData forKey:(__bridge id)kSecValueData];
OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)genericPasswordQuery, (__bridge CFDictionaryRef)attributesToUpdate);
secerror( "UPDATE:
if (status == 0) return YES; else return NO;
}
- (void)setPasswordFull:(NSString *)account service:(NSString *)service password:(NSString *) thePassword
{
secerror( "setPasswordFull account: if (![self createKeychainValueFull:account service:service password:thePassword])
[self updateKeychainValueFull:account service:service password:thePassword];
}
// Return a keychain-style dictionary populated with the password
- (NSMutableDictionary *) buildDictForPasswordFull:(NSString *)account service:(NSString *)service password:(NSString *)password
{
NSMutableDictionary *passwordDict = [self baseDictionaryFull:account service:service];
// Add the password
NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
[passwordDict setObject:passwordData forKey:(__bridge id)kSecValueData]; // password
return passwordDict;
}
- (void)clearAllKeychainItems
{
CFIndex ix, top;
OSStatus status = errSecSuccess;
NSArray *allItems = (NSArray *)[[MyKeychain sharedInstance] fetchDictionaryAll];
top = [allItems count];
secerror( "Deleting
for (ix=0; ix<top && !status; ix++)
{
NSDictionary *item = [allItems objectAtIndex:ix];
NSMutableDictionary *query = [[NSMutableDictionary alloc] init];
NSData *identifier = [PWKEY dataUsingEncoding:NSUTF8StringEncoding];
[query setObject:identifier forKey:(__bridge id)kSecAttrGeneric];
[query setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
[query setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecAttrSynchronizable];
[query setObject:[item objectForKey:(__bridge id)(kSecAttrAccount)] forKey:(__bridge id)kSecAttrAccount];
[query setObject:[item objectForKey:(__bridge id)(kSecAttrService)] forKey:(__bridge id)kSecAttrService];
status = SecItemDelete((__bridge CFDictionaryRef) query);
secerror( "DELETE: }
}
@end