1 #import <xpc/private.h>
3 #import "utilities/debugging.h"
4 #import <Security/SecItemPriv.h>
5 #import "LocalKeychainAnalytics.h"
7 #import "KeychainStasher.h"
9 NSString* const kApplicationIdentifier = @"com.apple.security.KeychainStasher";
11 @implementation KeychainStasher
13 - (NSError*)errorWithStatus:(OSStatus)status message:(NSString*)format, ... NS_FORMAT_FUNCTION(2, 3)
15 if (status == errSecSuccess) {
23 desc = [[NSString alloc] initWithFormat:format arguments:ap];
26 return [NSError errorWithDomain:NSOSStatusErrorDomain
28 userInfo:desc ? @{NSLocalizedDescriptionKey : desc} : nil];
31 - (NSMutableDictionary*)baseQuery {
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,
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"]);
53 NSMutableDictionary* query = [self baseQuery];
54 query[(id)kSecValueData] = key;
56 OSStatus status = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
57 secnotice("stashkey", "SecItemAdd result: %ld", (long)status);
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);
67 [[LocalKeychainAnalytics logger] logResultForEvent:LKAEventStash hardFailure:YES result:[self errorWithStatus:status message:nil]];
68 if (status == errSecSuccess) {
71 reply([self errorWithStatus:status message:@"Stash failed with keychain error"]);
75 - (void)loadKeyWithReply:(void (^)(NSData*, NSError*))reply {
76 secnotice("KeychainStasher", "Will attempt to retrieve stashed key");
78 NSMutableDictionary* query = [self baseQuery];
79 query[(id)kSecReturnData] = @YES;
81 CFTypeRef object = NULL;
82 OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &object);
84 if (status != errSecSuccess) {
85 if (status == errSecItemNotFound) {
86 reply(nil, [self errorWithStatus:status message:@"No stashed key found"]);
88 reply(nil, [self errorWithStatus:status message:@"Keychain error"]);
90 [[LocalKeychainAnalytics logger] logResultForEvent:LKAEventStashLoad hardFailure:YES result:[self errorWithStatus:status message:nil]];
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
101 result:[self errorWithStatus:errSecInternalError message:nil]];
105 // Caller does not need to wait for our delete
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);
113 [[LocalKeychainAnalytics logger] logResultForEvent:LKAEventStashLoad hardFailure:NO result:[self errorWithStatus:status message:nil]];