mirror of
https://github.com/samsonjs/arq_restore.git
synced 2026-03-25 09:25:53 +00:00
271 lines
10 KiB
Objective-C
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
|