/*- * Copyright (c) 2013 Kungliga Tekniska Högskolan * (Royal Institute of Technology, Stockholm, Sweden). * All rights reserved. * * Portions Copyright (c) 2013 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #import #import #import #import #import #import #import #import "HeimCredCoder.h" #import "common.h" @implementation HeimCredDecoder static uuid_t boolean_true = { 0xED, 0x43, 0xE0, 0xE6, 0x20, 0x15, 0x40, 0x43, 0xA9, 0x0C, 0xE7, 0x30, 0x6C, 0x9D, 0xED, 0x6B }; static uuid_t boolean_false = { 0x48, 0xD3, 0x95, 0xCD, 0x37, 0xC7, 0x41, 0x76, 0xA0, 0x97, 0x9B, 0x44, 0x9E, 0x2C, 0x10, 0x75 }; + (CFTypeRef)copyNS2CF:(id)ns { if (!ns) return NULL; if ([ns isKindOfClass:[NSString class]] || [ns isKindOfClass:[NSData class]] || [ns isKindOfClass:[NSNumber class]]) { return (CFTypeRef)[ns copy]; } if ([ns isKindOfClass:[NSUUID class]]) { union { CFUUIDBytes bytes; uuid_t uuid; } u; [ns getUUIDBytes:u.uuid]; if (uuid_compare(u.uuid, boolean_false) == 0) return kCFBooleanFalse; else if (uuid_compare(u.uuid, boolean_true) == 0) return kCFBooleanTrue; return CFUUIDCreateFromUUIDBytes(NULL, u.bytes); } if ([ns isKindOfClass:[NSArray class]]) { CFIndex n, len = [ns count]; CFMutableArrayRef array = CFArrayCreateMutable(NULL, len, &kCFTypeArrayCallBacks); if (!array) return NULL; for (n = 0; n < len; n++) { CFTypeRef obj = [HeimCredDecoder copyNS2CF:[ns objectAtIndex:n]]; if (obj) CFArrayAppendValue(array, obj); CFRELEASE_NULL(obj); } return array; } if ([ns isKindOfClass:[NSDictionary class]]) { CFIndex len = [ns count]; CFMutableDictionaryRef dict = CFDictionaryCreateMutable(NULL, len, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (!dict) return NULL; [ns enumerateKeysAndObjectsUsingBlock:^void(id key, id value, BOOL *stop) { CFTypeRef cfkey = [HeimCredDecoder copyNS2CF:key]; CFTypeRef cfvalue = [HeimCredDecoder copyNS2CF:value]; if (cfkey && cfvalue) CFDictionarySetValue(dict, cfkey, cfvalue); CFRELEASE_NULL(cfkey); CFRELEASE_NULL(cfvalue); }]; return dict; } return NULL; } static void convertDict(const void *key, const void *value, void *context) { NSMutableDictionary *dict = context; id nskey = [HeimCredDecoder copyCF2NS:key]; id nsvalue = [HeimCredDecoder copyCF2NS:value]; if (nskey && nsvalue) [dict setObject:nsvalue forKey:nskey]; [nskey release]; [nsvalue release]; } + (id)copyCF2NS:(CFTypeRef)cf { if (!cf) return nil; CFTypeID type = CFGetTypeID(cf); if (type == CFBooleanGetTypeID()) { if (CFBooleanGetValue(cf)) return [[NSUUID alloc] initWithUUIDBytes:boolean_true]; else return [[NSUUID alloc] initWithUUIDBytes:boolean_false]; } if (type == CFStringGetTypeID()) return (id)CFStringCreateCopy(NULL, cf); if (type == CFDataGetTypeID()) return (id)CFDataCreateCopy(NULL, cf); if (type == CFNumberGetTypeID()) return (id)CFRetain(cf); if (type == CFUUIDGetTypeID()) { union { CFUUIDBytes bytes; uuid_t uuid; } u; u.bytes = CFUUIDGetUUIDBytes(cf); return [[NSUUID alloc] initWithUUIDBytes:u.uuid]; } if (type == CFArrayGetTypeID()) { CFIndex n, len = CFArrayGetCount(cf); NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:len]; if (!array) return NULL; for (n = 0; n < len; n++) { id obj = [HeimCredDecoder copyCF2NS:CFArrayGetValueAtIndex(cf, n)]; if (obj) [array addObject:obj]; [obj release]; } return array; } if (type == CFDictionaryGetTypeID()) { CFIndex len = CFDictionaryGetCount(cf); NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity:len]; if (!dict) return NULL; CFDictionaryApplyFunction(cf, convertDict, dict); return dict; } return NULL; } static NSData * ksEncryptData(NSData *plainText) { NSMutableData *blob = NULL; const uint32_t bulkKeySize = 32; /* Use 256 bit AES key for bulkKey. */ const uint32_t maxKeyWrapOverHead = 8 + 32; uint8_t bulkKey[bulkKeySize]; uint8_t bulkKeyWrapped[bulkKeySize + maxKeyWrapOverHead]; size_t bulkKeyWrappedSize = sizeof(bulkKeyWrapped); uint32_t key_wrapped_size; CCCryptorStatus ccerr; if (![plainText isKindOfClass:[NSData class]]) abort(); size_t ctLen = [plainText length]; size_t tagLen = 16; if (SecRandomCopyBytes(kSecRandomDefault, bulkKeySize, bulkKey)) abort(); #if 0 /* Now that we're done using the bulkKey, in place encrypt it. */ require_noerr_quiet(error = ks_crypt(kAppleKeyStoreKeyWrap, keybag, keyclass, bulkKeySize, bulkKey, bulkKeyWrapped, &bulkKeyWrappedSize), out); #else bulkKeyWrappedSize = bulkKeySize; memcpy(bulkKeyWrapped, bulkKey, bulkKeySize); #endif key_wrapped_size = (uint32_t)bulkKeyWrappedSize; size_t blobLen = sizeof(key_wrapped_size) + key_wrapped_size + ctLen + tagLen; blob = [NSMutableData dataWithLength:blobLen]; if (blob == NULL) return NULL; UInt8 *cursor = [blob mutableBytes]; *((uint32_t *)cursor) = key_wrapped_size; cursor += sizeof(key_wrapped_size); memcpy(cursor, bulkKeyWrapped, key_wrapped_size); cursor += key_wrapped_size; ccerr = CCCryptorGCM(kCCEncrypt, kCCAlgorithmAES128, bulkKey, bulkKeySize, NULL, 0, /* iv */ NULL, 0, /* auth data */ [plainText bytes], ctLen, cursor, cursor + ctLen, &tagLen); memset(bulkKey, 0, sizeof(bulkKey)); if (ccerr || tagLen != 16) { [blob release]; return NULL; } return blob; } static int ks_decrypt_data(CFDataRef blob, CFDataRef *pPlainText) { const uint32_t bulkKeySize = 32; /* Use 256 bit AES key for bulkKey. */ uint8_t bulkKey[bulkKeySize]; int error = EINVAL; CCCryptorStatus ccerr; CFMutableDataRef plainText = NULL; if (CFGetTypeID(blob) != CFDataGetTypeID()) return EINVAL; size_t blobLen = CFDataGetLength(blob); const uint8_t *cursor = CFDataGetBytePtr(blob); uint32_t wrapped_key_size; size_t ctLen = blobLen; size_t tagLen = 16; if (ctLen < tagLen) return EINVAL; ctLen -= tagLen; uint8_t tag[tagLen]; if (ctLen < sizeof(wrapped_key_size)) return EINVAL; wrapped_key_size = *((uint32_t *)cursor); cursor += sizeof(wrapped_key_size); ctLen -= sizeof(wrapped_key_size); /* Validate key wrap length against total length */ if (ctLen < wrapped_key_size) return EINVAL; #if 0 size_t bulkKeyCapacity = sizeof(bulkKey); /* Now unwrap the bulk key using a key in the keybag. */ require_noerr_quiet(error = ks_crypt(kAppleKeyStoreKeyUnwrap, keybag, keyclass, wrapped_key_size, cursor, bulkKey, &bulkKeyCapacity), out); #else if (bulkKeySize != wrapped_key_size) { error = EINVAL; goto out; } memcpy(bulkKey, cursor, wrapped_key_size); #endif cursor += wrapped_key_size; ctLen -= wrapped_key_size; plainText = CFDataCreateMutable(NULL, ctLen); if (!plainText) { error = EINVAL; goto out; } CFDataSetLength(plainText, ctLen); /* Decrypt the cipherText with the bulkKey. */ ccerr = CCCryptorGCM(kCCDecrypt, kCCAlgorithmAES128, bulkKey, bulkKeySize, NULL, 0, /* iv */ NULL, 0, /* auth data */ cursor, ctLen, CFDataGetMutableBytePtr(plainText), tag, &tagLen); if (ccerr) { error = EINVAL; goto out; } if (tagLen != 16) { error = EINVAL; goto out; } cursor += ctLen; if (memcmp(tag, cursor, tagLen)) { error = EINVAL; goto out; } error = 0; out: memset(bulkKey, 0, bulkKeySize); if (error) { CFRELEASE_NULL(plainText); } else { *pPlainText = plainText; } return error; } + (id) copyUnarchiveObjectWithFileSecureEncoding:(NSString *)path { @autoreleasepool { CFDataRef clearText = NULL; NSData *data = [NSData dataWithContentsOfFile:path options:NSDataReadingMappedIfSafe error:NULL]; if (data == nil) return NULL; int error = ks_decrypt_data((CFDataRef)data, &clearText); if (error) return NULL; HeimCredDecoder *decoder = [[HeimCredDecoder alloc] initForReadingWithData:(NSData *)clearText]; if (decoder == nil) { CFRelease(clearText); return NULL; } [decoder setRequiresSecureCoding:true]; id obj = [[decoder decodeObjectForKey:NSKeyedArchiveRootObjectKey] retain]; [decoder finishDecoding]; [decoder release]; CFRelease(clearText); return obj; } } + (void)archiveRootObject:(id)object toFile:(NSString *)archiveFile { @autoreleasepool { NSData *data = [NSKeyedArchiver archivedDataWithRootObject:object]; if (data == nil) return; NSData *encText = ksEncryptData(data); if (encText == NULL) return; [encText writeToFile:archiveFile atomically:NO]; } } - (NSSet *)allowedClasses { static dispatch_once_t onceToken; static NSSet *_set; dispatch_once(&onceToken, ^{ _set = [[NSSet alloc] initWithObjects:[NSMutableDictionary class], [NSDictionary class], [NSMutableArray class], [NSArray class], [NSMutableString class], [NSString class], [NSNumber class], [NSUUID class], [NSMutableData class], [NSData class], nil]; }); return [[_set retain] autorelease]; } @end