2 * Copyright (c) 2018 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
24 #import "KeychainXCTest.h"
25 #import "SecDbKeychainItem.h"
26 #import "SecdTestKeychainUtilities.h"
28 #import "SecDbKeychainItemV7.h"
29 #import "SecDbKeychainMetadataKeyStore.h"
30 #import "SecDbBackupManager_Internal.h"
31 #import "SecAKSObjCWrappers.h"
32 #import "SecItemPriv.h"
33 #import "SecTaskPriv.h"
34 #import "server_security_helpers.h"
35 #import "SecItemServer.h"
37 #import "SecDbKeychainSerializedItemV7.h"
38 #import "SecDbKeychainSerializedMetadata.h"
39 #import "SecDbKeychainSerializedSecretData.h"
40 #import "SecDbKeychainSerializedAKSWrappedKey.h"
41 #import "SecCDKeychain.h"
42 #import <utilities/SecCFWrappers.h>
43 #import <SecurityFoundation/SFEncryptionOperation.h>
44 #import <SecurityFoundation/SFCryptoServicesErrors.h>
45 #import <SecurityFoundation/SFKeychain.h>
46 #import <XCTest/XCTest.h>
47 #import <OCMock/OCMock.h>
48 #include <corecrypto/ccpbkdf2.h>
49 #include <corecrypto/ccsha2.h>
50 #include <corecrypto/ccaes.h>
51 #include <corecrypto/ccmode.h>
52 #include <corecrypto/ccwrap.h>
53 #include "CheckV12DevEnabled.h"
55 void* testlist = NULL;
57 // TODO: Switch to '1' closer to deployment, but leave at '0' for now to test not breaking people
58 static int testCheckV12DevEnabled(void) {
64 @interface SecDbKeychainItemV7 ()
66 + (SFAESKeySpecifier*)keySpecifier;
70 @interface FakeAKSRefKey : NSObject <SecAKSRefKey>
73 @implementation FakeAKSRefKey {
77 - (instancetype)initWithKeybag:(keybag_handle_t)keybag keyclass:(keyclass_t)keyclass
79 if (self = [super init]) {
80 _key = [[SFAESKey alloc] initRandomKeyWithSpecifier:[[SFAESKeySpecifier alloc] initWithBitSize:SFAESKeyBitSize256] error:nil];
86 - (instancetype)initWithBlob:(NSData*)blob keybag:(keybag_handle_t)keybag
88 if (self = [super init]) {
89 _key = [[SFAESKey alloc] initWithData:blob specifier:[[SFAESKeySpecifier alloc] initWithBitSize:SFAESKeyBitSize256] error:nil];
95 - (NSData*)wrappedDataForKey:(SFAESKey*)key
97 SFAuthenticatedEncryptionOperation* encryptionOperation = [[SFAuthenticatedEncryptionOperation alloc] initWithKeySpecifier:[[SFAESKeySpecifier alloc] initWithBitSize:SFAESKeyBitSize256]];
98 return [NSKeyedArchiver archivedDataWithRootObject:[encryptionOperation encrypt:key.keyData withKey:_key error:nil] requiringSecureCoding:YES error:nil];
101 - (SFAESKey*)keyWithWrappedData:(NSData*)wrappedKeyData
103 SFAESKeySpecifier* keySpecifier = [[SFAESKeySpecifier alloc] initWithBitSize:SFAESKeyBitSize256];
104 SFAuthenticatedEncryptionOperation* encryptionOperation = [[SFAuthenticatedEncryptionOperation alloc] initWithKeySpecifier:keySpecifier];
105 NSData* keyData = [encryptionOperation decrypt:[NSKeyedUnarchiver unarchivedObjectOfClass:[SFAuthenticatedCiphertext class] fromData:wrappedKeyData error:nil] withKey:_key error:nil];
106 return [[SFAESKey alloc] initWithData:keyData specifier:keySpecifier error:nil];
109 - (NSData*)refKeyBlob
116 @implementation SFKeychainServerFakeConnection {
117 NSArray* _fakeAccessGroups;
120 - (void)setFakeAccessGroups:(NSArray*)fakeAccessGroups
122 _fakeAccessGroups = fakeAccessGroups.copy;
125 - (NSArray*)clientAccessGroups
127 return _fakeAccessGroups ?: @[@"com.apple.token"];
132 @implementation KeychainXCTest {
133 id _keychainPartialMock;
134 CFArrayRef _originalAccessGroups;
135 bool _simcrashenabled;
138 @synthesize keychainPartialMock = _keychainPartialMock;
144 // Do not want test code to be allowed to init real keychain!
145 secd_test_setup_temp_keychain("keychaintestthrowaway", NULL);
146 securityd_init(NULL);
151 _simcrashenabled = __security_simulatecrash_enabled();
152 __security_simulatecrash_enable(false);
156 self.lockState = LockStateUnlocked;
157 self.allowDecryption = true;
158 self.didAKSDecrypt = NO;
159 self.simulateRolledAKSKey = NO;
162 self.keyclassUsedForAKSDecryption = 0;
164 self.keySpecifier = [[SFAESKeySpecifier alloc] initWithBitSize:SFAESKeyBitSize256];
165 [self setNewFakeAKSKey:[NSData dataWithBytes:"1234567890123456789012345678901" length:32]];
167 self.mockSecDbKeychainItemV7 = OCMClassMock([SecDbKeychainItemV7 class]);
168 [[[self.mockSecDbKeychainItemV7 stub] andCall:@selector(decryptionOperation) onObject:self] decryptionOperation];
170 self.mockSecAKSObjCWrappers = OCMClassMock([SecAKSObjCWrappers class]);
171 [[[[self.mockSecAKSObjCWrappers stub] andCall:@selector(fakeAKSEncryptWithKeybag:keyclass:plaintext:outKeyclass:ciphertext:error:) onObject:self] ignoringNonObjectArgs] aksEncryptWithKeybag:0 keyclass:0 plaintext:[OCMArg any] outKeyclass:NULL ciphertext:[OCMArg any] error:NULL];
172 [[[[self.mockSecAKSObjCWrappers stub] andCall:@selector(fakeAKSDecryptWithKeybag:keyclass:ciphertext:outKeyclass:plaintext:error:) onObject:self] ignoringNonObjectArgs] aksDecryptWithKeybag:0 keyclass:0 ciphertext:[OCMArg any] outKeyclass:NULL plaintext:[OCMArg any] error:NULL];
174 // bring back with <rdar://problem/37523001>
175 // [[[self.mockSecDbKeychainItemV7 stub] andCall:@selector(isKeychainUnlocked) onObject:self] isKeychainUnlocked];
177 id refKeyMock = OCMClassMock([SecAKSRefKey class]);
178 [[[refKeyMock stub] andCall:@selector(alloc) onObject:[FakeAKSRefKey class]] alloc];
180 checkV12DevEnabled = testCheckV12DevEnabled;
181 NSArray* partsOfName = [self.name componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@" ]"]];
182 // Calls SecKeychainDbReset which also resets metadata keys and backup manager
183 secd_test_setup_temp_keychain([partsOfName[1] UTF8String], NULL);
185 _originalAccessGroups = SecAccessGroupsGetCurrent();
186 SecResetLocalSecuritydXPCFakeEntitlements();
191 [self.mockSecDbKeychainItemV7 stopMocking];
192 [self.mockSecAKSObjCWrappers stopMocking];
193 [self resetEntitlements];
194 SecAccessGroupsSetCurrent(_originalAccessGroups);
195 __security_simulatecrash_enable(_simcrashenabled);
201 SecResetLocalSecuritydXPCFakeEntitlements();
205 - (bool)isKeychainUnlocked
207 return self.lockState == LockStateUnlocked;
210 - (id)decryptionOperation
212 return self.allowDecryption ? [[SFAuthenticatedEncryptionOperation alloc] initWithKeySpecifier:[SecDbKeychainItemV7 keySpecifier]] : nil;
215 - (bool)setNewFakeAKSKey:(NSData*)newKeyData
217 self.fakeAKSKey = newKeyData;
221 - (NSData*)wrapKey:(NSData*)plaintextKey withKey:(NSData*)wrappingKey
223 const struct ccmode_ecb *ecb_mode = ccaes_ecb_encrypt_mode();
224 ccecb_ctx_decl(ccecb_context_size(ecb_mode), key);
225 NSMutableData* wrappedKey = [NSMutableData dataWithLength:ccwrap_wrapped_size(plaintextKey.length)];
227 ccecb_init(ecb_mode, key, wrappingKey.length, wrappingKey.bytes);
230 int wrap_status = ccwrap_auth_encrypt(ecb_mode, key, plaintextKey.length, plaintextKey.bytes,
231 &obytes, wrappedKey.mutableBytes);
232 if (wrap_status == 0) {
233 assert(obytes == wrappedKey.length);
238 ccecb_ctx_clear(ccecb_context_size(ecb_mode), key);
242 - (NSData*)unwrapKey:(NSData*)ciphertextKey withKey:(NSData*)wrappingKey
244 const struct ccmode_ecb *ecb_mode = ccaes_ecb_decrypt_mode();
245 ccecb_ctx_decl(ccecb_context_size(ecb_mode), key);
246 NSMutableData *unwrappedKey = [NSMutableData dataWithLength:ccwrap_unwrapped_size(ciphertextKey.length)];
248 ccecb_init(ecb_mode, key, wrappingKey.length, wrappingKey.bytes);
251 int status = ccwrap_auth_decrypt(ecb_mode, key, ciphertextKey.length, ciphertextKey.bytes,
252 &obytes, unwrappedKey.mutableBytes);
254 assert(obytes == (size_t)[unwrappedKey length]);
259 ccecb_ctx_clear(ccecb_context_size(ecb_mode), key);
263 - (bool)fakeAKSEncryptWithKeybag:(keybag_handle_t)keybag
264 keyclass:(keyclass_t)keyclass
265 plaintext:(NSData*)plaintext
266 outKeyclass:(keyclass_t*)outKeyclass
267 ciphertext:(NSMutableData*)ciphertextOut
268 error:(NSError**)error
270 if (self.lockState == LockStateLockedAndDisallowAKS) {
272 *error = [NSError errorWithDomain:(__bridge NSString *)kSecErrorDomain code:errSecInteractionNotAllowed userInfo:NULL];
277 if (keybag == KEYBAG_DEVICE) {
278 XCTAssertLessThanOrEqual(ciphertextOut.length, APPLE_KEYSTORE_MAX_SYM_WRAPPED_KEY_LEN);
279 } else { // this'll do for now: assume non-device bags are asymmetric backup bags
280 XCTAssertLessThanOrEqual(ciphertextOut.length, APPLE_KEYSTORE_MAX_ASYM_WRAPPED_KEY_LEN);
283 NSData* wrappedKey = [self wrapKey:plaintext withKey:self.fakeAKSKey];
284 if (ciphertextOut.length >= wrappedKey.length) {
285 memcpy(ciphertextOut.mutableBytes, wrappedKey.bytes, wrappedKey.length);
286 ciphertextOut.length = wrappedKey.length; // simulate ks_crypt behavior
287 if (self.simulateRolledAKSKey && outKeyclass) {
288 *outKeyclass = keyclass | (key_class_last + 1);
289 } else if (outKeyclass) {
290 *outKeyclass = keyclass;
294 XCTFail(@"output buffer too small for wrapped key");
299 - (bool)fakeAKSDecryptWithKeybag:(keybag_handle_t)keybag
300 keyclass:(keyclass_t)keyclass
301 ciphertext:(NSData*)ciphertextIn
302 outKeyclass:(keyclass_t*)outKeyclass
303 plaintext:(NSMutableData*)plaintext
304 error:(NSError**)error
306 if (self.lockState == LockStateLockedAndDisallowAKS) {
308 *error = [NSError errorWithDomain:(__bridge NSString *)kSecErrorDomain code:errSecInteractionNotAllowed userInfo:NULL];
313 if (self.simulateRolledAKSKey && keyclass < key_class_last) {
314 // let's make decryption fail like it would if this were an old metadata key entry made with a generational AKS key, but we didn't store that info in the database
318 self.keyclassUsedForAKSDecryption = keyclass;
319 NSData* unwrappedKey = [self unwrapKey:ciphertextIn withKey:self.fakeAKSKey];
320 if (unwrappedKey && plaintext.length >= unwrappedKey.length) {
321 memcpy(plaintext.mutableBytes, unwrappedKey.bytes, unwrappedKey.length);
322 plaintext.length = unwrappedKey.length; // simulate ks_crypt behavior
323 self.didAKSDecrypt = YES;
325 } else if (unwrappedKey) {
326 XCTFail(@"output buffer too small for unwrapped key");
329 if (error && !self.simulateRolledAKSKey && keyclass > key_class_last) {
330 // for this case we want to simulate what happens when we try decrypting with a rolled keyclass on a device which has never been rolled, which is it ends up with a NotPermitted error from AKS which the security layer translates as locked keybag
331 *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:errSecInteractionNotAllowed userInfo:nil];
334 *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:errSecDecode userInfo:nil];
340 - (NSData*)getDatabaseKeyDataWithError:(NSError**)error
342 if (_lockState == LockStateUnlocked) {
343 return [NSData dataWithBytes:"1234567890123456789012345678901" length:32];
347 // <rdar://problem/38972671> add SFKeychainErrorDeviceLocked
348 *error = [NSError errorWithDomain:SFKeychainErrorDomain code:SFKeychainErrorFailedToCommunicateWithServer userInfo:nil];
354 // Mock SecTask entitlement retrieval API, so that we can test access group entitlement parsing code in SecTaskCopyAccessGroups()
355 static NSDictionary *currentEntitlements = nil;
356 static BOOL currentEntitlementsValidated = YES;
358 CFTypeRef SecTaskCopyValueForEntitlement(SecTaskRef task, CFStringRef entitlement, CFErrorRef *error) {
359 id value = currentEntitlements[(__bridge id)entitlement];
360 if (value == nil && error != NULL) {
361 *error = (CFErrorRef)CFBridgingRetain([NSError errorWithDomain:NSPOSIXErrorDomain code:EINVAL userInfo:nil]);
363 return CFBridgingRetain(value);
366 Boolean SecTaskEntitlementsValidated(SecTaskRef task) {
367 return currentEntitlementsValidated;
370 - (void)setEntitlements:(NSDictionary<NSString *, id> *)entitlements validated:(BOOL)validated {
371 currentEntitlements = entitlements;
372 currentEntitlementsValidated = validated;
373 id task = CFBridgingRelease(SecTaskCreateFromSelf(kCFAllocatorDefault));
374 NSArray *currentAccessGroups = CFBridgingRelease(SecTaskCopyAccessGroups((__bridge SecTaskRef)task));
375 SecAccessGroupsSetCurrent((__bridge CFArrayRef)currentAccessGroups); // SetCurrent retains the access groups
378 - (void)resetEntitlements {
379 currentEntitlements = nil;
380 currentEntitlementsValidated = YES;