/ secdxctests / KeychainCryptoTests.m
KeychainCryptoTests.m
  1  /*
  2   * Copyright (c) 2017 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 "KeychainXCTest.h"
 25  #import "SecDbKeychainItem.h"
 26  #import "SecdTestKeychainUtilities.h"
 27  #import "CKKS.h"
 28  #import "SecDbKeychainItemV7.h"
 29  #import "SecDbKeychainMetadataKeyStore.h"
 30  #import "SecItemPriv.h"
 31  #import "SecItemServer.h"
 32  #import "spi.h"
 33  #import "SecDbKeychainSerializedItemV7.h"
 34  #import "SecDbKeychainSerializedMetadata.h"
 35  #import "SecDbKeychainSerializedMetadataKey.h"
 36  #import "SecDbKeychainSerializedSecretData.h"
 37  #import "SecDbKeychainSerializedAKSWrappedKey.h"
 38  #import <utilities/SecCFWrappers.h>
 39  #import <SecurityFoundation/SFEncryptionOperation.h>
 40  #import <SecurityFoundation/SFCryptoServicesErrors.h>
 41  #import <XCTest/XCTest.h>
 42  #import <OCMock/OCMock.h>
 43  #import <notify.h>
 44  
 45  @interface SecDbKeychainItemV7 ()
 46  
 47  + (SFAESKeySpecifier*)keySpecifier;
 48  
 49  @end
 50  
 51  #if USE_KEYSTORE
 52  #include "OSX/utilities/SecAKSWrappers.h"
 53  
 54  @interface KeychainCryptoTests : KeychainXCTest
 55  @end
 56  
 57  @implementation KeychainCryptoTests
 58  
 59  static keyclass_t parse_keyclass(CFTypeRef value) {
 60      if (!value || CFGetTypeID(value) != CFStringGetTypeID()) {
 61          return 0;
 62      }
 63      
 64      if (CFEqual(value, kSecAttrAccessibleWhenUnlocked)) {
 65          return key_class_ak;
 66      }
 67      else if (CFEqual(value, kSecAttrAccessibleAfterFirstUnlock)) {
 68          return key_class_ck;
 69      }
 70      else if (CFEqual(value, kSecAttrAccessibleAlwaysPrivate)) {
 71          return key_class_dk;
 72      }
 73      else if (CFEqual(value, kSecAttrAccessibleWhenUnlockedThisDeviceOnly)) {
 74          return key_class_aku;
 75      }
 76      else if (CFEqual(value, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly)) {
 77          return key_class_cku;
 78      }
 79      else if (CFEqual(value, kSecAttrAccessibleAlwaysThisDeviceOnlyPrivate)) {
 80          return key_class_dku;
 81      }
 82      else if (CFEqual(value, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly)) {
 83          return key_class_akpu;
 84      }
 85      else {
 86          return 0;
 87      }
 88  }
 89  
 90  - (NSDictionary* _Nullable)addTestItemExpecting:(OSStatus)code account:(NSString*)account accessible:(NSString*)accessible
 91  {
 92      NSDictionary* addQuery = @{ (id)kSecClass : (id)kSecClassGenericPassword,
 93                                  (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding],
 94                                  (id)kSecAttrAccount : account,
 95                                  (id)kSecAttrService : [NSString stringWithFormat:@"%@-Service", account],
 96                                  (id)kSecAttrAccessible : (id)accessible,
 97                                  (id)kSecUseDataProtectionKeychain : @(YES),
 98                                  (id)kSecReturnAttributes : @(YES),
 99                                  };
100      CFTypeRef result = NULL;
101  
102      if(code == errSecSuccess) {
103          XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)addQuery, &result), code, @"Should have succeeded in adding test item to keychain");
104          XCTAssertNotNil((__bridge id)result, @"Should have received a dictionary back from SecItemAdd");
105      } else {
106          XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)addQuery, &result), code, @"Should have failed to adding test item to keychain with code %d", (int)code);
107          XCTAssertNil((__bridge id)result, @"Should not have received a dictionary back from SecItemAdd");
108      }
109  
110      return CFBridgingRelease(result);
111  }
112  
113  - (NSDictionary* _Nullable)findTestItemExpecting:(OSStatus)code account:(NSString*)account
114  {
115      NSDictionary* findQuery = @{ (id)kSecClass : (id)kSecClassGenericPassword,
116                                   (id)kSecAttrAccount : account,
117                                   (id)kSecAttrService : [NSString stringWithFormat:@"%@-Service", account],
118                                   (id)kSecUseDataProtectionKeychain : @(YES),
119                                   (id)kSecReturnAttributes : @(YES),
120                                   };
121      CFTypeRef result = NULL;
122  
123      if(code == errSecSuccess) {
124          XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)findQuery, &result), code, @"Should have succeeded in finding test tiem");
125          XCTAssertNotNil((__bridge id)result, @"Should have received a dictionary back from SecItemCopyMatching");
126      } else {
127          XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)findQuery, &result), code, @"Should have failed to find items in keychain with code %d", (int)code);
128          XCTAssertNotNil((__bridge id)result, @"Should not have received a dictionary back from SecItemCopyMatching");
129      }
130  
131      return CFBridgingRelease(result);
132  }
133  
134  
135  - (void)testBasicEncryptDecrypt
136  {
137      CFDataRef enc = NULL;
138      CFErrorRef error = NULL;
139      SecAccessControlRef ac = NULL;
140  
141      NSDictionary* secretData = @{(id)kSecValueData : @"secret here"};
142      CFDictionaryRef emptyDict = (__bridge CFDictionaryRef)@{};
143  
144      ac = SecAccessControlCreate(NULL, &error);
145      XCTAssertNotNil((__bridge id)ac, @"failed to create access control with error: %@", (__bridge id)error);
146      XCTAssertNil((__bridge id)error, @"encountered error attempting to create access control: %@", (__bridge id)error);
147      XCTAssertTrue(SecAccessControlSetProtection(ac, kSecAttrAccessibleWhenUnlocked, &error), @"failed to set access control protection with error: %@", error);
148      XCTAssertNil((__bridge id)error, @"encountered error attempting to set access control protection: %@", (__bridge id)error);
149  
150      XCTAssertTrue(ks_encrypt_data(KEYBAG_DEVICE, ac, NULL, (__bridge CFDictionaryRef)secretData, emptyDict, emptyDict, &enc, true, &error), @"failed to encrypt data with error: %@", error);
151      XCTAssertTrue(enc != NULL, @"failed to get encrypted data from encryption function");
152      XCTAssertNil((__bridge id)error, @"encountered error attempting to encrypt data: %@", (__bridge id)error);
153      CFReleaseNull(ac);
154  
155      CFMutableDictionaryRef attributes = NULL;
156      uint32_t version = 0;
157      NSData* dummyACM = [NSData dataWithBytes:"dummy" length:5];
158      const SecDbClass* class = kc_class_with_name(kSecClassGenericPassword);
159      NSArray* dummyArray = [NSArray array];
160  
161      keyclass_t keyclass = 0;
162      XCTAssertTrue(ks_decrypt_data(KEYBAG_DEVICE, kAKSKeyOpDecrypt, &ac, (__bridge CFDataRef _Nonnull)dummyACM, enc, class, (__bridge CFArrayRef)dummyArray, &attributes, &version, true, &keyclass, &error), @"failed to decrypt data with error: %@", error);
163      XCTAssertNil((__bridge id)error, @"encountered error attempting to decrypt data: %@", (__bridge id)error);
164      XCTAssertEqual(keyclass, key_class_ak, @"failed to get back the keyclass from decryption");
165  
166      CFTypeRef aclProtection = ac ? SecAccessControlGetProtection(ac) : NULL;
167      XCTAssertNotNil((__bridge id)aclProtection, @"failed to get ACL from keychain item decryption");
168      if (aclProtection) {
169          XCTAssertTrue(CFEqual(aclProtection, kSecAttrAccessibleWhenUnlocked), @"the acl we got back from decryption does not match what we put in");
170      }
171      CFReleaseNull(ac);
172  
173      CFReleaseNull(error);
174      CFReleaseNull(enc);
175  }
176  
177  - (void)testGetMetadataThenData
178  {
179      NSDictionary* item = @{ (id)kSecClass : (id)kSecClassGenericPassword,
180                              (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding],
181                              (id)kSecAttrAccount : @"TestAccount",
182                              (id)kSecAttrService : @"TestService",
183                              (id)kSecUseDataProtectionKeychain : @(YES) };
184  
185      OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL);
186      XCTAssertEqual(result, 0, @"failed to add test item to keychain");
187  
188      NSMutableDictionary* metadataQuery = item.mutableCopy;
189      [metadataQuery removeObjectForKey:(id)kSecValueData];
190      metadataQuery[(id)kSecReturnAttributes] = @(YES);
191      CFTypeRef foundItem = NULL;
192      result = SecItemCopyMatching((__bridge CFDictionaryRef)metadataQuery, &foundItem);
193      XCTAssertEqual(result, 0, @"failed to find the metadata for the item we just added in the keychain");
194  
195      NSMutableDictionary* dataQuery = [(__bridge NSDictionary*)foundItem mutableCopy];
196      dataQuery[(id)kSecReturnData] = @(YES);
197      dataQuery[(id)kSecClass] = (id)kSecClassGenericPassword;
198      dataQuery[(id)kSecUseDataProtectionKeychain] = @(YES);
199      result = SecItemCopyMatching((__bridge CFDictionaryRef)dataQuery, &foundItem);
200      XCTAssertEqual(result, 0, @"failed to find the data for the item we just added to the keychain");
201  
202      NSData* foundData = (__bridge NSData*)foundItem;
203      if ([foundData isKindOfClass:[NSData class]]) {
204          NSString* foundPassword = [[NSString alloc] initWithData:(__bridge NSData*)foundItem encoding:NSUTF8StringEncoding];
205          XCTAssertEqualObjects(foundPassword, @"password", @"found password (%@) does not match the expected password", foundPassword);
206      }
207      else {
208          XCTAssertTrue(false, @"found data is not the expected class: %@", foundData);
209      }
210  }
211  
212  - (void)testGetReference
213  {
214      NSDictionary* keyParams = @{ (id)kSecAttrKeyType : (id)kSecAttrKeyTypeRSA, (id)kSecAttrKeySizeInBits : @(1024) };
215      SecKeyRef key = SecKeyCreateRandomKey((__bridge CFDictionaryRef)keyParams, NULL);
216      NSDictionary* item = @{ (id)kSecClass : (id)kSecClassKey,
217                              (id)kSecValueRef : (__bridge id)key,
218                              (id)kSecAttrLabel : @"TestLabel",
219                              (id)kSecUseDataProtectionKeychain : @(YES) };
220  
221      OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL);
222      XCTAssertEqual(result, 0, @"failed to add test item to keychain");
223  
224      NSMutableDictionary* refQuery = item.mutableCopy;
225      [refQuery removeObjectForKey:(id)kSecValueData];
226      refQuery[(id)kSecReturnRef] = @(YES);
227      CFTypeRef foundItem = NULL;
228      result = SecItemCopyMatching((__bridge CFDictionaryRef)refQuery, &foundItem);
229      XCTAssertEqual(result, 0, @"failed to find the reference for the item we just added in the keychain");
230  
231      NSData* originalKeyData = (__bridge_transfer NSData*)SecKeyCopyExternalRepresentation(key, NULL);
232      NSData* foundKeyData = (__bridge_transfer NSData*)SecKeyCopyExternalRepresentation((SecKeyRef)foundItem, NULL);
233      XCTAssertEqualObjects(originalKeyData, foundKeyData, @"found key does not match the key we put in the keychain");
234  }
235  
236  - (void)testMetadataQueriesDoNotGetSecret
237  {
238      NSDictionary* item = @{ (id)kSecClass : (id)kSecClassGenericPassword,
239                              (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding],
240                              (id)kSecAttrAccount : @"TestAccount",
241                              (id)kSecAttrService : @"TestService",
242                              (id)kSecUseDataProtectionKeychain : @(YES) };
243  
244      OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL);
245      XCTAssertEqual(result, 0, @"failed to add test item to keychain");
246  
247      NSMutableDictionary* metadataQuery = item.mutableCopy;
248      [metadataQuery removeObjectForKey:(id)kSecValueData];
249      metadataQuery[(id)kSecReturnAttributes] = @(YES);
250      CFTypeRef foundItem = NULL;
251      result = SecItemCopyMatching((__bridge CFDictionaryRef)metadataQuery, &foundItem);
252      XCTAssertEqual(result, 0, @"failed to find the metadata for the item we just added in the keychain");
253  
254      NSData* data = [(__bridge NSDictionary*)foundItem valueForKey:(id)kSecValueData];
255      XCTAssertNil(data, @"unexpectedly found data in a metadata query");
256  }
257  
258  - (void)testDeleteItem
259  {
260      NSDictionary* item = @{ (id)kSecClass : (id)kSecClassGenericPassword,
261                              (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding],
262                              (id)kSecAttrAccount : @"TestAccount",
263                              (id)kSecAttrService : @"TestService",
264                              (id)kSecUseDataProtectionKeychain : @(YES) };
265  
266      OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL);
267      XCTAssertEqual(result, 0, @"failed to add test item to keychain");
268  
269      NSMutableDictionary* dataQuery = item.mutableCopy;
270      [dataQuery removeObjectForKey:(id)kSecValueData];
271      dataQuery[(id)kSecReturnData] = @(YES);
272      CFTypeRef foundItem = NULL;
273      result = SecItemCopyMatching((__bridge CFDictionaryRef)dataQuery, &foundItem);
274      XCTAssertEqual(result, 0, @"failed to find the data for the item we just added in the keychain");
275  
276      result = SecItemDelete((__bridge CFDictionaryRef)dataQuery);
277      XCTAssertEqual(result, 0, @"failed to delete item");
278  }
279  
280  - (SecDbKeychainSerializedItemV7*)serializedItemWithPassword:(NSString*)password metadataAttributes:(NSDictionary*)metadata
281  {
282      NSError* error;
283      SecDbKeychainItemV7* item = [[SecDbKeychainItemV7 alloc] initWithSecretAttributes:@{(id)kSecValueData : password} metadataAttributes:metadata tamperCheck:[[NSUUID UUID] UUIDString] keyclass:9];
284      [item encryptMetadataWithKeybag:0 error:&error];
285      XCTAssertNil(error, "error encrypting metadata with keybag 0");
286      [item encryptSecretDataWithKeybag:0 accessControl:SecAccessControlCreate(NULL, NULL) acmContext:nil error:&error];
287      XCTAssertNil(error, "error encrypting secret data with keybag 0");
288      SecDbKeychainSerializedItemV7* serializedItem = [[SecDbKeychainSerializedItemV7 alloc] init];
289      serializedItem.encryptedMetadata = item.encryptedMetadataBlob;
290      serializedItem.encryptedSecretData = item.encryptedSecretDataBlob;
291      serializedItem.keyclass = 9;
292      return serializedItem;
293  }
294  
295  - (void)testTamperChecksThwartTampering
296  {
297      SecDbKeychainSerializedItemV7* serializedItem1 = [self serializedItemWithPassword:@"first password" metadataAttributes:nil];
298      SecDbKeychainSerializedItemV7* serializedItem2 = [self serializedItemWithPassword:@"second password" metadataAttributes:nil];
299      
300      serializedItem1.encryptedSecretData = serializedItem2.encryptedSecretData;
301      NSData* tamperedSerializedItemBlob = serializedItem1.data;
302      
303      NSError* error = nil;
304      SecDbKeychainItemV7* item = [[SecDbKeychainItemV7 alloc] initWithData:tamperedSerializedItemBlob decryptionKeybag:0 error:&error];
305      XCTAssertNil(item, @"unexpectedly deserialized an item blob which has been tampered");
306      XCTAssertNotNil(error, @"failed to get an error when deserializing tampered item blob");
307  }
308  
309  - (void)testCacheExpiration
310  {
311  
312      NSDictionary* item = @{ (id)kSecClass : (id)kSecClassGenericPassword,
313                              (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding],
314                              (id)kSecAttrAccount : @"TestAccount",
315                              (id)kSecAttrService : @"TestService",
316                              (id)kSecAttrAccessible : (id)kSecAttrAccessibleWhenUnlocked,
317                              (id)kSecUseDataProtectionKeychain : @YES };
318  
319      OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL);
320      XCTAssertEqual(result, 0, @"failed to add test item to keychain");
321  
322      NSMutableDictionary* dataQuery = item.mutableCopy;
323      [dataQuery removeObjectForKey:(id)kSecValueData];
324      dataQuery[(id)kSecReturnData] = @(YES);
325  
326      CFTypeRef foundItem = NULL;
327  
328      result = SecItemCopyMatching((__bridge CFDictionaryRef)dataQuery, &foundItem);
329      XCTAssertEqual(result, 0, @"failed to find the data for the item we just added in the keychain");
330      CFReleaseNull(foundItem);
331  
332      self.lockState = LockStateLockedAndDisallowAKS;
333      
334      result = SecItemCopyMatching((__bridge CFDictionaryRef)dataQuery, &foundItem);
335      XCTAssertEqual(result, errSecInteractionNotAllowed, @"get the lock error");
336      XCTAssertEqual(foundItem, NULL, @"got item anyway: %@", foundItem);
337  
338      self.lockState = LockStateUnlocked;
339  
340      result = SecItemCopyMatching((__bridge CFDictionaryRef)dataQuery, &foundItem);
341      XCTAssertEqual(result, 0, @"failed to find the data for the item we just added in the keychain");
342      CFReleaseNull(foundItem);
343  
344      result = SecItemDelete((__bridge CFDictionaryRef)dataQuery);
345      XCTAssertEqual(result, 0, @"failed to delete item");
346  }
347  
348  - (void)trashMetadataClassAKey
349  {
350      __block CFErrorRef cferror = NULL;
351      kc_with_dbt(true, &cferror, ^bool(SecDbConnectionRef dbt) {
352          CFStringRef sql = CFSTR("UPDATE metadatakeys SET data = ? WHERE keyclass = '6'");
353          NSData* garbage = [@"super bad key" dataUsingEncoding:NSUTF8StringEncoding];
354          SecDbPrepare(dbt, sql, &cferror, ^(sqlite3_stmt *stmt) {
355              SecDbBindObject(stmt, 1, (__bridge CFDataRef)garbage, &cferror);
356              SecDbStep(dbt, stmt, &cferror, NULL);
357              XCTAssertEqual(cferror, NULL, "Should be no error trashing class A metadatakey");
358              CFReleaseNull(cferror);
359          });
360          XCTAssertEqual(cferror, NULL, "Should be no error completing SecDbPrepare for trashing class A metadatakey");
361          return true;
362      });
363      CFReleaseNull(cferror);
364  
365      [[SecDbKeychainMetadataKeyStore sharedStore] dropClassAKeys];
366  }
367  
368  - (void)deleteMetadataClassAKey
369  {
370      CFErrorRef cferror = NULL;
371  
372      kc_with_dbt(true, &cferror, ^bool(SecDbConnectionRef dbt) {
373          CFErrorRef errref = NULL;
374          SecDbExec(dbt, CFSTR("DELETE FROM metadatakeys WHERE keyclass = '6'"), &errref);
375          XCTAssertEqual(errref, NULL, "Should be no error deleting class A metadatakey");
376          CFReleaseNull(errref);
377          return true;
378      });
379      CFReleaseNull(cferror);
380  
381      [[SecDbKeychainMetadataKeyStore sharedStore] dropClassAKeys];
382  }
383  
384  - (void)checkDatabaseExistenceOfMetadataKey:(keyclass_t)keyclass shouldExist:(bool)shouldExist value:(NSData*)expectedData
385  {
386      CFErrorRef cferror = NULL;
387      __block NSData* wrappedKey;
388      kc_with_dbt(true, &cferror, ^bool(SecDbConnectionRef dbt) {
389          __block CFErrorRef errref = NULL;
390  
391          NSString* sql = [NSString stringWithFormat:@"SELECT data, actualKeyclass FROM metadatakeys WHERE keyclass = %d", keyclass];
392          __block bool ok = true;
393          ok &= SecDbPrepare(dbt, (__bridge CFStringRef)sql, &errref, ^(sqlite3_stmt *stmt) {
394              ok &= SecDbStep(dbt, stmt, &errref, ^(bool *stop) {
395                  wrappedKey = [[NSData alloc] initWithBytes:sqlite3_column_blob(stmt, 0) length:sqlite3_column_bytes(stmt, 0)];
396              });
397          });
398  
399          XCTAssertTrue(ok, "Should have completed all operations correctly");
400          XCTAssertEqual(errref, NULL, "Should be no error trying to find class A metadatakey");
401  
402          if(shouldExist) {
403              XCTAssertNotNil(wrappedKey, "Metadata class key should exist");
404              if (expectedData) {
405                  XCTAssertEqualObjects(wrappedKey, expectedData);
406              }
407          } else {
408              XCTAssertNil(wrappedKey, "Metadata class key should not exist");
409          }
410          CFReleaseNull(errref);
411          return true;
412      });
413      CFReleaseNull(cferror);
414  }
415  
416  - (void)testKeychainCorruptionCopyMatching
417  {
418      NSDictionary* item = @{ (id)kSecClass : (id)kSecClassGenericPassword,
419                              (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding],
420                              (id)kSecAttrAccount : @"TestAccount",
421                              (id)kSecAttrService : @"TestService",
422                              (id)kSecAttrAccessible : (id)kSecAttrAccessibleWhenUnlocked,
423                              (id)kSecUseDataProtectionKeychain : @YES };
424  
425      OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL);
426      XCTAssertEqual(result, 0, @"failed to add test item to keychain");
427      [self checkDatabaseExistenceOfMetadataKey:key_class_ak shouldExist:true value:nil];
428  
429      NSMutableDictionary* dataQuery = item.mutableCopy;
430      [dataQuery removeObjectForKey:(id)kSecValueData];
431      dataQuery[(id)kSecReturnData] = @(YES);
432  
433      CFTypeRef foundItem = NULL;
434  
435      result = SecItemCopyMatching((__bridge CFDictionaryRef)dataQuery, &foundItem);
436      XCTAssertEqual(result, 0, @"failed to find the data for the item we just added in the keychain");
437      CFReleaseNull(foundItem);
438  
439      [self trashMetadataClassAKey];
440      [self checkDatabaseExistenceOfMetadataKey:key_class_ak shouldExist:true value:[@"super bad key" dataUsingEncoding:NSUTF8StringEncoding]];
441  
442      /* when metadata corrupted, we should not find the item */
443      result = SecItemCopyMatching((__bridge CFDictionaryRef)dataQuery, &foundItem);
444      XCTAssertEqual(result, errSecItemNotFound, @"failed to find the data for the item we just added in the keychain");
445      CFReleaseNull(foundItem);
446  
447      // Just calling SecItemCopyMatching shouldn't have created a new metadata key
448      [self checkDatabaseExistenceOfMetadataKey:key_class_ak shouldExist:true value:[@"super bad key" dataUsingEncoding:NSUTF8StringEncoding]];
449  
450      /* CopyMatching will delete corrupt pre-emptively */
451      result = SecItemDelete((__bridge CFDictionaryRef)dataQuery);
452      XCTAssertEqual(result, -25300, @"corrupt item was not deleted for us");
453  }
454  
455  - (void)testKeychainDeletionCopyMatching
456  {
457      NSDictionary* item = @{ (id)kSecClass : (id)kSecClassGenericPassword,
458                              (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding],
459                              (id)kSecAttrAccount : @"TestAccount",
460                              (id)kSecAttrService : @"TestService",
461                              (id)kSecAttrAccessible : (id)kSecAttrAccessibleWhenUnlocked,
462                              (id)kSecUseDataProtectionKeychain : @YES };
463  
464      OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL);
465      XCTAssertEqual(result, 0, @"failed to add test item to keychain");
466      [self checkDatabaseExistenceOfMetadataKey:key_class_ak shouldExist:true value:nil];
467  
468      NSMutableDictionary* dataQuery = item.mutableCopy;
469      [dataQuery removeObjectForKey:(id)kSecValueData];
470      dataQuery[(id)kSecReturnData] = @(YES);
471  
472      CFTypeRef foundItem = NULL;
473  
474      result = SecItemCopyMatching((__bridge CFDictionaryRef)dataQuery, &foundItem);
475      XCTAssertEqual(result, 0, @"failed to find the data for the item we just added in the keychain");
476      CFReleaseNull(foundItem);
477  
478      [self deleteMetadataClassAKey];
479      [self checkDatabaseExistenceOfMetadataKey:key_class_ak shouldExist:false value:nil];
480  
481      /* when metadata corrupted, we should not find the item */
482      result = SecItemCopyMatching((__bridge CFDictionaryRef)dataQuery, &foundItem);
483      XCTAssertEqual(result, errSecItemNotFound, @"failed to find the data for the item we just added in the keychain");
484      CFReleaseNull(foundItem);
485  
486      // Just calling SecItemCopyMatching shouldn't have created a new metadata key
487      [self checkDatabaseExistenceOfMetadataKey:key_class_ak shouldExist:false value:nil];
488  
489      /* CopyMatching will delete corrupt pre-emptively */
490      result = SecItemDelete((__bridge CFDictionaryRef)dataQuery);
491      XCTAssertEqual(result, -25300, @"corrupt item was not deleted for us");
492  }
493  
494  - (void)testKeychainCorruptionAddOverCorruptedEntry
495  {
496      __security_simulatecrash_enable(false);
497  
498      CFTypeRef foundItem = NULL;
499      NSDictionary* item = @{ (id)kSecClass : (id)kSecClassGenericPassword,
500                              (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding],
501                              (id)kSecAttrAccount : @"TestAccount",
502                              (id)kSecAttrService : @"TestService",
503                              (id)kSecAttrAccessible : (id)kSecAttrAccessibleWhenUnlocked,
504                              (id)kSecUseDataProtectionKeychain : @YES };
505  
506      OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL);
507      XCTAssertEqual(result, 0, @"failed to add test item to keychain");
508  
509      NSMutableDictionary* dataQuery = item.mutableCopy;
510      [dataQuery removeObjectForKey:(id)kSecValueData];
511      dataQuery[(id)kSecReturnData] = @(YES);
512  
513      result = SecItemCopyMatching((__bridge CFDictionaryRef)dataQuery, &foundItem);
514      XCTAssertEqual(result, 0, @"failed to find the data for the item we just added in the keychain");
515      CFReleaseNull(foundItem);
516  
517      [self trashMetadataClassAKey];
518  
519      result = SecItemAdd((__bridge CFDictionaryRef)item, NULL);
520      XCTAssertEqual(result, 0, @"failed to add test item to keychain");
521  
522      result = SecItemDelete((__bridge CFDictionaryRef)dataQuery);
523      XCTAssertEqual(result, 0, @"failed to delete item");
524  }
525  
526  - (void)testKeychainCorruptionUpdateCorruptedEntry
527  {
528      __security_simulatecrash_enable(false);
529  
530      CFTypeRef foundItem = NULL;
531      NSDictionary* item = @{ (id)kSecClass : (id)kSecClassGenericPassword,
532                              (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding],
533                              (id)kSecAttrAccount : @"TestAccount",
534                              (id)kSecAttrService : @"TestService",
535                              (id)kSecAttrAccessible : (id)kSecAttrAccessibleWhenUnlocked,
536                              (id)kSecUseDataProtectionKeychain : @YES };
537  
538      OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL);
539      XCTAssertEqual(result, 0, @"failed to add test item to keychain");
540  
541      NSMutableDictionary* dataQuery = item.mutableCopy;
542      [dataQuery removeObjectForKey:(id)kSecValueData];
543      dataQuery[(id)kSecReturnData] = @(YES);
544  
545      result = SecItemCopyMatching((__bridge CFDictionaryRef)dataQuery, &foundItem);
546      XCTAssertEqual(result, 0, @"failed to find the data for the item we just added in the keychain");
547      CFReleaseNull(foundItem);
548  
549      [self trashMetadataClassAKey];
550  
551      NSMutableDictionary* updateQuery = item.mutableCopy;
552      updateQuery[(id)kSecValueData] = NULL;
553      NSDictionary *updateData = @{
554          (id)kSecValueData : [@"foo" dataUsingEncoding:NSUTF8StringEncoding],
555      };
556  
557      result = SecItemUpdate((__bridge CFDictionaryRef)updateQuery,
558                             (__bridge CFDictionaryRef)updateData );
559      XCTAssertEqual(result, errSecItemNotFound, @"failed to add test item to keychain");
560  
561      result = SecItemDelete((__bridge CFDictionaryRef)dataQuery);
562      XCTAssertEqual(result, 0, @"failed to delete item");
563  }
564  
565  - (void)testNoCrashWhenMetadataDecryptionFails
566  {
567      CFDataRef enc = NULL;
568      CFErrorRef error = NULL;
569      SecAccessControlRef ac = NULL;
570  
571      self.allowDecryption = NO;
572  
573      NSDictionary* secretData = @{(id)kSecValueData : @"secret here"};
574      CFDictionaryRef emptyDict = (__bridge CFDictionaryRef)@{};
575  
576      ac = SecAccessControlCreate(NULL, &error);
577      XCTAssertNotNil((__bridge id)ac, @"failed to create access control with error: %@", (__bridge id)error);
578      XCTAssertNil((__bridge id)error, @"encountered error attempting to create access control: %@", (__bridge id)error);
579      XCTAssertTrue(SecAccessControlSetProtection(ac, kSecAttrAccessibleWhenUnlocked, &error), @"failed to set access control protection with error: %@", error);
580      XCTAssertNil((__bridge id)error, @"encountered error attempting to set access control protection: %@", (__bridge id)error);
581  
582      XCTAssertTrue(ks_encrypt_data(KEYBAG_DEVICE, ac, NULL, (__bridge CFDictionaryRef)secretData, emptyDict, emptyDict, &enc, true, &error), @"failed to encrypt data with error: %@", error);
583      XCTAssertTrue(enc != NULL, @"failed to get encrypted data from encryption function");
584      XCTAssertNil((__bridge id)error, @"encountered error attempting to encrypt data: %@", (__bridge id)error);
585      CFReleaseNull(ac);
586  
587      CFMutableDictionaryRef attributes = NULL;
588      uint32_t version = 0;
589      NSData* dummyACM = [NSData dataWithBytes:"dummy" length:5];
590      const SecDbClass* class = kc_class_with_name(kSecClassGenericPassword);
591      NSArray* dummyArray = [NSArray array];
592  
593      keyclass_t keyclass = 0;
594      XCTAssertNoThrow(ks_decrypt_data(KEYBAG_DEVICE, kAKSKeyOpDecrypt, &ac, (__bridge CFDataRef _Nonnull)dummyACM, enc, class, (__bridge CFArrayRef)dummyArray, &attributes, &version, true, &keyclass, &error), @"unexpected exception when decryption fails");
595      XCTAssertEqual(keyclass, key_class_ak, @"failed to get back the keyclass when decryption failed");
596  
597      self.allowDecryption = YES;
598  }
599  
600  #if 0
601  // these tests fail until we address <rdar://problem/37523001> Fix keychain lock state check to be both secure and fast for EDU mode
602  - (void)testOperationsDontUseCachedKeysWhileLockedWithAKSAvailable // simulating the backup situation
603  {
604      self.lockState = LockStateLockedAndAllowAKS;
605  
606      NSDictionary* item = @{ (id)kSecClass : (id)kSecClassGenericPassword,
607                              (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding],
608                              (id)kSecAttrAccount : @"TestAccount",
609                              (id)kSecAttrService : @"TestService",
610                              (id)kSecUseDataProtectionKeychain : @(YES) };
611  
612      OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL);
613      XCTAssertEqual(result, 0, @"failed to add test item to keychain");
614  
615      NSMutableDictionary* metadataQuery = item.mutableCopy;
616      [metadataQuery removeObjectForKey:(id)kSecValueData];
617      metadataQuery[(id)kSecReturnAttributes] = @(YES);
618      CFTypeRef foundItem = NULL;
619      result = SecItemCopyMatching((__bridge CFDictionaryRef)metadataQuery, &foundItem);
620      XCTAssertEqual(result, 0, @"failed to find the metadata for the item we just added in the keychain");
621  
622      XCTAssertTrue(self.didAKSDecrypt, @"we did not go through AKS to decrypt the metadata key while locked - bad!");
623  
624      NSMutableDictionary* dataQuery = item.mutableCopy;
625      dataQuery[(id)kSecReturnData] = @(YES);
626      result = SecItemCopyMatching((__bridge CFDictionaryRef)dataQuery, &foundItem);
627      XCTAssertEqual(result, 0, @"failed to find the data for the item we just added to the keychain");
628  
629      NSData* foundData = (__bridge NSData*)foundItem;
630      if ([foundData isKindOfClass:[NSData class]]) {
631          NSString* foundPassword = [[NSString alloc] initWithData:(__bridge NSData*)foundItem encoding:NSUTF8StringEncoding];
632          XCTAssertEqualObjects(foundPassword, @"password", @"found password (%@) does not match the expected password", foundPassword);
633      }
634      else {
635          XCTAssertTrue(false, @"found data is not the expected class: %@", foundData);
636      }
637  }
638  
639  - (void)testNoResultsWhenLocked
640  {
641      NSDictionary* item = @{ (id)kSecClass : (id)kSecClassGenericPassword,
642                              (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding],
643                              (id)kSecAttrAccount : @"TestAccount",
644                              (id)kSecAttrService : @"TestService",
645                              (id)kSecUseDataProtectionKeychain : @(YES) };
646  
647      OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL);
648      XCTAssertEqual(result, 0, @"failed to add test item to keychain");
649  
650      self.lockState = LockStateLockedAndDisallowAKS;
651  
652      NSMutableDictionary* metadataQuery = item.mutableCopy;
653      [metadataQuery removeObjectForKey:(id)kSecValueData];
654      metadataQuery[(id)kSecReturnAttributes] = @(YES);
655      CFTypeRef foundItem = NULL;
656      result = SecItemCopyMatching((__bridge CFDictionaryRef)metadataQuery, &foundItem);
657      XCTAssertEqual(foundItem, NULL, @"somehow still got results when AKS was locked");
658  }
659  #endif
660  
661  + (NSData*)fakeDecrypt:(SFAuthenticatedCiphertext*)ciphertext withKey:(SFSymmetricKey*)key error:(NSError**)error
662  {
663      return nil;
664  }
665  
666  - (void)testRecoverFromBadMetadataKey
667  {
668      __security_simulatecrash_enable(false);
669  
670      // Disable caching, so we can change AKS encrypt/decrypt
671      id mockSecDbKeychainMetadataKeyStore = OCMClassMock([SecDbKeychainMetadataKeyStore class]);
672      OCMStub([mockSecDbKeychainMetadataKeyStore cachingEnabled]).andReturn(false);
673  
674      NSDictionary* addQuery = @{ (id)kSecClass : (id)kSecClassGenericPassword,
675                                  (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding],
676                                  (id)kSecAttrAccount : @"TestAccount",
677                                  (id)kSecAttrService : @"TestService",
678                                  (id)kSecUseDataProtectionKeychain : @(YES),
679                                  (id)kSecReturnAttributes : @(YES),
680                               };
681  
682      NSDictionary* findQuery = @{ (id)kSecClass : (id)kSecClassGenericPassword,
683                                   (id)kSecAttrAccount : @"TestAccount",
684                                   (id)kSecAttrService : @"TestService",
685                                   (id)kSecUseDataProtectionKeychain : @(YES),
686                                   (id)kSecReturnAttributes : @(YES),
687                                  };
688      
689  #if TARGET_OS_OSX
690      NSDictionary* updateQuery = findQuery;
691  #else
692      // iOS won't tolerate kSecReturnAttributes in SecItemUpdate
693      NSDictionary* updateQuery = @{ (id)kSecClass : (id)kSecClassGenericPassword,
694                                     (id)kSecAttrAccount : @"TestAccount",
695                                     (id)kSecAttrService : @"TestService",
696                                     (id)kSecUseDataProtectionKeychain : @(YES),
697                                   };
698  #endif
699  
700      NSDictionary* addQuery2 = @{ (id)kSecClass : (id)kSecClassGenericPassword,
701                                   (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding],
702                                   (id)kSecAttrAccount : @"TestAccount-second",
703                                   (id)kSecAttrService : @"TestService-second",
704                                   (id)kSecUseDataProtectionKeychain : @(YES),
705                                   (id)kSecReturnAttributes : @(YES),
706                                   };
707  
708      NSDictionary* findQuery2 = @{ (id)kSecClass : (id)kSecClassGenericPassword,
709                                    (id)kSecAttrAccount : @"TestAccount-second",
710                                    (id)kSecAttrService : @"TestService-second",
711                                    (id)kSecUseDataProtectionKeychain : @(YES),
712                                    (id)kSecReturnAttributes : @(YES),
713                                   };
714  
715      CFTypeRef result = NULL;
716  
717      // Add the item
718      XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)addQuery, &result), errSecSuccess, @"Should have succeeded in adding test item to keychain");
719      XCTAssertNotNil((__bridge id)result, @"Should have received a dictionary back from SecItemAdd");
720      CFReleaseNull(result);
721  
722      // Add a second item, for fun and profit
723      XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)addQuery2, &result),
724                     errSecSuccess,
725                     @"Should have succeeded in adding test2 item to keychain");
726  
727      // And we can find te item
728      XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)findQuery, &result), errSecSuccess, @"Should be able to find item");
729      XCTAssertNotNil((__bridge id)result, @"Should have received a dictionary back from SecItemCopyMatching");
730      CFReleaseNull(result);
731  
732      // And we can update the item
733      XCTAssertEqual(SecItemUpdate((__bridge CFDictionaryRef)updateQuery,
734                                   (__bridge CFDictionaryRef)@{(id)kSecValueData: [@"otherpassword" dataUsingEncoding:NSUTF8StringEncoding]}),
735                     errSecSuccess,
736                     "Should be able to update an item");
737  
738      // And find it again
739      XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)findQuery, &result), errSecSuccess, @"Should be able to find item");
740      XCTAssertNotNil((__bridge id)result, @"Should have received a dictionary back from SecItemCopyMatching");
741      CFReleaseNull(result);
742  
743      // And we can find the second item
744      XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)findQuery2, &result),
745                     errSecSuccess, @"Should be able to find second item");
746      XCTAssertNotNil((__bridge id)result, @"Should have received a dictionary back from SecItemCopyMatching for item 2");
747      CFReleaseNull(result);
748  
749      ///////////////////////////////////////////////////////////////////////////////////
750      // Now, the metadata keys go corrupt (fake that by changing the underlying AKS key)
751      [self setNewFakeAKSKey:[NSData dataWithBytes:"NotthesameAKSkeyas0123456789etc" length:32]];
752  
753      XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)findQuery, &result), errSecItemNotFound,
754                     "should have received errSecItemNotFound when metadata keys are invalid");
755      XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)findQuery, &result), errSecItemNotFound,
756                     "Multiple finds of the same item should receive errSecItemNotFound when metadata keys are invalid");
757      XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)findQuery, &result), errSecItemNotFound,
758                     "Multiple finds of the same item should receive errSecItemNotFound when metadata keys are invalid");
759  
760      XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)findQuery2, &result),
761                     errSecItemNotFound, @"Should not be able to find corrupt second item");
762      XCTAssertNil((__bridge id)result, @"Should have received no data back from SICM for corrupt item");
763  
764      // Updating the now-corrupt item should fail
765      XCTAssertEqual(SecItemUpdate((__bridge CFDictionaryRef)updateQuery,
766                                   (__bridge CFDictionaryRef)@{ (id)kSecValueData: [@"otherpassword" dataUsingEncoding:NSUTF8StringEncoding] }),
767                     errSecItemNotFound,
768                     "Should not be able to update a corrupt item");
769  
770      // Re-add the item (should succeed)
771      XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)addQuery, &result), errSecSuccess, @"Should have succeeded in adding test item to keychain");
772      XCTAssertNotNil((__bridge id)result, @"Should have received a dictionary back from SecItemAdd");
773      CFReleaseNull(result);
774  
775      // And we can find it again
776      XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)findQuery, &result), errSecSuccess, @"Should be able to find item");
777      XCTAssertNotNil((__bridge id)result, @"Should have received a dictionary back from SecItemAdd");
778      CFReleaseNull(result);
779  
780      // And update it
781      XCTAssertEqual(SecItemUpdate((__bridge CFDictionaryRef)updateQuery,
782                                   (__bridge CFDictionaryRef)@{ (id)kSecValueData: [@"otherpassword" dataUsingEncoding:NSUTF8StringEncoding] }),
783                     errSecSuccess,
784                     "Should be able to update a fixed item");
785  
786      /////////////
787      // And our second item, which is wrapped under an old key, can't be found
788      XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)findQuery2, &result),
789                     errSecItemNotFound, @"Should not be able to find corrupt second item");
790      XCTAssertNil((__bridge id)result, @"Should have received no data back from SICM for corrupt item");
791  
792      // But can be re-added
793      XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)addQuery2, &result),
794                     errSecSuccess,
795                     @"Should have succeeded in adding test2 item to keychain after corruption");
796      XCTAssertNotNil((__bridge id)result, @"Should have received a dictionary back from SecItemAdd for item 2 (after corruption)");
797      CFReleaseNull(result);
798  
799      // And we can find the second item again
800      XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)findQuery2, &result),
801                     errSecSuccess, @"Should be able to find second item after re-add");
802      XCTAssertNotNil((__bridge id)result, @"Should have received a dictionary back from SecItemCopyMatching for item 2 (after re-add)");
803      CFReleaseNull(result);
804  
805      [mockSecDbKeychainMetadataKeyStore stopMocking];
806  }
807  
808  // If a metadata key is created during a database transaction which is later rolled back, it shouldn't be cached for use later.
809  - (void)testMetadataKeyDoesntOutliveTxionRollback {
810      NSString* testAccount = @"TestAccount";
811      NSString* otherAccount = @"OtherAccount";
812      NSString* thirdAccount = @"ThirdAccount";
813      [self addTestItemExpecting:errSecSuccess account:testAccount accessible:(id)kSecAttrAccessibleAfterFirstUnlock];
814      [self checkDatabaseExistenceOfMetadataKey:key_class_ck shouldExist:true value:nil];
815      [self checkDatabaseExistenceOfMetadataKey:key_class_cku shouldExist:false value:nil];
816  
817      // This should fail, and not create a CKU metadata key
818      [self addTestItemExpecting:errSecDuplicateItem account:testAccount accessible:(id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly];
819      [self checkDatabaseExistenceOfMetadataKey:key_class_ck shouldExist:true value:nil];
820      [self checkDatabaseExistenceOfMetadataKey:key_class_cku shouldExist:false value:nil];
821  
822      // But successfully creating a new CKU item should create the key
823      [self addTestItemExpecting:errSecSuccess account:otherAccount accessible:(id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly];
824      [self checkDatabaseExistenceOfMetadataKey:key_class_ck shouldExist:true value:nil];
825      [self checkDatabaseExistenceOfMetadataKey:key_class_cku shouldExist:true value:nil];
826  
827      // Drop all metadata key caches
828      [SecDbKeychainMetadataKeyStore resetSharedStore];
829  
830      [self findTestItemExpecting:errSecSuccess account:testAccount];
831      [self findTestItemExpecting:errSecSuccess account:otherAccount];
832  
833      // Adding another CKU item now should be fine
834      [self addTestItemExpecting:errSecSuccess account:thirdAccount accessible:(id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly];
835      [self checkDatabaseExistenceOfMetadataKey:key_class_ck shouldExist:true value:nil];
836      [self checkDatabaseExistenceOfMetadataKey:key_class_cku shouldExist:true value:nil];
837  
838      // Drop all metadata key caches once more, to ensure we can find all three items from the persisted keys
839      [SecDbKeychainMetadataKeyStore resetSharedStore];
840  
841      [self findTestItemExpecting:errSecSuccess account:testAccount];
842      [self findTestItemExpecting:errSecSuccess account:otherAccount];
843      [self findTestItemExpecting:errSecSuccess account:thirdAccount];
844  }
845  
846  - (void)testRecoverDataFromBadKeyclassStorage
847  {
848      NSDictionary* metadataAttributesInput = @{@"TestMetadata" : @"TestValue"};
849      SecDbKeychainSerializedItemV7* serializedItem = [self serializedItemWithPassword:@"password" metadataAttributes:metadataAttributesInput];
850      serializedItem.keyclass = (serializedItem.keyclass | key_class_last + 1);
851      
852      NSError* error = nil;
853      SecDbKeychainItemV7* item = [[SecDbKeychainItemV7 alloc] initWithData:serializedItem.data decryptionKeybag:0 error:&error];
854      NSDictionary* metadataAttributesOut = [item metadataAttributesWithError:&error];
855      XCTAssertEqualObjects(metadataAttributesOut, metadataAttributesInput, @"failed to retrieve metadata with error: %@", error);
856      XCTAssertNil(error, @"error encountered attempting to retrieve metadata: %@", error);
857  }
858  
859  - (NSData*)performItemEncryptionWithAccessibility:(CFStringRef)accessibility
860  {
861      SecAccessControlRef ac = NULL;
862      CFDataRef enc = NULL;
863      CFErrorRef error = NULL;
864      
865      NSDictionary* secretData = @{(id)kSecValueData : @"secret here"};
866      CFDictionaryRef emptyDict = (__bridge CFDictionaryRef)@{};
867      
868      ac = SecAccessControlCreate(NULL, &error);
869      XCTAssertNotNil((__bridge id)ac, @"failed to create access control with error: %@", (__bridge id)error);
870      XCTAssertNil((__bridge id)error, @"encountered error attempting to create access control: %@", (__bridge id)error);
871      XCTAssertTrue(SecAccessControlSetProtection(ac, accessibility, &error), @"failed to set access control protection with error: %@", error);
872      XCTAssertNil((__bridge id)error, @"encountered error attempting to set access control protection: %@", (__bridge id)error);
873      
874      XCTAssertTrue(ks_encrypt_data(KEYBAG_DEVICE, ac, NULL, (__bridge CFDictionaryRef)secretData, emptyDict, emptyDict, &enc, true, &error), @"failed to encrypt data with error: %@", error);
875      XCTAssertTrue(enc != NULL, @"failed to get encrypted data from encryption function");
876      XCTAssertNil((__bridge id)error, @"encountered error attempting to encrypt data: %@", (__bridge id)error);
877      CFReleaseNull(ac);
878      
879      return (__bridge_transfer NSData*)enc;
880  }
881  
882  - (void)performMetadataDecryptionOfData:(NSData*)encryptedData verifyingAccessibility:(CFStringRef)accessibility
883  {
884      CFErrorRef error = NULL;
885      CFMutableDictionaryRef attributes = NULL;
886      uint32_t version = 0;
887      
888      SecAccessControlRef ac = SecAccessControlCreate(NULL, &error);
889      XCTAssertNotNil((__bridge id)ac, @"failed to create access control with error: %@", (__bridge id)error);
890      XCTAssertNil((__bridge id)error, @"encountered error attempting to create access control: %@", (__bridge id)error);
891      XCTAssertTrue(SecAccessControlSetProtection(ac, accessibility, &error), @"failed to set access control protection with error: %@", error);
892      XCTAssertNil((__bridge id)error, @"encountered error attempting to set access control protection: %@", (__bridge id)error);
893      
894      keyclass_t keyclass = 0;
895      NSData* dummyACM = [NSData dataWithBytes:"dummy" length:5];
896      const SecDbClass* class = kc_class_with_name(kSecClassGenericPassword);
897      NSArray* dummyArray = [NSArray array];
898  
899      XCTAssertTrue(ks_decrypt_data(KEYBAG_DEVICE, kAKSKeyOpDecrypt, &ac, (__bridge CFDataRef _Nonnull)dummyACM, (__bridge CFDataRef)encryptedData, class, (__bridge CFArrayRef)dummyArray, &attributes, &version, false, &keyclass, &error), @"failed to decrypt data with error: %@", error);
900      XCTAssertNil((__bridge id)error, @"encountered error attempting to decrypt data: %@", (__bridge id)error);
901      XCTAssertEqual(keyclass & key_class_last, parse_keyclass(accessibility), @"failed to get back the keyclass from decryption");
902      
903      CFReleaseNull(error);
904  }
905  
906  - (void)performMetadataEncryptDecryptWithAccessibility:(CFStringRef)accessibility
907  {
908      NSData* encryptedData = [self performItemEncryptionWithAccessibility:accessibility];
909      
910      [SecDbKeychainMetadataKeyStore resetSharedStore];
911      
912      [self performMetadataDecryptionOfData:encryptedData verifyingAccessibility:accessibility];
913  }
914  
915  - (void)testMetadataClassKeyDecryptionWithSimulatedAKSRolledKeys
916  {
917      self.simulateRolledAKSKey = YES;
918      
919      [self performMetadataEncryptDecryptWithAccessibility:kSecAttrAccessibleWhenUnlocked];
920      XCTAssertEqual(self.keyclassUsedForAKSDecryption, key_class_ak | key_class_last + 1);
921      
922      [self performMetadataEncryptDecryptWithAccessibility:kSecAttrAccessibleAfterFirstUnlock];
923      XCTAssertEqual(self.keyclassUsedForAKSDecryption, key_class_ck | key_class_last + 1);
924  
925  #pragma clang diagnostic push
926  #pragma clang diagnostic ignored "-Wdeprecated-declarations"
927      [self performMetadataEncryptDecryptWithAccessibility:kSecAttrAccessibleAlways];
928      XCTAssertEqual(self.keyclassUsedForAKSDecryption, key_class_dk | key_class_last + 1);
929  #pragma clang diagnostic pop
930      
931      [self performMetadataEncryptDecryptWithAccessibility:kSecAttrAccessibleWhenUnlockedThisDeviceOnly];
932      XCTAssertEqual(self.keyclassUsedForAKSDecryption, key_class_aku | key_class_last + 1);
933      
934      [self performMetadataEncryptDecryptWithAccessibility:kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly];
935      XCTAssertEqual(self.keyclassUsedForAKSDecryption, key_class_cku | key_class_last + 1);
936  
937  #pragma clang diagnostic push
938  #pragma clang diagnostic ignored "-Wdeprecated-declarations"
939      [self performMetadataEncryptDecryptWithAccessibility:kSecAttrAccessibleAlwaysThisDeviceOnly];
940      XCTAssertEqual(self.keyclassUsedForAKSDecryption, key_class_dku | key_class_last + 1);
941  #pragma clang diagnostic pop
942      
943      [self performMetadataEncryptDecryptWithAccessibility:kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly];
944      XCTAssertEqual(self.keyclassUsedForAKSDecryption, key_class_akpu | key_class_last + 1);
945  }
946  
947  - (void)testUpgradingMetadataKeyEntry
948  {
949      // first, force the creation of a metadata key
950      NSData* encryptedData = [self performItemEncryptionWithAccessibility:kSecAttrAccessibleWhenUnlocked];
951  
952      // now let's jury-rig this metadata key to look like an old one with no actualKeyclass information
953      __block CFErrorRef error = NULL;
954      __block bool ok = true;
955      ok &= kc_with_dbt(true, &error, ^bool(SecDbConnectionRef dbt) {
956          if (checkV12DevEnabled()) { // item is in new format, turn it into an old format item
957              NSString* sql = [NSString stringWithFormat:@"SELECT metadatakeydata FROM metadatakeys WHERE keyclass = %d", key_class_ak];
958              __block NSData* key;
959              ok &= SecDbPrepare(dbt, (__bridge CFStringRef)sql, &error, ^(sqlite3_stmt* stmt) {
960                  ok &= SecDbStep(dbt, stmt, &error, ^(bool *stop) {
961                      NSData* wrappedKey = [[NSData alloc] initWithBytes:sqlite3_column_blob(stmt, 0) length:sqlite3_column_bytes(stmt, 0)];
962                      SecDbKeychainSerializedMetadataKey* mdkdata = [[SecDbKeychainSerializedMetadataKey alloc] initWithData:wrappedKey];
963                      key = mdkdata.akswrappedkey;
964                  });
965              });
966              sql = [NSString stringWithFormat:@"UPDATE metadatakeys SET actualKeyclass = 0, data = ?, metadatakeydata = ? WHERE keyclass = %d", key_class_ak];
967              ok &= SecDbPrepare(dbt, (__bridge CFStringRef)sql, &error, ^(sqlite3_stmt *stmt) {
968                  ok &= SecDbBindBlob(stmt, 1, key.bytes, key.length, SQLITE_TRANSIENT, &error);
969                  ok &= SecDbStep(dbt, stmt, &error, NULL);
970              });
971          } else {
972              NSString* sql = [NSString stringWithFormat:@"UPDATE metadatakeys SET actualKeyclass = %d WHERE keyclass = %d", 0, key_class_ak];
973              ok &= SecDbPrepare(dbt, (__bridge CFStringRef)sql, &error, ^(sqlite3_stmt* stmt) {
974                  ok &= SecDbStep(dbt, stmt, &error, NULL);
975              });
976          }
977          return ok;
978      });
979  
980      // now, let's simulate AKS rejecting the decryption, and see if we recover and also update the database
981      self.simulateRolledAKSKey = YES;
982      [SecDbKeychainMetadataKeyStore resetSharedStore];
983      [self performMetadataDecryptionOfData:encryptedData verifyingAccessibility:kSecAttrAccessibleWhenUnlocked];
984  }
985  
986  @end
987  
988  #endif