]>
Commit | Line | Data |
---|---|---|
d64be36e A |
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 |