2 * Copyright (c) 2018 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
24 #import "SecDbKeychainMetadataKeyStore.h"
25 #import <dispatch/dispatch.h>
26 #import <utilities/SecAKSWrappers.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"
36 NS_ASSUME_NONNULL_BEGIN
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);
47 @interface SecDbKeychainMetadataKeyStore ()
48 @property dispatch_queue_t queue;
49 @property NSMutableDictionary* keysDict;
50 @property int keybagNotificationToken;
53 @implementation SecDbKeychainMetadataKeyStore
55 + (void)resetSharedStore
57 initializeSharedMetadataStoreQueue();
58 dispatch_sync(sharedMetadataStoreQueue, ^{
60 [sharedStore dropAllKeys];
66 + (instancetype)sharedStore
68 __block SecDbKeychainMetadataKeyStore* ret;
69 initializeSharedMetadataStoreQueue();
70 dispatch_sync(sharedMetadataStoreQueue, ^{
72 sharedStore = [[self alloc] _init];
81 + (bool)cachingEnabled
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;
93 __weak __typeof(self) weakSelf = self;
94 notify_register_dispatch(kUserKeybagStateChangeNotification, &_keybagNotificationToken, _queue, ^(int inToken) {
96 CFErrorRef error = NULL;
97 if (!SecAKSGetIsLocked(&locked, &error)) {
98 secerror("SecDbKeychainMetadataKeyStore: error getting lock state: %@", error);
103 [weakSelf _onQueueDropClassAKeys];
112 if (_keybagNotificationToken != NOTIFY_TOKEN_INVALID) {
113 notify_cancel(_keybagNotificationToken);
114 _keybagNotificationToken = NOTIFY_TOKEN_INVALID;
118 - (void)dropClassAKeys
120 dispatch_sync(_queue, ^{
121 [self _onQueueDropClassAKeys];
125 - (void)_onQueueDropClassAKeys
127 dispatch_assert_queue(_queue);
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;
137 dispatch_sync(_queue, ^{
138 [self _onQueueDropAllKeys];
142 - (void)_onQueueDropAllKeys
144 dispatch_assert_queue(_queue);
146 secnotice("SecDbKeychainMetadataKeyStore", "dropping all metadata keys");
147 [_keysDict removeAllObjects];
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
158 keyclass_t classToUse = keyclass;
159 if (*outKeyclass != key_class_none && *outKeyclass != keyclass) {
160 classToUse = *outKeyclass;
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];
169 secerror("SecDbKeychainItemV7: AKS decrypted metadata blob for class %d but could not turn it into a key: %@", classToUse, localError);
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];
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);
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
199 NSError *localError = nil;
200 SFAESKey* key = [[SFAESKey alloc] initRandomKeyWithSpecifier:specifier error:&localError];
207 return [self writeKey:key ForKeyclass:keyclass withKeybag:keybag keySpecifier:specifier database:dbt error:error];
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
217 NSError *localError = nil;
218 dispatch_assert_queue(_queue);
220 NSMutableData* wrappedKey = [NSMutableData dataWithLength:APPLE_KEYSTORE_MAX_SYM_WRAPPED_KEY_LEN];
221 keyclass_t outKeyclass = keyclass;
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);
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);
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];
254 __block bool ok = true;
255 __block CFErrorRef cfErr = NULL;
257 if (checkV12DevEnabled()) {
258 sql = @"INSERT OR REPLACE INTO metadatakeys (keyclass, actualKeyclass, data, metadatakeydata) VALUES (?,?,?,?)";
260 sql = @"INSERT OR REPLACE INTO metadatakeys (keyclass, actualKeyclass, data) VALUES (?,?,?)";
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);
268 // Leave stmt param 3 unbound so SQLite will NULL it out
269 ok &= SecDbBindBlob(stmt, 4, mdkdatablob.bytes, mdkdatablob.length, SQLITE_TRANSIENT, &cfErr);
271 ok &= SecDbStep(dbt, stmt, &cfErr, NULL);
275 secerror("Failed to write new metadata key for %d: %@", keyclass, cfErr);
276 BridgeCFErrorToNSErrorOut(error, cfErr);
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
290 dispatch_assert_queue(_queue);
293 if (checkV12DevEnabled()) {
294 sql = @"SELECT data, actualKeyclass, metadatakeydata FROM metadatakeys WHERE keyclass = ?";
296 sql = @"SELECT data, actualKeyclass FROM metadatakeys WHERE keyclass = ?";
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)];
314 // ok && !found means no error is passed back, which is specifically handled in keyForKeyclass
316 BridgeCFErrorToNSErrorOut(error, cfError);
317 *actualKeyclass = key_class_none;
321 *oldFmt = wrappedKey;
322 *newFmt = mdkdatablob;
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
333 dispatch_assert_queue(_queue);
337 keyclass_t actualKeyClass = key_class_none;
338 if (![self readKeyDataForClass:keyclass fromDb:dbt actualKeyclass:&actualKeyClass oldFormatData:&wrappedKey newFormatData:&mdkdatablob error:error]) {
342 // each entry should be either old format or new format. Otherwise this is a bug.
343 if (!(wrappedKey.length == 0 ^ mdkdatablob.length == 0)) {
345 *error = [NSError errorWithDomain:(id)kSecErrorDomain code:errSecInternal userInfo:@{NSLocalizedDescriptionKey: @"Metadata key blob both old-world and new-world"}];
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];
359 if (checkV12DevEnabled()) {
362 } else if (mdkdatablob.length > 0) { // new format read
363 SecDbKeychainSerializedMetadataKey* mdkdata = [[SecDbKeychainSerializedMetadataKey alloc] initWithData:mdkdatablob];
365 // bad read, key corrupt?
367 *error = [NSError errorWithDomain:(id)kSecErrorDomain code:errSecDecode userInfo:@{NSLocalizedDescriptionKey: @"New-format metadata key blob didn't deserialize"}];
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];
380 if (!mdkdata.backupwrappedkey || ![mdkdata.baguuid isEqual:[[SecDbBackupManager manager] currentBackupBagUUID]]) {
382 secnotice("SecDbMetadataKeyStore", "Metadata key for %d has no or mismatching backup data; will rewrite.", keyclass);
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.");
390 if (allowWrites && (rewrite || classFromDisk != actualKeyClass)) {
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);
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
409 secerror("keyForKeyclass called without error param, this is a bug");
413 static __thread BOOL reentrant = NO;
414 NSAssert(!reentrant, @"re-entering -[%@ %@] - that shouldn't happen!", NSStringFromClass(self.class), NSStringFromSelector(_cmd));
417 keyclass = SecAKSSanitizedKeyclass(keyclass);
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];
427 key = allowCaching ? self->_keysDict[@(keyclass)] : nil;
429 return; // Cache contains validated key for class, excellent!
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
437 specifier:keySpecifier
438 allowWrites:allowWrites
439 error:&nsErrorLocal];
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);
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);
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"}];
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);
464 key = [self newKeyForKeyclass:keyclass withKeybag:keybag keySpecifier:keySpecifier database:dbt error:&nsErrorLocal];
466 secerror("SecDbMetadataKeyStore: unable to create or save new key: %@", nsErrorLocal);
470 secerror("SecDbMetadataKeyStore: scary error encountered: %@", nsErrorLocal);
471 } else if (allowCaching) {
472 self->_keysDict[@(keyclass)] = key; // Only cache keys fetched from disk
480 *error = nsErrorLocal;
481 CFReleaseNull(cfError);
483 BridgeCFErrorToNSErrorOut(error, cfError);
485 assert(*error); // Triggers only in testing, which is by design not to break production
496 NS_ASSUME_NONNULL_END