KeychainStasher.m
1 #import <xpc/private.h> 2 3 #import "utilities/debugging.h" 4 #import <Security/SecItemPriv.h> 5 #import "LocalKeychainAnalytics.h" 6 7 #import "KeychainStasher.h" 8 9 NSString* const kApplicationIdentifier = @"com.apple.security.KeychainStasher"; 10 11 @implementation KeychainStasher 12 13 - (NSError*)errorWithStatus:(OSStatus)status message:(NSString*)format, ... NS_FORMAT_FUNCTION(2, 3) 14 { 15 if (status == errSecSuccess) { 16 return nil; 17 } 18 19 NSString* desc; 20 if (format) { 21 va_list ap; 22 va_start(ap, format); 23 desc = [[NSString alloc] initWithFormat:format arguments:ap]; 24 va_end(ap); 25 } 26 return [NSError errorWithDomain:NSOSStatusErrorDomain 27 code:status 28 userInfo:desc ? @{NSLocalizedDescriptionKey : desc} : nil]; 29 } 30 31 - (NSMutableDictionary*)baseQuery { 32 return [@{ 33 (id)kSecUseDataProtectionKeychain : @YES, 34 (id)kSecAttrAccessible : (id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly, 35 // Prevents backups in case this ever comes to the Mac, prevents sync 36 (id)kSecAttrSysBound : @(kSecSecAttrSysBoundPreserveDuringRestore), 37 // Belt-and-suspenders prohibition on syncing 38 (id)kSecAttrSynchronizable : @NO, 39 (id)kSecClass : (id)kSecClassKey, 40 (id)kSecAttrApplicationLabel : @"loginstash", 41 // This is the default, but just making sure 42 (id)kSecAttrAccessGroup : kApplicationIdentifier, 43 } mutableCopy]; 44 } 45 46 - (void)stashKey:(NSData*)key withReply:(void (^)(NSError*))reply { 47 secnotice("stashkey", "Will attempt to stash key"); 48 if (!key || key.length == 0) { 49 reply([self errorWithStatus:errSecParam message:@"nil or empty key passed"]); 50 return; 51 } 52 53 NSMutableDictionary* query = [self baseQuery]; 54 query[(id)kSecValueData] = key; 55 56 OSStatus status = SecItemAdd((__bridge CFDictionaryRef)query, NULL); 57 secnotice("stashkey", "SecItemAdd result: %ld", (long)status); 58 59 if (status == errSecDuplicateItem) { 60 [[LocalKeychainAnalytics logger] logResultForEvent:LKAEventStash hardFailure:NO result:[self errorWithStatus:status message:nil]]; 61 query[(id)kSecValueData] = nil; 62 NSDictionary* update = @{(id)kSecValueData : key}; 63 status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)update); 64 secnotice("stashkey", "SecItemUpdate result: %ld", (long)status); 65 } 66 67 [[LocalKeychainAnalytics logger] logResultForEvent:LKAEventStash hardFailure:YES result:[self errorWithStatus:status message:nil]]; 68 if (status == errSecSuccess) { 69 reply(nil); 70 } else { 71 reply([self errorWithStatus:status message:@"Stash failed with keychain error"]); 72 } 73 } 74 75 - (void)loadKeyWithReply:(void (^)(NSData*, NSError*))reply { 76 secnotice("KeychainStasher", "Will attempt to retrieve stashed key"); 77 78 NSMutableDictionary* query = [self baseQuery]; 79 query[(id)kSecReturnData] = @YES; 80 81 CFTypeRef object = NULL; 82 OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &object); 83 84 if (status != errSecSuccess) { 85 if (status == errSecItemNotFound) { 86 reply(nil, [self errorWithStatus:status message:@"No stashed key found"]); 87 } else { 88 reply(nil, [self errorWithStatus:status message:@"Keychain error"]); 89 } 90 [[LocalKeychainAnalytics logger] logResultForEvent:LKAEventStashLoad hardFailure:YES result:[self errorWithStatus:status message:nil]]; 91 return; 92 } 93 94 NSData* key; 95 if (!object || CFGetTypeID(object) != CFDataGetTypeID() || !(key = CFBridgingRelease(object))) { 96 reply(nil, [self errorWithStatus:errSecInternalError 97 message:@"No or bad object: %d / %lu / %d", 98 object != NULL, object ? CFGetTypeID(object) : 0, key != nil]); 99 [[LocalKeychainAnalytics logger] logResultForEvent:LKAEventStashLoad 100 hardFailure:YES 101 result:[self errorWithStatus:errSecInternalError message:nil]]; 102 return; 103 } 104 105 // Caller does not need to wait for our delete 106 reply(key, nil); 107 108 query[(id)kSecReturnData] = nil; 109 status = SecItemDelete((__bridge CFDictionaryRef)query); 110 if (status != errSecSuccess) { 111 seccritical("Unable to delete masterkey after load: %d", (int)status); 112 } 113 [[LocalKeychainAnalytics logger] logResultForEvent:LKAEventStashLoad hardFailure:NO result:[self errorWithStatus:status message:nil]]; 114 } 115 116 @end