/ keychain / securityd / SecDbKeychainMetadataKeyStore.m
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