1 #import "SecDbBackupTestsBase.h"
2 #import "keychain/securityd/SecDbBackupManager.h"
4 #if !SECDB_BACKUPS_ENABLED
6 @interface SecDbBackupTests : XCTestCase
9 @implementation SecDbBackupTests
12 #else // SECDB_BACKUPS_ENABLED
14 #import "keychain/securityd/SecDbBackupManager_Internal.h"
17 #import <objc/runtime.h>
18 #include "utilities/der_plist.h"
19 #include <Security/SecItemPriv.h>
21 @interface SecDbBackupTests : SecDbBackupTestsBase
25 SecDbBackupManager* _manager;
27 @implementation SecDbBackupTests
39 [SecDbBackupManager resetManager];
40 _manager = [SecDbBackupManager manager];
43 - (void)setFakeBagIdentity {
44 SecDbBackupBagIdentity* identity = [SecDbBackupBagIdentity new];
45 NSUUID* nsuuid = [NSUUID UUID];
47 [nsuuid getUUIDBytes:uuid];
48 identity.baguuid = [NSData dataWithBytes:uuid length:UUIDBYTESLENGTH];
49 NSMutableData* digest = [NSMutableData dataWithLength:CC_SHA512_DIGEST_LENGTH];
50 CC_SHA512(identity.baguuid.bytes, (CC_LONG)identity.baguuid.length, digest.mutableBytes);
51 identity.baghash = digest;
52 _manager.bagIdentity = identity;
55 - (SFAESKey*)randomAESKey {
56 return [[SFAESKey alloc] initRandomKeyWithSpecifier:[[SFAESKeySpecifier alloc] initWithBitSize:SFAESKeyBitSize256] error:nil];
59 - (NSData*)bagIdentData {
60 NSDictionary* bagIdentDict = @{@"baguuid" : _manager.bagIdentity.baguuid, @"baghash" : _manager.bagIdentity.baghash};
61 return (__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFDictionaryRef)bagIdentDict, NULL);
66 - (void)testAAA_MakeSureThisMakesSense {
67 XCTAssertEqual(checkV12DevEnabled(), 1, "V12 dev flag is off, no good will come of this");
68 if (!checkV12DevEnabled) {
73 - (void)testCreateBackupBagSecret {
75 NSData* secret = [_manager createBackupBagSecret:&error];
76 XCTAssertNil(error, "Error creating backup bag secret");
77 XCTAssertNotNil(secret, "No NSData from creating backup bag secret: %@", error);
78 XCTAssertEqual(secret.length, BACKUPBAG_PASSPHRASE_LENGTH, "NSData is not %i bytes long", BACKUPBAG_PASSPHRASE_LENGTH);
80 // Good luck testing randomness, but let's stipulate we don't accept all-zeroes as a key
81 uint8_t buf[BACKUPBAG_PASSPHRASE_LENGTH] = {0};
82 XCTAssertNotEqual(memcmp(secret.bytes, buf, MIN(BACKUPBAG_PASSPHRASE_LENGTH, secret.length)), 0, "Secret is all zeroes");
84 XCTAssert([secret isMemberOfClass:objc_lookUpClass("_NSClrDat")], "Secret is not a zeroing NSData");
87 - (void)testCreateAndSaveBackupBag {
89 NSData* secret = [_manager createBackupBagSecret:&error];
90 XCTAssertNil(error, "Unable to generate secret");
91 XCTAssertNotNil(secret, "Didn't get secret");
92 keybag_handle_t handle = [_manager createBackupBagWithSecret:secret error:&error];
93 XCTAssertNil(error, "Got error creating backup bag: %@", error);
94 XCTAssertNotEqual(handle, bad_keybag_handle, "Unexpected bag handle");
95 keybag_state_t keybagstate;
96 XCTAssertEqual(aks_get_lock_state(handle, &keybagstate), kAKSReturnSuccess, "Unable to check lock state of backup bag");
97 XCTAssert(keybagstate | keybag_state_locked, "Keybag unexpectedly not locked");
98 XCTAssert(keybagstate | keybag_state_been_unlocked, "Keybag unexpectedly never been unlocked (huh?)");
100 XCTAssert([_manager saveBackupBag:handle asDefault:YES error:&error]);
101 XCTAssertNil(error, "Error saving backup bag to keychain");
102 XCTAssertEqual(aks_unload_bag(handle), kAKSReturnSuccess, "Couldn't unload backup bag");
105 - (void)testCreateAndSaveBagTwice {
107 NSData* secret = [_manager createBackupBagSecret:&error];
108 XCTAssertNil(error, "Unable to generate secret");
109 XCTAssertNotNil(secret, "Didn't get secret");
110 keybag_handle_t handle = [_manager createBackupBagWithSecret:secret error:&error];
111 XCTAssertNil(error, "Got error creating backup bag: %@", error);
112 XCTAssertNotEqual(handle, bad_keybag_handle, "Unexpected bag handle");
114 XCTAssert([_manager saveBackupBag:handle asDefault:YES error:&error]);
115 XCTAssertNil(error, "Error saving backup bag to keychain");
117 XCTAssertFalse([_manager saveBackupBag:handle asDefault:YES error:&error]);
118 XCTAssert(error, "Unexpectedly did not get error saving same bag twice");
119 XCTAssertEqual(error.code, SecDbBackupWriteFailure, "Unexpected error code for double insertion: %@", error);
120 XCTAssertEqual(aks_unload_bag(handle), kAKSReturnSuccess, "Couldn't unload backup bag");
123 - (void)testLoadNonExistentDefaultBackupBag {
125 XCTAssertEqual([_manager loadBackupBag:nil error:&error], bad_keybag_handle, "Found default bag after not inserting any");
126 XCTAssertEqual(error.code, SecDbBackupNoBackupBagFound, "Didn't get an appropriate error for missing keybag: %@", error);
129 - (void)testLoadDefaultBackupBag {
131 keybag_handle_t handle = bad_keybag_handle;
132 handle = [_manager createBackupBagWithSecret:[_manager createBackupBagSecret:&error] error:&error];
133 XCTAssertNotEqual(handle, bad_keybag_handle, "Didn't get a good keybag handle");
134 XCTAssertNotEqual(handle, device_keybag_handle, "Got device keybag handle (or manager is nil)");
135 XCTAssertNil(error, "Error creating backup bag");
136 [_manager saveBackupBag:handle asDefault:YES error:&error];
137 XCTAssertNil(error, "Error saving backup bag");
140 XCTAssertEqual(aks_get_bag_uuid(handle, uuid1), kAKSReturnSuccess, "Couldn't get bag uuid");
141 XCTAssertEqual(aks_unload_bag(handle), kAKSReturnSuccess, "Couldn't unload backup bag");
142 handle = bad_keybag_handle;
144 handle = [_manager loadBackupBag:nil error:&error];
145 XCTAssertNotEqual(handle, bad_keybag_handle, "Got bad handle loading default keybag");
146 XCTAssertNil(error, "Got error loading default keybag");
149 XCTAssertEqual(aks_get_bag_uuid(handle, uuid2), kAKSReturnSuccess, "Couldn't get bag uuid");
150 XCTAssertEqual(aks_unload_bag(handle), kAKSReturnSuccess, "Couldn't unload backup bag");
151 XCTAssertEqual(memcmp(uuid1, uuid2, UUIDBYTESLENGTH), 0, "UUIDs do not match after backup bag save/load");
154 uuid_t uuidnull = {0};
155 XCTAssertNotEqual(memcmp(uuid1, uuidnull, UUIDBYTESLENGTH), 0, "uuid1 is all zeroes");
156 XCTAssertNotEqual(memcmp(uuid2, uuidnull, UUIDBYTESLENGTH), 0, "uuid2 is all zeroes");
158 // TODO: signature match?
161 - (void)testLoadBackupBagByUUID {
163 keybag_handle_t handle1 = bad_keybag_handle;
164 handle1 = [_manager createBackupBagWithSecret:[_manager createBackupBagSecret:&error] error:&error];
165 XCTAssertNotEqual(handle1, bad_keybag_handle, "Didn't get a good keybag handle");
166 XCTAssertNil(error, "Error creating backup bag");
167 XCTAssert([_manager saveBackupBag:handle1 asDefault:NO error:&error], "Unable to save bag 1");
168 XCTAssertNil(error, "Error saving backup bag");
170 keybag_handle_t handle2 = bad_keybag_handle;
171 handle2 = [_manager createBackupBagWithSecret:[_manager createBackupBagSecret:&error] error:&error];
172 XCTAssertNotEqual(handle2, bad_keybag_handle, "Didn't get a good keybag handle");
173 XCTAssertNil(error, "Error creating backup bag");
174 XCTAssert([_manager saveBackupBag:handle2 asDefault:NO error:&error], "Unable to save bag 2");
175 XCTAssertNil(error, "Error saving backup bag");
179 XCTAssertEqual(aks_get_bag_uuid(handle1, uuid1), kAKSReturnSuccess, "Couldn't get bag 1 uuid");
180 XCTAssertEqual(aks_get_bag_uuid(handle2, uuid2), kAKSReturnSuccess, "Couldn't get bag 2 uuid");
181 XCTAssertEqual(aks_unload_bag(handle1), kAKSReturnSuccess, "Couldn't unload backup bag 1");
182 XCTAssertEqual(aks_unload_bag(handle2), kAKSReturnSuccess, "Couldn't unload backup bag 2");
183 handle1 = bad_keybag_handle;
184 handle2 = bad_keybag_handle;
186 XCTAssertNotEqual(handle1 = [_manager loadBackupBag:[[NSUUID alloc] initWithUUIDBytes:uuid1] error:&error], bad_keybag_handle, "Didn't get handle loading bag 1 by UUID");
187 XCTAssertNotEqual(handle2 = [_manager loadBackupBag:[[NSUUID alloc] initWithUUIDBytes:uuid2] error:&error], bad_keybag_handle, "Didn't get handle loading bag 2 by UUID");
189 uuid_t uuid1_2 = {0};
190 uuid_t uuid2_2 = {0};
191 XCTAssertEqual(aks_get_bag_uuid(handle1, uuid1_2), kAKSReturnSuccess, "Couldn't get bag 1 uuid");
192 XCTAssertEqual(aks_get_bag_uuid(handle2, uuid2_2), kAKSReturnSuccess, "Couldn't get bag 2 uuid");
194 XCTAssertEqual(memcmp(uuid1, uuid1_2, UUIDBYTESLENGTH), 0, "UUIDs do not match after bag 1 save/load");
195 XCTAssertEqual(memcmp(uuid2, uuid2_2, UUIDBYTESLENGTH), 0, "UUIDs do not match after bag 2 save/load");
197 XCTAssertEqual(aks_unload_bag(handle1), kAKSReturnSuccess, "Couldn't unload backup bag 1");
198 XCTAssertEqual(aks_unload_bag(handle2), kAKSReturnSuccess, "Couldn't unload backup bag 2");
201 - (void)testCreateBackupInfrastructure
204 XCTAssert([_manager createOrLoadBackupInfrastructure:&error], @"Couldn't create/load backup infrastructure");
205 XCTAssertNil(error, @"Error creating/loading backup infrastructure");
207 SFECKeyPair* ak = [_manager fetchKCSKForKeyclass:key_class_ak error:&error];
211 SFECKeyPair* ck = [_manager fetchKCSKForKeyclass:key_class_ck error:&error];
215 SFECKeyPair* dk = [_manager fetchKCSKForKeyclass:key_class_dk error:&error];
219 SFECKeyPair* aku = [_manager fetchKCSKForKeyclass:key_class_aku error:&error];
220 XCTAssertNotNil(aku);
223 SFECKeyPair* cku = [_manager fetchKCSKForKeyclass:key_class_cku error:&error];
224 XCTAssertNotNil(cku);
227 SFECKeyPair* dku = [_manager fetchKCSKForKeyclass:key_class_dku error:&error];
228 XCTAssertNotNil(dku);
231 SFECKeyPair* akpu = [_manager fetchKCSKForKeyclass:key_class_akpu error:&error];
233 XCTAssertEqual(error.code, SecDbBackupNoKCSKFound);
236 - (void)testCreateOrLoadBackupInfrastructureFromC
238 CFErrorRef cferror = NULL;
239 XCTAssertTrue(SecDbBackupCreateOrLoadBackupInfrastructure(&cferror), @"Could create backup infrastructure from C");
240 XCTAssertFalse(cferror, @"Do not expect error creating backup infrastructure from C: %@", cferror);
241 CFReleaseNull(cferror);
244 // Should not run this on real AKS because don't want to lock keybag
245 - (void)disabledtestCreateOrLoadBackupInfrastructureWhileLocked
248 XCTAssertFalse([_manager createOrLoadBackupInfrastructure:&error], @"Keychain locked, don't expect to create infrastructure");
249 XCTAssertEqual(error.code, SecDbBackupKeychainLocked, @"Expected failure creating backup infrastructure while locked");
252 // Should not run this on real AKS because don't want to lock keybag
253 - (void)disabledtestCreateOrLoadBackupInfrastructureWhileLockedFromC
255 CFErrorRef cferror = NULL;
256 XCTAssertFalse(SecDbBackupCreateOrLoadBackupInfrastructure(&cferror), @"Could create backup infrastructure from C");
257 XCTAssertTrue(cferror, @"Expect error creating backup infrastructure while locked from C: %@", cferror);
259 XCTAssertEqual(CFErrorGetCode(cferror), errSecInteractionNotAllowed, @"Expect errSecInteractionNotAllowed creating backup infrastructure while locked from C");
261 CFReleaseNull(cferror);
264 - (void)testOnlyOneDefaultBackupBag {
265 // Generate two backup bags, each successively claiming makeDefault
266 // Expect: the second call should fail
269 - (void)testLoadBackupBagFromDifferentDevice {
270 // Generate keybag on some device, manually insert it on some other device and try to load it.
271 // Expect: failure unless in recovery mode.
274 - (void)testLoadBackupBagWithGarbageData {
275 // manually write garbage to keychain, then call loadBackupBag
279 - (void)testCreateKCSK {
280 [self setFakeBagIdentity];
283 XCTAssertNil([_manager createKCSKForKeyClass:key_class_ck withWrapper:nil error:&error], @"Shouldn't get KCSK without wrapper");
284 XCTAssertEqual(error.code, SecDbBackupInvalidArgument, @"createKSCKForKeyClass ought to be angry about not having a wrapper");
287 SFAESKey* key = [self randomAESKey];
288 XCTAssertNotNil(key, @"Expect key from SFAESKey");
289 SecDbBackupKeyClassSigningKey* kcsk = [_manager createKCSKForKeyClass:key_class_ak withWrapper:key error:&error];
290 XCTAssertNotNil(kcsk, @"Got a KCSK");
291 XCTAssertNil(error, @"Did not expect KCSK error: %@", error);
293 // Let's examine the KCSK
295 XCTAssertEqual(kcsk.keyClass, key_class_ak, @"key class matches");
298 aks_ref_key_t refkey = NULL;
299 XCTAssertNotNil(kcsk.aksRefKey, @"Got an AKS ref key");
300 XCTAssertEqual(aks_ref_key_create_with_blob(KEYBAG_DEVICE, kcsk.aksRefKey.bytes,
301 kcsk.aksRefKey.length, &refkey), kAKSReturnSuccess, @"Got a refkey out of kcsk blob");
303 // Verify aksWrappedKey
304 void* aksunwrappedbytes = NULL;
305 size_t aksunwrappedlen = 0;
306 XCTAssertEqual(aks_ref_key_decrypt(refkey, NULL, 0, kcsk.aksWrappedKey.bytes, kcsk.aksWrappedKey.length, &aksunwrappedbytes, &aksunwrappedlen), kAKSReturnSuccess, @"Successfully unwrapped KCSK private key");
307 SFECKeyPair* aksunwrapped = [_manager getECKeyPairFromDERBytes:aksunwrappedbytes length:aksunwrappedlen error:&error];
308 XCTAssertNil(error, @"No error reconstructing AKS backup key");
309 XCTAssert(aksunwrapped, @"Got key from getECKeyPairFromDERBytes");
310 aks_ref_key_free(&refkey);
312 // Verify backupWrappedKey
313 SFAuthenticatedEncryptionOperation* op = [[SFAuthenticatedEncryptionOperation alloc] initWithKeySpecifier:[[SFAESKeySpecifier alloc] initWithBitSize:SFAESKeyBitSize256]];
314 SFAuthenticatedCiphertext* ciphertext = [NSKeyedUnarchiver unarchivedObjectOfClass:[SFAuthenticatedCiphertext class] fromData:kcsk.backupWrappedKey error:&error];
315 XCTAssertNotNil(ciphertext, @"Reconstituted ciphertext from kcsk (%@)", error);
316 XCTAssertNil(error, @"Didn't expect error reconstituting ciphertext from kcsk: %@", error);
318 NSData* bagIdentData = [self bagIdentData];
319 NSData* bkunwrappedData = [op decrypt:ciphertext withKey:key additionalAuthenticatedData:bagIdentData error:&error];
320 XCTAssertNotNil(bkunwrappedData, @"backup-wrapped key decrypts");
321 SFECKeyPair* bkunwrapped = [[SFECKeyPair alloc] initWithData:bkunwrappedData specifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384] error:&error];
322 XCTAssertNotNil(bkunwrapped, @"unwrapped blob turns into an SFECKey (%@)", error);
324 XCTAssertEqualObjects(aksunwrapped, bkunwrapped, @"Private key same between aks and bk");
327 - (void)testCreateRecoverySetForRecoveryKey {
331 - (void)testCreateRecoverySetForAKS {
334 [self setFakeBagIdentity];
336 SecDbBackupRecoverySet* set = [_manager createRecoverySetWithBagSecret:nil forType:SecDbBackupRecoveryTypeAKS error:&error];
337 XCTAssertNil(set, @"No set without secret!");
338 XCTAssertEqual(error.code, SecDbBackupInvalidArgument, @"Expected different error without secret: %@", error);
341 NSData* secret = [_manager createBackupBagSecret:&error];
342 set = [_manager createRecoverySetWithBagSecret:secret forType:SecDbBackupRecoveryTypeAKS error:&error];
343 XCTAssertNotNil(set, @"Got aks recoveryset from backup manager");
344 XCTAssertNil(error, @"Didn't expect error obtaining recoveryset: %@", error);
346 XCTAssertEqual(set.recoveryType, SecDbBackupRecoveryTypeAKS, @"Unexpected recovery type");
347 XCTAssertEqualObjects(set.bagIdentity, _manager.bagIdentity, @"Bag identity copied properly");
348 XCTAssertNotNil(set.wrappedBagSecret, @"Have bag secret in recovery set");
349 XCTAssertNotNil(set.wrappedKCSKSecret, @"Have kcsk secret in recovery set");
350 XCTAssertNotNil(set.wrappedRecoveryKey, @"Have recovery key in recovery set");
352 NSMutableData* recoverykeydata = [NSMutableData dataWithLength:APPLE_KEYSTORE_MAX_KEY_LEN];
353 [SecAKSObjCWrappers aksDecryptWithKeybag:KEYBAG_DEVICE keyclass:key_class_aku
354 ciphertext:set.wrappedRecoveryKey outKeyclass:nil plaintext:recoverykeydata error:&error];
355 XCTAssertNil(error, @"Able to decrypt recovery key: %@", error);
356 SFAESKey* recoverykey = [[SFAESKey alloc] initWithData:recoverykeydata specifier:[[SFAESKeySpecifier alloc]
357 initWithBitSize:SFAESKeyBitSize256] error:&error];
358 XCTAssert(recoverykey, @"Got a recovery key from blob");
359 XCTAssertNil(error, @"Didn't get error from recovery key blob: %@", error);
361 SFAuthenticatedEncryptionOperation* op = [[SFAuthenticatedEncryptionOperation alloc] initWithKeySpecifier:[[SFAESKeySpecifier alloc]
362 initWithBitSize:SFAESKeyBitSize256]];
363 NSData* bagsecret = [op decrypt:[NSKeyedUnarchiver unarchivedObjectOfClass:[SFAuthenticatedCiphertext class] fromData:set.wrappedBagSecret error:&error] withKey:recoverykey error:&error];
364 XCTAssert(bagsecret, @"Reconstituted bag secret");
365 XCTAssertNil(error, @"Didn't expect error reconstituting bag secret: %@", error);
366 XCTAssertEqualObjects(bagsecret, secret, @"Returned bag secret same as provided secret");
368 NSData* kcsksecret = [op decrypt:[NSKeyedUnarchiver unarchivedObjectOfClass:[SFAuthenticatedCiphertext class] fromData:set.wrappedKCSKSecret error:&error] withKey:recoverykey error:&error];
369 XCTAssert(kcsksecret, @"Reconstituted kcsk secret");
370 XCTAssertNil(error, @"Didn't expect error reconstituting kcsk secret: %@", error);
373 - (void)testWrapItemKey {
374 SFAESKey* randomKey = [self randomAESKey];
376 SecDbBackupWrappedKey* itemKey = [_manager wrapItemKey:randomKey forKeyclass:key_class_akpu error:&error];
377 XCTAssertNil(itemKey, @"Do not expect result wrapping to akpu");
378 XCTAssertEqual(error.code, SecDbBackupInvalidArgument, @"Expect invalid argument error wrapping to akpu");
381 itemKey = [_manager wrapItemKey:randomKey forKeyclass:key_class_ak error:&error];
382 XCTAssertNil(error, @"No error wrapping item to ak");
383 XCTAssertEqualObjects(itemKey.baguuid, _manager.bagIdentity.baguuid, @"item wrapped under expected bag uuid");
385 // TODO: implement decryption and test it
388 - (void)testWrapMetadataKey {
389 SFAESKey* randomKey = [self randomAESKey];
391 SecDbBackupWrappedKey* itemKey = [_manager wrapMetadataKey:randomKey forKeyclass:key_class_akpu error:&error];
392 XCTAssertNil(itemKey, @"Do not expect result wrapping to akpu");
393 XCTAssertEqual(error.code, SecDbBackupInvalidArgument, @"Expect invalid argument error wrapping to akpu");
396 itemKey = [_manager wrapMetadataKey:randomKey forKeyclass:key_class_ak error:&error];
397 XCTAssertNil(error, @"No error wrapping item to ak");
398 XCTAssertEqualObjects(itemKey.baguuid, _manager.bagIdentity.baguuid, @"item wrapped under expected bag uuid");
400 // TODO: implement decryption and test it
403 // Does not inspect the item because it's encrypted and no code yet built to do recovery.
404 - (void)testSecItemAddAddsBackupEncryption {
405 NSDictionary* q = @{(id)kSecClass : (id)kSecClassGenericPassword,
406 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding],
407 (id)kSecAttrAccessGroup : @"com.apple.security.securityd",
408 (id)kSecUseDataProtectionKeychain : @(YES),
409 (id)kSecReturnAttributes : @(YES)
411 OSStatus status = SecItemAdd((__bridge CFDictionaryRef)q, NULL);
412 XCTAssertEqual(status, errSecSuccess, @"Regular old SecItemAdd succeeds");
414 __block CFErrorRef cfError = NULL;
415 __block bool ok = true;
416 __block NSData* readUUID;
417 ok &= kc_with_dbt(false, &cfError, ^bool(SecDbConnectionRef dbt) {
418 NSString* sql = @"SELECT backupUUID FROM genp WHERE agrp = 'com.apple.security.securityd'";
419 ok &= SecDbPrepare(dbt, (__bridge CFStringRef)sql, &cfError, ^(sqlite3_stmt *stmt) {
420 ok &= SecDbStep(dbt, stmt, &cfError, ^(bool *stop) {
421 readUUID = [[NSData alloc] initWithBytes:sqlite3_column_blob(stmt, 0) length:sqlite3_column_bytes(stmt, 0)];
427 XCTAssert(ok, @"Talking to keychain went okay");
428 XCTAssertEqual(cfError, NULL, @"Talking to keychain didn't yield an error (%@)", cfError);
429 CFReleaseNull(cfError);
430 XCTAssert(readUUID, @"Got stuff out of the keychain");
432 XCTAssertEqualObjects(readUUID, _manager.bagIdentity.baguuid, @"backup UUID is good");
437 #endif // SECDB_BACKUPS_ENABLED