]> git.saurik.com Git - apple/security.git/blob - keychain/securityd/SecDbKeychainMetadataKeyStore.m
Security-59306.61.1.tar.gz
[apple/security.git] / keychain / securityd / SecDbKeychainMetadataKeyStore.m
1 /*
2 * Copyright (c) 2018 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 #import "SecDbKeychainMetadataKeyStore.h"
25 #import <dispatch/dispatch.h>
26 #import <utilities/SecAKSWrappers.h>
27 #import <notify.h>
28 #import "SecItemServer.h"
29 #import "SecAKSObjCWrappers.h"
30 #import "sec_action.h"
31
32 static SecDbKeychainMetadataKeyStore* sharedStore = nil;
33 static dispatch_queue_t sharedMetadataStoreQueue;
34 static void initializeSharedMetadataStoreQueue(void) {
35 static dispatch_once_t onceToken;
36 dispatch_once(&onceToken, ^{
37 sharedMetadataStoreQueue = dispatch_queue_create("metadata_store", DISPATCH_QUEUE_SERIAL);
38 });
39 }
40
41 @interface SecDbKeychainMetadataKeyStore ()
42 @property dispatch_queue_t queue;
43 @property NSMutableDictionary* keysDict;
44 @property int keybagNotificationToken;
45 @end
46
47 @implementation SecDbKeychainMetadataKeyStore
48
49 + (void)resetSharedStore
50 {
51 initializeSharedMetadataStoreQueue();
52 dispatch_sync(sharedMetadataStoreQueue, ^{
53 if(sharedStore) {
54 dispatch_sync(sharedStore->_queue, ^{
55 [sharedStore _onQueueDropAllKeys];
56 });
57 }
58 sharedStore = nil;
59 });
60 }
61
62 + (instancetype)sharedStore
63 {
64 __block SecDbKeychainMetadataKeyStore* ret;
65 initializeSharedMetadataStoreQueue();
66 dispatch_sync(sharedMetadataStoreQueue, ^{
67 if(!sharedStore) {
68 sharedStore = [[self alloc] _init];
69 }
70
71 ret = sharedStore;
72 });
73
74 return ret;
75 }
76
77 + (bool)cachingEnabled
78 {
79 return true;
80 }
81
82 - (instancetype)_init
83 {
84 if (self = [super init]) {
85 _keysDict = [[NSMutableDictionary alloc] init];
86 _queue = dispatch_queue_create("SecDbKeychainMetadataKeyStore", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
87 _keybagNotificationToken = NOTIFY_TOKEN_INVALID;
88
89 __weak __typeof(self) weakSelf = self;
90 notify_register_dispatch(kUserKeybagStateChangeNotification, &_keybagNotificationToken, _queue, ^(int inToken) {
91 bool locked = true;
92 CFErrorRef error = NULL;
93 if (!SecAKSGetIsLocked(&locked, &error)) {
94 secerror("SecDbKeychainMetadataKeyStore: error getting lock state: %@", error);
95 CFReleaseNull(error);
96 }
97
98 if (locked) {
99 [weakSelf _onQueueDropClassAKeys];
100 }
101 });
102 }
103
104 return self;
105 }
106
107 - (void)dealloc {
108 if (_keybagNotificationToken != NOTIFY_TOKEN_INVALID) {
109 notify_cancel(_keybagNotificationToken);
110 _keybagNotificationToken = NOTIFY_TOKEN_INVALID;
111 }
112 }
113
114 - (void)dropClassAKeys
115 {
116 dispatch_sync(_queue, ^{
117 [self _onQueueDropClassAKeys];
118 });
119 }
120
121 - (void)_onQueueDropClassAKeys
122 {
123 dispatch_assert_queue(_queue);
124
125 secnotice("SecDbKeychainMetadataKeyStore", "dropping class A metadata keys");
126 _keysDict[@(key_class_ak)] = nil;
127 _keysDict[@(key_class_aku)] = nil;
128 _keysDict[@(key_class_akpu)] = nil;
129 }
130
131 - (void)_onQueueDropAllKeys
132 {
133 dispatch_assert_queue(_queue);
134
135 secnotice("SecDbKeychainMetadataKeyStore", "dropping all metadata keys");
136 [_keysDict removeAllObjects];
137 }
138
139 - (void)_updateActualKeyclassIfNeeded:(keyclass_t)actualKeyclassToWriteBackToDB keyclass:(keyclass_t)keyclass
140 {
141 __block CFErrorRef cfError = NULL;
142
143 secnotice("SecDbKeychainItemV7", "saving actualKeyclass %d for metadata keyclass %d", actualKeyclassToWriteBackToDB, keyclass);
144
145 kc_with_dbt_non_item_tables(true, &cfError, ^bool(SecDbConnectionRef dbt) {
146 __block bool actualKeyWriteBackOk = true;
147
148 // we did not find an actualKeyclass entry in the db, so let's add one in now.
149 NSString *sql = @"UPDATE metadatakeys SET actualKeyclass = ? WHERE keyclass = ? AND actualKeyclass IS NULL";
150 __block CFErrorRef actualKeyWriteBackError = NULL;
151 actualKeyWriteBackOk &= SecDbPrepare(dbt, (__bridge CFStringRef)sql, &actualKeyWriteBackError, ^(sqlite3_stmt* stmt) {
152 actualKeyWriteBackOk &= SecDbBindInt(stmt, 1, actualKeyclassToWriteBackToDB, &actualKeyWriteBackError);
153 actualKeyWriteBackOk &= SecDbBindInt(stmt, 2, keyclass, &actualKeyWriteBackError);
154 actualKeyWriteBackOk &= SecDbStep(dbt, stmt, &actualKeyWriteBackError, ^(bool* stop) {
155 // woohoo
156 });
157 });
158
159 if (actualKeyWriteBackOk) {
160 secnotice("SecDbKeychainItemV7", "successfully saved actualKeyclass %d for metadata keyclass %d", actualKeyclassToWriteBackToDB, keyclass);
161
162 }
163 else {
164 // we can always try this again in the future if it failed
165 secerror("SecDbKeychainItemV7: failed to save actualKeyclass %d for metadata keyclass %d; error: %@", actualKeyclassToWriteBackToDB, keyclass, actualKeyWriteBackError);
166 }
167 return actualKeyWriteBackOk;
168 });
169 }
170
171 - (SFAESKey*)keyForKeyclass:(keyclass_t)keyclass
172 keybag:(keybag_handle_t)keybag
173 keySpecifier:(SFAESKeySpecifier*)keySpecifier
174 createKeyIfMissing:(bool)createIfMissing
175 overwriteCorruptKey:(bool)overwriteCorruptKey
176 error:(NSError**)error
177 {
178 __block SFAESKey* key = nil;
179 __block NSError* nsErrorLocal = nil;
180 __block CFErrorRef cfError = NULL;
181 static __thread BOOL reentrant = NO;
182
183 NSAssert(!reentrant, @"re-entering -[%@ %@] - that shouldn't happen!", NSStringFromClass(self.class), NSStringFromSelector(_cmd));
184 reentrant = YES;
185
186 keyclass = SecAKSSanitizedKeyclass(keyclass);
187
188 dispatch_sync(_queue, ^{
189 // if we think we're locked, it's possible AKS will still give us access to keys, such as during backup,
190 // but we should force AKS to be the truth and not used cached class A keys while locked
191 bool allowKeyCaching = [SecDbKeychainMetadataKeyStore cachingEnabled];
192
193 // However, we must not cache a newly-created key, just in case someone above us in the stack rolls back our database transaction and the stored key is lost.
194 __block bool keyIsNewlyCreated = false;
195 #if 0
196 // <rdar://problem/37523001> Fix keychain lock state check to be both secure and fast for EDU mode
197 if (![SecDbKeychainItemV7 isKeychainUnlocked]) {
198 [self _onQueueDropClassAKeys];
199 allowKeyCaching = !(keyclass == key_class_ak || keyclass == key_class_aku || keyclass == key_class_akpu);
200 }
201 #endif
202
203 key = allowKeyCaching ? self->_keysDict[@(keyclass)] : nil;
204 if (!key) {
205 __block bool ok = true;
206 __block bool metadataKeyDoesntAuthenticate = false;
207 ok &= kc_with_dbt_non_item_tables(createIfMissing, &cfError, ^bool(SecDbConnectionRef dbt) {
208 __block NSString* sql = [NSString stringWithFormat:@"SELECT data, actualKeyclass FROM metadatakeys WHERE keyclass = %d", keyclass];
209 ok &= SecDbPrepare(dbt, (__bridge CFStringRef)sql, &cfError, ^(sqlite3_stmt *stmt) {
210 ok &= SecDbStep(dbt, stmt, &cfError, ^(bool *stop) {
211 NSData* wrappedKeyData = [[NSData alloc] initWithBytes:sqlite3_column_blob(stmt, 0) length:sqlite3_column_bytes(stmt, 0)];
212 NSMutableData* unwrappedKeyData = [NSMutableData dataWithLength:wrappedKeyData.length];
213
214 keyclass_t actualKeyclass = sqlite3_column_int(stmt, 1);
215
216 keyclass_t actualKeyclassToWriteBackToDB = 0;
217 keyclass_t keyclassForUnwrapping = actualKeyclass == 0 ? keyclass : actualKeyclass;
218 ok &= [SecAKSObjCWrappers aksDecryptWithKeybag:keybag keyclass:keyclassForUnwrapping ciphertext:wrappedKeyData outKeyclass:NULL plaintext:unwrappedKeyData error:&nsErrorLocal];
219 if (ok) {
220 key = [[SFAESKey alloc] initWithData:unwrappedKeyData specifier:keySpecifier error:&nsErrorLocal];
221
222 if(!key) {
223 if (__security_simulatecrash_enabled()) {
224 os_log_fault(secLogObjForScope("SecDbKeychainItemV7"), "Metadata class key (%d) decrypted, but didn't become a key: %@", keyclass, nsErrorLocal);
225 }
226 }
227
228 if (actualKeyclass == 0) {
229 actualKeyclassToWriteBackToDB = keyclassForUnwrapping;
230 }
231 }
232 #if USE_KEYSTORE
233 else if (actualKeyclass == 0 && keyclass <= key_class_last) {
234 // in this case we might have luck decrypting with a key-rolled keyclass
235 keyclass_t keyrolledKeyclass = keyclass | (key_class_last + 1);
236 secerror("SecDbKeychainItemV7: failed to decrypt metadata key for class %d, but trying keyrolled keyclass (%d); error: %@", keyclass, keyrolledKeyclass, nsErrorLocal);
237
238 // we don't want to pollute subsequent error-handling logic with what happens on our retry
239 // we'll give it a shot, and if it works, great - if it doesn't work, we'll just report that error in the log and move on
240 NSError* retryError = nil;
241 ok = [SecAKSObjCWrappers aksDecryptWithKeybag:keybag keyclass:keyrolledKeyclass ciphertext:wrappedKeyData outKeyclass:NULL plaintext:unwrappedKeyData error:&retryError];
242
243 if (ok) {
244 secerror("SecDbKeychainItemV7: successfully decrypted metadata key using keyrolled keyclass %d", keyrolledKeyclass);
245 key = [[SFAESKey alloc] initWithData:unwrappedKeyData specifier:keySpecifier error:&retryError];
246
247 if(!key) {
248 if (__security_simulatecrash_enabled()) {
249 os_log_fault(secLogObjForScope("SecDbKeychainItemV7"),
250 "Metadata class key (%d) decrypted using keyrolled keyclass %d, but didn't become a key: %@",
251 keyclass, keyrolledKeyclass, retryError);
252 }
253 nsErrorLocal = retryError;
254 }
255 }
256 else {
257 secerror("SecDbKeychainItemV7: failed to decrypt metadata key with keyrolled keyclass %d; error: %@", keyrolledKeyclass, retryError);
258 }
259 }
260 #endif
261
262 if (ok && key) {
263 if (actualKeyclassToWriteBackToDB > 0) {
264 // check if we have updated this keyclass or not already
265 static NSMutableDictionary* updated = NULL;
266 if (!updated) {
267 updated = [NSMutableDictionary dictionary];
268 }
269 if (!updated[@(keyclass)]) {
270 updated[@(keyclass)] = @YES;
271 dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
272 [self _updateActualKeyclassIfNeeded:actualKeyclassToWriteBackToDB keyclass:keyclass];
273 });
274 }
275 }
276 }
277 else {
278 if (nsErrorLocal && [nsErrorLocal.domain isEqualToString:(__bridge NSString*)kSecErrorDomain] && nsErrorLocal.code == errSecInteractionNotAllowed) {
279 static dispatch_once_t kclockedtoken;
280 static sec_action_t kclockedaction;
281 dispatch_once(&kclockedtoken, ^{
282 kclockedaction = sec_action_create("keychainlockedlogmessage", 1);
283 sec_action_set_handler(kclockedaction, ^{
284 secerror("SecDbKeychainItemV7: failed to decrypt metadata key because the keychain is locked (%d)", (int)errSecInteractionNotAllowed);
285 });
286 });
287 sec_action_perform(kclockedaction);
288 } else {
289 secerror("SecDbKeychainItemV7: failed to decrypt and create metadata key for class %d; error: %@", keyclass, nsErrorLocal);
290
291 // If this error is errSecDecode, then it's failed authentication and likely will forever. Other errors are scary.
292 metadataKeyDoesntAuthenticate = [nsErrorLocal.domain isEqualToString:NSOSStatusErrorDomain] && nsErrorLocal.code == errSecDecode;
293 if(metadataKeyDoesntAuthenticate) {
294 if (__security_simulatecrash_enabled()) {
295 os_log_fault(secLogObjForScope("SecDbKeychainItemV7"), "Metadata class key (%d) failed to decrypt: %@", keyclass, nsErrorLocal);
296 }
297 }
298 }
299 }
300 });
301 });
302
303 bool keyNotYetCreated = ok && !key;
304 bool forceOverwriteBadKey = !key && metadataKeyDoesntAuthenticate && overwriteCorruptKey;
305
306 if (createIfMissing && (keyNotYetCreated || forceOverwriteBadKey)) {
307 // we completed the database query, but no key exists or it's broken - we should create one
308 if(forceOverwriteBadKey) {
309 secerror("SecDbKeychainItemV7: metadata key is irreparably corrupt; throwing away forever");
310 // TODO: track this in LocalKeychainAnalytics
311 }
312
313 ok = true; // Reset 'ok': we have a second chance
314
315 key = [[SFAESKey alloc] initRandomKeyWithSpecifier:keySpecifier error:&nsErrorLocal];
316 keyIsNewlyCreated = true;
317
318 if (key) {
319 NSMutableData* wrappedKey = [NSMutableData dataWithLength:key.keyData.length + 40];
320 keyclass_t outKeyclass = keyclass;
321 ok &= [SecAKSObjCWrappers aksEncryptWithKeybag:keybag keyclass:keyclass plaintext:key.keyData outKeyclass:&outKeyclass ciphertext:wrappedKey error:&nsErrorLocal];
322 if (ok) {
323 secinfo("SecDbKeychainItemV7", "attempting to save new metadata key for keyclass %d with actualKeyclass %d", keyclass, outKeyclass);
324 NSString* insertString = forceOverwriteBadKey ? @"INSERT OR REPLACE" : @"INSERT";
325 sql = [NSString stringWithFormat:@"%@ into metadatakeys (keyclass, actualKeyclass, data) VALUES (?, ?, ?)", insertString];
326 ok &= SecDbPrepare(dbt, (__bridge CFStringRef)sql, &cfError, ^(sqlite3_stmt* stmt) {
327 ok &= SecDbBindInt(stmt, 1, keyclass, &cfError);
328 ok &= SecDbBindInt(stmt, 2, outKeyclass, &cfError);
329 ok &= SecDbBindBlob(stmt, 3, wrappedKey.bytes, wrappedKey.length, SQLITE_TRANSIENT, NULL);
330 ok &= SecDbStep(dbt, stmt, &cfError, ^(bool *stop) {
331 // woohoo
332 });
333 });
334
335 if (ok) {
336 secnotice("SecDbKeychainItemV7", "successfully saved new metadata key for keyclass %d", keyclass);
337 }
338 else {
339 secerror("SecDbKeychainItemV7: failed to save new metadata key for keyclass %d - probably there is already one in the database: %@", keyclass, cfError);
340 }
341 } else {
342 secerror("SecDbKeychainItemV7: unable to encrypt new metadata key(%d) with keybag(%d): %@", keyclass, keybag, nsErrorLocal);
343 }
344 }
345 else {
346 ok = false;
347 }
348 } else if(!key) {
349 // No key, but we're not supposed to make one. Make an error if one doesn't yet exist.
350 ok = false;
351 if(!nsErrorLocal) {
352 nsErrorLocal = [NSError errorWithDomain:(id)kSecErrorDomain code:errSecDecode userInfo:@{NSLocalizedDescriptionKey: @"Unable to find or create a suitable metadata key"}];
353 }
354 }
355
356 return ok;
357 });
358
359 if (ok && key) {
360 // We can't cache a newly-created key, just in case this db transaction is rolled back and we lose the persisted key.
361 // Don't worry, we'll cache it as soon as it's used again.
362 if (allowKeyCaching && !keyIsNewlyCreated) {
363 self->_keysDict[@(keyclass)] = key;
364 __weak __typeof(self) weakSelf = self;
365 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(60 * 5 * NSEC_PER_SEC)), self->_queue, ^{
366 [weakSelf _onQueueDropClassAKeys];
367 });
368 }
369 }
370 else {
371 key = nil;
372 }
373 }
374 });
375
376 reentrant = NO;
377
378 if (error && nsErrorLocal) {
379 *error = nsErrorLocal;
380 CFReleaseNull(cfError);
381 }
382 else {
383 BridgeCFErrorToNSErrorOut(error, cfError);
384 }
385
386 return key;
387 }
388
389 @end