arq_restore/AppKeychain.m
2011-08-16 15:19:44 -04:00

271 lines
10 KiB
Objective-C

//
// AppKeychain.m
// Backup
//
// Created by Stefan Reitshamer on 8/26/09.
// Copyright 2009 PhotoMinds LLC. All rights reserved.
//
#import "AppKeychain.h"
#import "SetNSError.h"
#import "UserLibrary.h"
#define ARQ_S3_LABEL @"Arq S3"
#define ARQ_TWITTER_LABEL @"Arq Twitter Access Token"
#define ARQ_ENCRYPTION_ACCOUNT_LABEL @"EncryptionKey"
@interface AppKeychain (internal)
+ (NSString *)encryptionPasswordLabelForS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID;
+ (BOOL)account:(NSString **)account password:(NSString **)password forLabel:(NSString *)label error:(NSError **)error;
+ (BOOL)findItem:(NSString *)theLabel item:(SecKeychainItemRef *)item error:(NSError **)error;
- (BOOL)loadSecAccess:(NSError **)error;
- (BOOL)deleteLabel:(NSString *)theLabel error:(NSError **)error;
- (BOOL)addLabel:(NSString *)theLabel account:(NSString *)theAccount password:(NSString *)thePassword error:(NSError **)error;
@end
@implementation AppKeychain
+ (BOOL)accessKeyID:(NSString **)accessKey secretAccessKey:(NSString **)secret error:(NSError **)error {
NSString *account = nil;
NSString *password = nil;
if (![AppKeychain account:&account password:&password forLabel:ARQ_S3_LABEL error:error]) {
return NO;
}
if (accessKey != nil) {
*accessKey = account;
}
if (secret != nil) {
*secret = password;
}
return YES;
}
+ (BOOL)containsEncryptionPasswordForS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID {
NSString *encryptionPassword = nil;
NSError *myError = nil;
return [AppKeychain encryptionPassword:&encryptionPassword forS3BucketName:theS3BucketName computerUUID:theComputerUUID error:&myError];
}
+ (BOOL)encryptionPassword:(NSString **)encryptionPassword forS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID error:(NSError **)error {
NSString *label = [AppKeychain encryptionPasswordLabelForS3BucketName:theS3BucketName computerUUID:theComputerUUID];
NSString *account = nil;
NSString *password = nil;
if (![AppKeychain account:&account password:&password forLabel:label error:error]) {
return NO;
}
if (encryptionPassword != nil) {
*encryptionPassword = password;
}
return YES;
}
+ (BOOL)twitterAccessKey:(NSString **)theAccessKey secret:(NSString **)secret error:(NSError **)error {
return [AppKeychain account:theAccessKey password:secret forLabel:ARQ_TWITTER_LABEL error:error];
}
- (id)initWithBackupAppPath:(NSString *)theBackupAppPath
agentAppPath:(NSString *)theAgentAppPath {
if (self = [super init]) {
backupAppPath = [theBackupAppPath copy];
agentAppPath = [theAgentAppPath copy];
}
return self;
}
- (void)dealloc {
if (access != NULL) {
CFRelease(access);
}
[backupAppPath release];
[agentAppPath release];
[super dealloc];
}
- (BOOL)setAccessKeyID:(NSString *)theAccessKeyID
secretAccessKey:(NSString *)theSecretAccessKey
error:(NSError **)error {
return [self addLabel:ARQ_S3_LABEL account:theAccessKeyID password:theSecretAccessKey error:error];
}
- (BOOL)setEncryptionKey:(NSString *)theEncryptionPassword
forS3BucketName:(NSString *)theS3BucketName
computerUUID:(NSString *)theComputerUUID
error:(NSError **)error {
NSString *label = [AppKeychain encryptionPasswordLabelForS3BucketName:theS3BucketName computerUUID:theComputerUUID];
return [self addLabel:label account:ARQ_ENCRYPTION_ACCOUNT_LABEL password:theEncryptionPassword error:error];
}
- (BOOL)setTwitterAccessKey:(NSString *)theKey secret:(NSString *)theSecret error:(NSError **)error {
return [self addLabel:ARQ_TWITTER_LABEL account:theKey password:theSecret error:error];
}
- (BOOL)deleteTwitterAccessKey:(NSError **)error {
return [self deleteLabel:ARQ_TWITTER_LABEL error:error];
}
@end
@implementation AppKeychain (internal)
+ (NSString *)encryptionPasswordLabelForS3BucketName:(NSString *)theS3BucketName computerUUID:(NSString *)theComputerUUID {
return [NSString stringWithFormat:@"Arq Encryption:%@:%@", theS3BucketName, theComputerUUID];
}
+ (BOOL)account:(NSString **)account password:(NSString **)password forLabel:(NSString *)theLabel error:(NSError **)error {
SecKeychainItemRef item = NULL;
if (![AppKeychain findItem:theLabel item:&item error:error]) {
return NO;
}
if (item == NULL) {
SETNSERROR(@"AppKeychainErrorDomain", -1, @"Keychain item %@ not found", theLabel);
return NO;
}
SecKeychainAttributeList *outAttrList = NULL;
UInt32 length;
void *data;
UInt32 tags[] = {
kSecAccountItemAttr
};
UInt32 formats[] = {
CSSM_DB_ATTRIBUTE_FORMAT_STRING
};
SecKeychainAttributeInfo info = {
1,
tags,
formats
};
OSStatus oss = SecKeychainItemCopyAttributesAndData(item, &info, NULL, &outAttrList, &length, &data);
if (oss != noErr) {
NSString *errorMessage = (NSString *)SecCopyErrorMessageString(oss, NULL);
SETNSERROR(@"AppKeychainErrorDomain", -1, @"Error reading data from Keychain item %@: %@ (code %d)", theLabel, errorMessage, oss);
[errorMessage release];
return NO;
}
if (account != nil) {
*account = nil;
for (UInt32 index = 0; index < outAttrList->count; index++) {
SecKeychainAttribute *attr = outAttrList->attr + index;
if (attr->tag == kSecAccountItemAttr) {
*account = [[[NSString alloc] initWithBytes:attr->data length:attr->length encoding:NSUTF8StringEncoding] autorelease];
break;
}
}
}
if (password != nil) {
*password = [[[NSString alloc] initWithBytes:data length:length encoding:NSUTF8StringEncoding] autorelease];
}
SecKeychainItemFreeAttributesAndData(outAttrList, data);
CFRelease(item);
return YES;
}
+ (BOOL)findItem:(NSString *)theLabel item:(SecKeychainItemRef *)item error:(NSError **)error {
*item = NULL;
const char *label = [theLabel UTF8String];
SecKeychainAttribute attrs[] = {
{
kSecLabelItemAttr,
strlen(label),
(char *)label
}
};
SecKeychainAttributeList attributes = {
sizeof(attrs) / sizeof(attrs[0]),
attrs
};
SecKeychainSearchRef searchRef;
OSStatus oss;
oss = SecKeychainSearchCreateFromAttributes(NULL, kSecGenericPasswordItemClass, &attributes, &searchRef);
if (oss != noErr) {
SETNSERROR(@"AppKeychainErrorDomain", -1, @"error creating keychain search");
return NO;
}
SecKeychainSearchCopyNext(searchRef, item);
CFRelease(searchRef);
return YES;
}
- (BOOL)loadSecAccess:(NSError **)error {
if (access == NULL) {
NSMutableArray *trustedApplications = [NSMutableArray array];
OSStatus oss;
SecTrustedApplicationRef agentApp;
oss = SecTrustedApplicationCreateFromPath([agentAppPath fileSystemRepresentation], &agentApp);
if (oss != noErr) {
CFStringRef msg = SecCopyErrorMessageString(oss, NULL);
SETNSERROR(@"AppKeychainErrorDomain", -1, @"Error creating Agent trusted application %@: %@ (code %d)", agentAppPath, (NSString *)msg, oss);
CFRelease(msg);
return NO;
}
[trustedApplications addObject:(id)agentApp];
CFRelease(agentApp);
SecTrustedApplicationRef backupApp;
oss = SecTrustedApplicationCreateFromPath([backupAppPath fileSystemRepresentation], &backupApp);
if (oss != noErr) {
CFStringRef msg = SecCopyErrorMessageString(oss, NULL);
SETNSERROR(@"AppKeychainErrorDomain", -1, @"Error creating trusted application: %@ (code %d)", (NSString *)msg, oss);
CFRelease(agentApp);
CFRelease(msg);
return NO;
}
[trustedApplications addObject:(id)backupApp];
CFRelease(backupApp);
oss = SecAccessCreate((CFStringRef)@"Arq", (CFArrayRef)trustedApplications, &access);
if (oss != noErr) {
CFStringRef msg = SecCopyErrorMessageString(oss, NULL);
SETNSERROR(@"AppKeychainErrorDomain", -1, @"Error creating SecAccessRef: %@ (code %d)", (NSString *)msg, oss);
CFRelease(msg);
return NO;
}
}
return YES;
}
- (BOOL)deleteLabel:(NSString *)theLabel error:(NSError **)error {
SecKeychainItemRef item = NULL;
if (![AppKeychain findItem:theLabel item:&item error:error]) {
return NO;
}
if (item != NULL) {
OSStatus oss = SecKeychainItemDelete(item);
CFRelease(item);
if (oss != noErr) {
NSString *errorMessage = (NSString *)SecCopyErrorMessageString(oss, NULL);
SETNSERROR(@"AppKeychainErrorDomain", -1, @"error deleting item for label %@: %@ (code %d)", theLabel, errorMessage, oss);
[errorMessage release];
return NO;
}
}
return YES;
}
- (BOOL)addLabel:(NSString *)theLabel account:(NSString *)theAccount password:(NSString *)thePassword error:(NSError **)error {
if (![self loadSecAccess:error]) {
return NO;
}
if (![self deleteLabel:theLabel error:error]) {
return NO;
}
const char *label = [theLabel UTF8String];
const char *account = [theAccount UTF8String];
const char *password = [thePassword UTF8String];
SecKeychainAttribute attrs[] = {
{
kSecLabelItemAttr,
strlen(label),
(char *)label
},
{
kSecAccountItemAttr,
strlen(account),
(char *)account
},
{
kSecServiceItemAttr,
strlen(label),
(char *)label
}
};
SecKeychainAttributeList attributes = {
sizeof(attrs) / sizeof(attrs[0]),
attrs
};
OSStatus oss = SecKeychainItemCreateFromContent(kSecGenericPasswordItemClass, &attributes, strlen(password), password, NULL, access, NULL);
if (oss != noErr) {
NSString *errorMessage = (NSString *)SecCopyErrorMessageString(oss, NULL);
SETNSERROR(@"AppKeychainErrorDomain", -1, @"Error creating keychain item: %@ (code %d)", errorMessage, oss);
[errorMessage release];
return NO;
}
return YES;
}
@end