]> git.saurik.com Git - apple/security.git/blob - keychain/securityd/SecDbKeychainMetadataKeyStore.m
Security-59754.41.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 "SecDbBackupManager.h"
31 #import "SecDbKeychainSerializedMetadataKey.h"
32 #include "SecItemDb.h" // kc_transaction
33 #include "CheckV12DevEnabled.h"
34 #import "sec_action.h"
35
36 NS_ASSUME_NONNULL_BEGIN
37
38 static SecDbKeychainMetadataKeyStore*_Nullable sharedStore = nil;
39 static dispatch_queue_t sharedMetadataStoreQueue;
40 static void initializeSharedMetadataStoreQueue(void) {
41 static dispatch_once_t onceToken;
42 dispatch_once(&onceToken, ^{
43 sharedMetadataStoreQueue = dispatch_queue_create("metadata_store", DISPATCH_QUEUE_SERIAL);
44 });
45 }
46
47 @interface SecDbKeychainMetadataKeyStore ()
48 @property dispatch_queue_t queue;
49 @property NSMutableDictionary* keysDict;
50 @property int keybagNotificationToken;
51 @end
52
53 @implementation SecDbKeychainMetadataKeyStore
54
55 + (void)resetSharedStore
56 {
57 initializeSharedMetadataStoreQueue();
58 dispatch_sync(sharedMetadataStoreQueue, ^{
59 if(sharedStore) {
60 [sharedStore dropAllKeys];
61 }
62 sharedStore = nil;
63 });
64 }
65
66 + (instancetype)sharedStore
67 {
68 __block SecDbKeychainMetadataKeyStore* ret;
69 initializeSharedMetadataStoreQueue();
70 dispatch_sync(sharedMetadataStoreQueue, ^{
71 if(!sharedStore) {
72 sharedStore = [[self alloc] _init];
73 }
74
75 ret = sharedStore;
76 });
77
78 return ret;
79 }
80
81 + (bool)cachingEnabled
82 {
83 return true;
84 }
85
86 - (instancetype)_init
87 {
88 if (self = [super init]) {
89 _keysDict = [[NSMutableDictionary alloc] init];
90 _queue = dispatch_queue_create("SecDbKeychainMetadataKeyStore", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
91 _keybagNotificationToken = NOTIFY_TOKEN_INVALID;
92
93 __weak __typeof(self) weakSelf = self;
94 notify_register_dispatch(kUserKeybagStateChangeNotification, &_keybagNotificationToken, _queue, ^(int inToken) {
95 bool locked = true;
96 CFErrorRef error = NULL;
97 if (!SecAKSGetIsLocked(&locked, &error)) {
98 secerror("SecDbKeychainMetadataKeyStore: error getting lock state: %@", error);
99 CFReleaseNull(error);
100 }
101
102 if (locked) {
103 [weakSelf _onQueueDropClassAKeys];
104 }
105 });
106 }
107
108 return self;
109 }
110
111 - (void)dealloc {
112 if (_keybagNotificationToken != NOTIFY_TOKEN_INVALID) {
113 notify_cancel(_keybagNotificationToken);
114 _keybagNotificationToken = NOTIFY_TOKEN_INVALID;
115 }
116 }
117
118 - (void)dropClassAKeys
119 {
120 dispatch_sync(_queue, ^{
121 [self _onQueueDropClassAKeys];
122 });
123 }
124
125 - (void)_onQueueDropClassAKeys
126 {
127 dispatch_assert_queue(_queue);
128
129 secnotice("SecDbKeychainMetadataKeyStore", "dropping class A metadata keys");
130 _keysDict[@(key_class_ak)] = nil;
131 _keysDict[@(key_class_aku)] = nil;
132 _keysDict[@(key_class_akpu)] = nil;
133 }
134
135 - (void)dropAllKeys
136 {
137 dispatch_sync(_queue, ^{
138 [self _onQueueDropAllKeys];
139 });
140 }
141
142 - (void)_onQueueDropAllKeys
143 {
144 dispatch_assert_queue(_queue);
145
146 secnotice("SecDbKeychainMetadataKeyStore", "dropping all metadata keys");
147 [_keysDict removeAllObjects];
148 }
149
150 // Return SFAESKey and actual keyclass if NSData decrypts, or nil if it does not and populate error
151 - (SFAESKey* _Nullable)validateWrappedKey:(NSData*)wrapped
152 forKeyClass:(keyclass_t)keyclass
153 actualKeyClass:(keyclass_t*)outKeyclass
154 keybag:(keybag_handle_t)keybag
155 keySpecifier:(SFAESKeySpecifier*)specifier
156 error:(NSError**)error
157 {
158 keyclass_t classToUse = keyclass;
159 if (*outKeyclass != key_class_none && *outKeyclass != keyclass) {
160 classToUse = *outKeyclass;
161 }
162
163 NSMutableData* unwrapped = [NSMutableData dataWithLength:APPLE_KEYSTORE_MAX_KEY_LEN];
164 SFAESKey* key = NULL;
165 NSError *localError = nil;
166 if ([SecAKSObjCWrappers aksDecryptWithKeybag:keybag keyclass:classToUse ciphertext:wrapped outKeyclass:NULL plaintext:unwrapped error:&localError]) {
167 key = [[SFAESKey alloc] initWithData:unwrapped specifier:specifier error:&localError];
168 if (!key) {
169 secerror("SecDbKeychainItemV7: AKS decrypted metadata blob for class %d but could not turn it into a key: %@", classToUse, localError);
170 }
171 }
172 #if USE_KEYSTORE
173 else if (classToUse < key_class_last && *outKeyclass == key_class_none) {
174 *outKeyclass = classToUse | (key_class_last + 1);
175 if ([SecAKSObjCWrappers aksDecryptWithKeybag:keybag keyclass:*outKeyclass ciphertext:wrapped outKeyclass:NULL plaintext:unwrapped error:&localError]) {
176 key = [[SFAESKey alloc] initWithData:unwrapped specifier:specifier error:&localError];
177 }
178 }
179 #endif
180 if (!key) {
181 // Don't be noisy for mundane error
182 if (!([localError.domain isEqualToString:(__bridge NSString*)kSecErrorDomain] && localError.code == errSecInteractionNotAllowed)) {
183 secerror("SecDbKeychainItemV7: Unable to create key from retrieved data: %@", localError);
184 }
185 if (error) {
186 *error = localError;
187 }
188 }
189
190 return key;
191 }
192
193 - (SFAESKey* _Nullable)newKeyForKeyclass:(keyclass_t)keyclass
194 withKeybag:(keybag_handle_t)keybag
195 keySpecifier:(SFAESKeySpecifier*)specifier
196 database:(SecDbConnectionRef)dbt
197 error:(NSError**)error
198 {
199 NSError *localError = nil;
200 SFAESKey* key = [[SFAESKey alloc] initRandomKeyWithSpecifier:specifier error:&localError];
201 if (!key) {
202 if (error) {
203 *error = localError;
204 }
205 return nil;
206 }
207 return [self writeKey:key ForKeyclass:keyclass withKeybag:keybag keySpecifier:specifier database:dbt error:error];
208 }
209
210 - (SFAESKey* _Nullable)writeKey:(SFAESKey*)key
211 ForKeyclass:(keyclass_t)keyclass
212 withKeybag:(keybag_handle_t)keybag
213 keySpecifier:(SFAESKeySpecifier*)specifier
214 database:(SecDbConnectionRef)dbt
215 error:(NSError**)error
216 {
217 NSError *localError = nil;
218 dispatch_assert_queue(_queue);
219
220 NSMutableData* wrappedKey = [NSMutableData dataWithLength:APPLE_KEYSTORE_MAX_SYM_WRAPPED_KEY_LEN];
221 keyclass_t outKeyclass = keyclass;
222
223 if (![SecAKSObjCWrappers aksEncryptWithKeybag:keybag keyclass:keyclass plaintext:key.keyData outKeyclass:&outKeyclass ciphertext:wrappedKey error:&localError]) {
224 secerror("SecDbMetadataKeyStore: Unable to encrypt new metadata key to keybag: %@", localError);
225 if (error) {
226 *error = localError;
227 }
228 return nil;
229 }
230
231 NSData* mdkdatablob;
232 if (checkV12DevEnabled()) {
233 SecDbBackupWrappedKey* backupWrappedKey;
234 if (SecAKSSanitizedKeyclass(keyclass) != key_class_akpu) {
235 backupWrappedKey = [[SecDbBackupManager manager] wrapMetadataKey:key forKeyclass:keyclass error:&localError];
236 if (!backupWrappedKey) {
237 secerror("SecDbMetadataKeyStore: Unable to encrypt new metadata key to backup infrastructure: %@", localError);
238 if (error) {
239 *error = localError;
240 }
241 return nil;
242 }
243 }
244
245 SecDbKeychainSerializedMetadataKey* metadatakeydata = [SecDbKeychainSerializedMetadataKey new];
246 metadatakeydata.keyclass = keyclass;
247 metadatakeydata.actualKeyclass = outKeyclass;
248 metadatakeydata.baguuid = backupWrappedKey.baguuid;
249 metadatakeydata.akswrappedkey = wrappedKey;
250 metadatakeydata.backupwrappedkey = backupWrappedKey.wrappedKey;
251 mdkdatablob = [metadatakeydata data];
252 }
253
254 __block bool ok = true;
255 __block CFErrorRef cfErr = NULL;
256 NSString* sql;
257 if (checkV12DevEnabled()) {
258 sql = @"INSERT OR REPLACE INTO metadatakeys (keyclass, actualKeyclass, data, metadatakeydata) VALUES (?,?,?,?)";
259 } else {
260 sql = @"INSERT OR REPLACE INTO metadatakeys (keyclass, actualKeyclass, data) VALUES (?,?,?)";
261 }
262 ok &= SecDbPrepare(dbt, (__bridge CFStringRef)sql, &cfErr, ^(sqlite3_stmt *stmt) {
263 ok &= SecDbBindObject(stmt, 1, (__bridge CFNumberRef)@(keyclass), &cfErr);
264 ok &= SecDbBindObject(stmt, 2, (__bridge CFNumberRef)@(outKeyclass), &cfErr);
265 if (!checkV12DevEnabled()) {
266 ok &= SecDbBindBlob(stmt, 3, wrappedKey.bytes, wrappedKey.length, SQLITE_TRANSIENT, &cfErr);
267 } else {
268 // Leave stmt param 3 unbound so SQLite will NULL it out
269 ok &= SecDbBindBlob(stmt, 4, mdkdatablob.bytes, mdkdatablob.length, SQLITE_TRANSIENT, &cfErr);
270 }
271 ok &= SecDbStep(dbt, stmt, &cfErr, NULL);
272 });
273
274 if (!ok) {
275 secerror("Failed to write new metadata key for %d: %@", keyclass, cfErr);
276 BridgeCFErrorToNSErrorOut(error, cfErr);
277 return nil;
278 }
279
280 return key;
281 }
282
283 - (BOOL)readKeyDataForClass:(keyclass_t)keyclass
284 fromDb:(SecDbConnectionRef)dbt
285 actualKeyclass:(keyclass_t*)actualKeyclass
286 oldFormatData:(NSData**)oldFmt
287 newFormatData:(NSData**)newFmt
288 error:(NSError**)error
289 {
290 dispatch_assert_queue(_queue);
291
292 NSString* sql;
293 if (checkV12DevEnabled()) {
294 sql = @"SELECT data, actualKeyclass, metadatakeydata FROM metadatakeys WHERE keyclass = ?";
295 } else {
296 sql = @"SELECT data, actualKeyclass FROM metadatakeys WHERE keyclass = ?";
297 }
298
299 __block NSData* wrappedKey;
300 __block NSData* mdkdatablob;
301 __block bool ok = true;
302 __block bool found = false;
303 __block CFErrorRef cfError = NULL;
304 ok &= SecDbPrepare(dbt, (__bridge CFStringRef)sql, &cfError, ^(sqlite3_stmt *stmt) {
305 ok &= SecDbBindObject(stmt, 1, (__bridge CFNumberRef)@(keyclass), &cfError);
306 ok &= SecDbStep(dbt, stmt, &cfError, ^(bool *stop) {
307 wrappedKey = [[NSData alloc] initWithBytes:sqlite3_column_blob(stmt, 0) length:sqlite3_column_bytes(stmt, 0)];
308 *actualKeyclass = sqlite3_column_int(stmt, 1);
309 mdkdatablob = [[NSData alloc] initWithBytes:sqlite3_column_blob(stmt, 2) length:sqlite3_column_bytes(stmt, 2)];
310 found = true;
311 });
312 });
313
314 // ok && !found means no error is passed back, which is specifically handled in keyForKeyclass
315 if (!ok || !found) {
316 BridgeCFErrorToNSErrorOut(error, cfError);
317 *actualKeyclass = key_class_none;
318 return NO;
319 }
320
321 *oldFmt = wrappedKey;
322 *newFmt = mdkdatablob;
323 return YES;
324 }
325
326 - (SFAESKey* _Nullable)fetchKeyForClass:(keyclass_t)keyclass
327 fromDb:(SecDbConnectionRef)dbt
328 keybag:(keybag_handle_t)keybag
329 specifier:(SFAESKeySpecifier*)keySpecifier
330 allowWrites:(BOOL)allowWrites
331 error:(NSError**)error
332 {
333 dispatch_assert_queue(_queue);
334
335 NSData* wrappedKey;
336 NSData* mdkdatablob;
337 keyclass_t actualKeyClass = key_class_none;
338 if (![self readKeyDataForClass:keyclass fromDb:dbt actualKeyclass:&actualKeyClass oldFormatData:&wrappedKey newFormatData:&mdkdatablob error:error]) {
339 return nil;
340 }
341
342 // each entry should be either old format or new format. Otherwise this is a bug.
343 if (!(wrappedKey.length == 0 ^ mdkdatablob.length == 0)) {
344 if (error) {
345 *error = [NSError errorWithDomain:(id)kSecErrorDomain code:errSecInternal userInfo:@{NSLocalizedDescriptionKey: @"Metadata key blob both old-world and new-world"}];
346 }
347 return nil;
348 }
349
350 SFAESKey* key;
351 BOOL rewrite = NO;
352 keyclass_t classFromDisk = key_class_none;
353 if (wrappedKey.length > 0) { // old format read
354 classFromDisk = actualKeyClass;
355 key = [self validateWrappedKey:wrappedKey forKeyClass:keyclass actualKeyClass:&actualKeyClass keybag:keybag keySpecifier:keySpecifier error:error];
356 if (!key) {
357 return nil;
358 }
359 if (checkV12DevEnabled()) {
360 rewrite = YES;
361 }
362 } else if (mdkdatablob.length > 0) { // new format read
363 SecDbKeychainSerializedMetadataKey* mdkdata = [[SecDbKeychainSerializedMetadataKey alloc] initWithData:mdkdatablob];
364 if (!mdkdata) {
365 // bad read, key corrupt?
366 if (error) {
367 *error = [NSError errorWithDomain:(id)kSecErrorDomain code:errSecDecode userInfo:@{NSLocalizedDescriptionKey: @"New-format metadata key blob didn't deserialize"}];
368 }
369 return nil;
370 }
371
372 // Ignore the old-read actualKeyClass and use blob
373 actualKeyClass = mdkdata.actualKeyclass;
374 classFromDisk = mdkdata.actualKeyclass;
375 key = [self validateWrappedKey:mdkdata.akswrappedkey forKeyClass:keyclass actualKeyClass:&actualKeyClass keybag:keybag keySpecifier:keySpecifier error:error];
376 if (!key) {
377 return nil;
378 }
379
380 if (!mdkdata.backupwrappedkey || ![mdkdata.baguuid isEqual:[[SecDbBackupManager manager] currentBackupBagUUID]]) {
381 rewrite = YES;
382 secnotice("SecDbMetadataKeyStore", "Metadata key for %d has no or mismatching backup data; will rewrite.", keyclass);
383 }
384
385 } else { // Wait, there's a row for this class but not something which might be a key?
386 secnotice("SecDbMetadataKeyStore", "No metadata key found on disk despite existing row. That's odd.");
387 return nil;
388 }
389
390 if (allowWrites && (rewrite || classFromDisk != actualKeyClass)) {
391 NSError* localError;
392 if (![self writeKey:key ForKeyclass:keyclass withKeybag:keybag keySpecifier:keySpecifier database:dbt error:&localError]) {
393 // if this fails we can try again in future
394 secwarning("SecDbMetadataKeyStore: Unable to rewrite metadata key for %d to new format: %@", keyclass, localError);
395 localError = nil;
396 }
397 }
398
399 return key;
400 }
401
402 - (SFAESKey* _Nullable)keyForKeyclass:(keyclass_t)keyclass
403 keybag:(keybag_handle_t)keybag
404 keySpecifier:(SFAESKeySpecifier*)keySpecifier
405 allowWrites:(BOOL)allowWrites
406 error:(NSError**)error
407 {
408 if (!error) {
409 secerror("keyForKeyclass called without error param, this is a bug");
410 return nil;
411 }
412
413 static __thread BOOL reentrant = NO;
414 NSAssert(!reentrant, @"re-entering -[%@ %@] - that shouldn't happen!", NSStringFromClass(self.class), NSStringFromSelector(_cmd));
415 reentrant = YES;
416
417 keyclass = SecAKSSanitizedKeyclass(keyclass);
418
419 __block SFAESKey* key = nil;
420 __block NSError* nsErrorLocal = nil;
421 __block CFErrorRef cfError = NULL;
422 __block bool ok = true;
423 dispatch_sync(_queue, ^{
424 // try our cache first and rejoice if that succeeds
425 bool allowCaching = [SecDbKeychainMetadataKeyStore cachingEnabled];
426
427 key = allowCaching ? self->_keysDict[@(keyclass)] : nil;
428 if (key) {
429 return; // Cache contains validated key for class, excellent!
430 }
431
432 // Key not in cache. Open a transaction to find or optionally (re)create key. Transactions can be nested, so this is fine.
433 ok &= kc_with_dbt_non_item_tables(true, &cfError, ^bool(SecDbConnectionRef dbt) {
434 key = [self fetchKeyForClass:keyclass
435 fromDb:dbt
436 keybag:keybag
437 specifier:keySpecifier
438 allowWrites:allowWrites
439 error:&nsErrorLocal];
440
441 // The code for this conditional is a little convoluted because I want the "keychain locked" message to take precedence over the "not allowed to create one" message.
442 if (!key && ([nsErrorLocal.domain isEqualToString:(__bridge NSString*)kSecErrorDomain] && nsErrorLocal.code == errSecInteractionNotAllowed)) {
443 static sec_action_t logKeychainLockedMessageAction;
444 static dispatch_once_t keychainLockedMessageOnceToken;
445 dispatch_once(&keychainLockedMessageOnceToken, ^{
446 logKeychainLockedMessageAction = sec_action_create("keychainlockedlogmessage", 1);
447 sec_action_set_handler(logKeychainLockedMessageAction, ^{
448 secerror("SecDbKeychainItemV7: cannot decrypt metadata key because the keychain is locked (%ld)", (long)nsErrorLocal.code);
449 });
450 });
451 sec_action_perform(logKeychainLockedMessageAction);
452 } else if (!key && !allowWrites) {
453 secwarning("SecDbMetadataKeyStore: Unable to load metadatakey for class %d from disk (%@) and not allowed to create new one", keyclass, nsErrorLocal);
454 if (!nsErrorLocal) {
455 // If this is at creation time we are allowed to create so won't be here
456 // If this is at fetch time and we have a missing or bad key then the item /is/ dead
457 nsErrorLocal = [NSError errorWithDomain:(id)kSecErrorDomain code:errSecDecode userInfo:@{NSLocalizedDescriptionKey: @"Unable to find a suitable metadata key and not permitted to create one"}];
458 }
459 return false;
460 // If this error is errSecDecode, then it's failed authentication and likely will forever. Other errors are scary. If !key and !error then no key existed.
461 } else if ((!key && !nsErrorLocal) || (!key && [nsErrorLocal.domain isEqualToString:NSOSStatusErrorDomain] && nsErrorLocal.code == errSecDecode)) {
462 secwarning("SecDbMetadataKeyStore: unable to use key (%ld), will attempt to create new one", (long)nsErrorLocal.code);
463 nsErrorLocal = nil;
464 key = [self newKeyForKeyclass:keyclass withKeybag:keybag keySpecifier:keySpecifier database:dbt error:&nsErrorLocal];
465 if (!key) {
466 secerror("SecDbMetadataKeyStore: unable to create or save new key: %@", nsErrorLocal);
467 return false;
468 }
469 } else if (!key) {
470 secerror("SecDbMetadataKeyStore: scary error encountered: %@", nsErrorLocal);
471 } else if (allowCaching) {
472 self->_keysDict[@(keyclass)] = key; // Only cache keys fetched from disk
473 }
474 return !!key;
475 }); // kc_with_dbt
476 }); // our queue
477
478 if (!ok || !key) {
479 if (nsErrorLocal) {
480 *error = nsErrorLocal;
481 CFReleaseNull(cfError);
482 } else {
483 BridgeCFErrorToNSErrorOut(error, cfError);
484 }
485 assert(*error); // Triggers only in testing, which is by design not to break production
486 key = nil;
487 }
488
489 reentrant = NO;
490
491 return key;
492 }
493
494 @end
495
496 NS_ASSUME_NONNULL_END