]> git.saurik.com Git - apple/security.git/blob - secdxctests/KeychainXCTest.m
Security-59754.80.3.tar.gz
[apple/security.git] / secdxctests / KeychainXCTest.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 "KeychainXCTest.h"
25 #import "SecDbKeychainItem.h"
26 #import "SecdTestKeychainUtilities.h"
27 #import "CKKS.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"
36 #import "spi.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"
54
55 void* testlist = NULL;
56
57 // TODO: Switch to '1' closer to deployment, but leave at '0' for now to test not breaking people
58 static int testCheckV12DevEnabled(void) {
59 return 0;
60 }
61
62 #if USE_KEYSTORE
63
64 @interface SecDbKeychainItemV7 ()
65
66 + (SFAESKeySpecifier*)keySpecifier;
67
68 @end
69
70 @interface FakeAKSRefKey : NSObject <SecAKSRefKey>
71 @end
72
73 @implementation FakeAKSRefKey {
74 SFAESKey* _key;
75 }
76
77 - (instancetype)initWithKeybag:(keybag_handle_t)keybag keyclass:(keyclass_t)keyclass
78 {
79 if (self = [super init]) {
80 _key = [[SFAESKey alloc] initRandomKeyWithSpecifier:[[SFAESKeySpecifier alloc] initWithBitSize:SFAESKeyBitSize256] error:nil];
81 }
82
83 return self;
84 }
85
86 - (instancetype)initWithBlob:(NSData*)blob keybag:(keybag_handle_t)keybag
87 {
88 if (self = [super init]) {
89 _key = [[SFAESKey alloc] initWithData:blob specifier:[[SFAESKeySpecifier alloc] initWithBitSize:SFAESKeyBitSize256] error:nil];
90 }
91
92 return self;
93 }
94
95 - (NSData*)wrappedDataForKey:(SFAESKey*)key
96 {
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];
99 }
100
101 - (SFAESKey*)keyWithWrappedData:(NSData*)wrappedKeyData
102 {
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];
107 }
108
109 - (NSData*)refKeyBlob
110 {
111 return _key.keyData;
112 }
113
114 @end
115
116 @implementation SFKeychainServerFakeConnection {
117 NSArray* _fakeAccessGroups;
118 }
119
120 - (void)setFakeAccessGroups:(NSArray*)fakeAccessGroups
121 {
122 _fakeAccessGroups = fakeAccessGroups.copy;
123 }
124
125 - (NSArray*)clientAccessGroups
126 {
127 return _fakeAccessGroups ?: @[@"com.apple.token"];
128 }
129
130 @end
131
132 @implementation KeychainXCTest {
133 id _keychainPartialMock;
134 CFArrayRef _originalAccessGroups;
135 bool _simcrashenabled;
136 }
137
138 @synthesize keychainPartialMock = _keychainPartialMock;
139
140 + (void)setUp
141 {
142 [super setUp];
143 SecCKKSDisable();
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);
147 }
148
149 - (void)setUp
150 {
151 _simcrashenabled = __security_simulatecrash_enabled();
152 __security_simulatecrash_enable(false);
153
154 [super setUp];
155
156 self.lockState = LockStateUnlocked;
157 self.allowDecryption = true;
158 self.didAKSDecrypt = NO;
159 self.simulateRolledAKSKey = NO;
160
161
162 self.keyclassUsedForAKSDecryption = 0;
163
164 self.keySpecifier = [[SFAESKeySpecifier alloc] initWithBitSize:SFAESKeyBitSize256];
165 [self setNewFakeAKSKey:[NSData dataWithBytes:"1234567890123456789012345678901" length:32]];
166
167 self.mockSecDbKeychainItemV7 = OCMClassMock([SecDbKeychainItemV7 class]);
168 [[[self.mockSecDbKeychainItemV7 stub] andCall:@selector(decryptionOperation) onObject:self] decryptionOperation];
169
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];
173
174 // bring back with <rdar://problem/37523001>
175 // [[[self.mockSecDbKeychainItemV7 stub] andCall:@selector(isKeychainUnlocked) onObject:self] isKeychainUnlocked];
176
177 id refKeyMock = OCMClassMock([SecAKSRefKey class]);
178 [[[refKeyMock stub] andCall:@selector(alloc) onObject:[FakeAKSRefKey class]] alloc];
179
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);
184
185 _originalAccessGroups = SecAccessGroupsGetCurrent();
186 SecResetLocalSecuritydXPCFakeEntitlements();
187 }
188
189 - (void)tearDown
190 {
191 [self.mockSecDbKeychainItemV7 stopMocking];
192 [self.mockSecAKSObjCWrappers stopMocking];
193 [self resetEntitlements];
194 SecAccessGroupsSetCurrent(_originalAccessGroups);
195 __security_simulatecrash_enable(_simcrashenabled);
196
197 [super tearDown];
198 }
199
200 + (void)tearDown {
201 SecResetLocalSecuritydXPCFakeEntitlements();
202 [super tearDown];
203 }
204
205 - (bool)isKeychainUnlocked
206 {
207 return self.lockState == LockStateUnlocked;
208 }
209
210 - (id)decryptionOperation
211 {
212 return self.allowDecryption ? [[SFAuthenticatedEncryptionOperation alloc] initWithKeySpecifier:[SecDbKeychainItemV7 keySpecifier]] : nil;
213 }
214
215 - (bool)setNewFakeAKSKey:(NSData*)newKeyData
216 {
217 self.fakeAKSKey = newKeyData;
218 return true;
219 }
220
221 - (NSData*)wrapKey:(NSData*)plaintextKey withKey:(NSData*)wrappingKey
222 {
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)];
226
227 ccecb_init(ecb_mode, key, wrappingKey.length, wrappingKey.bytes);
228
229 size_t obytes = 0;
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);
234 } else {
235 wrappedKey = nil;
236 }
237
238 ccecb_ctx_clear(ccecb_context_size(ecb_mode), key);
239 return wrappedKey;
240 }
241
242 - (NSData*)unwrapKey:(NSData*)ciphertextKey withKey:(NSData*)wrappingKey
243 {
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)];
247
248 ccecb_init(ecb_mode, key, wrappingKey.length, wrappingKey.bytes);
249
250 size_t obytes = 0;
251 int status = ccwrap_auth_decrypt(ecb_mode, key, ciphertextKey.length, ciphertextKey.bytes,
252 &obytes, unwrappedKey.mutableBytes);
253 if (status == 0) {
254 assert(obytes == (size_t)[unwrappedKey length]);
255 } else {
256 unwrappedKey = nil;
257 }
258
259 ccecb_ctx_clear(ccecb_context_size(ecb_mode), key);
260 return unwrappedKey;
261 }
262
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
269 {
270 if (self.lockState == LockStateLockedAndDisallowAKS) {
271 if (error) {
272 *error = [NSError errorWithDomain:(__bridge NSString *)kSecErrorDomain code:errSecInteractionNotAllowed userInfo:NULL];
273 }
274 return false;
275 }
276
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);
281 }
282
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;
291 }
292 return true;
293 } else {
294 XCTFail(@"output buffer too small for wrapped key");
295 return false;
296 }
297 }
298
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
305 {
306 if (self.lockState == LockStateLockedAndDisallowAKS) {
307 if (error) {
308 *error = [NSError errorWithDomain:(__bridge NSString *)kSecErrorDomain code:errSecInteractionNotAllowed userInfo:NULL];
309 }
310 return false;
311 }
312
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
315 return false;
316 }
317
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;
324 return true;
325 } else if (unwrappedKey) {
326 XCTFail(@"output buffer too small for unwrapped key");
327 return false;
328 } else {
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];
332 }
333 else if (error) {
334 *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:errSecDecode userInfo:nil];
335 }
336 return false;
337 }
338 }
339
340 - (NSData*)getDatabaseKeyDataWithError:(NSError**)error
341 {
342 if (_lockState == LockStateUnlocked) {
343 return [NSData dataWithBytes:"1234567890123456789012345678901" length:32];
344 }
345 else {
346 if (error) {
347 // <rdar://problem/38972671> add SFKeychainErrorDeviceLocked
348 *error = [NSError errorWithDomain:SFKeychainErrorDomain code:SFKeychainErrorFailedToCommunicateWithServer userInfo:nil];
349 }
350 return nil;
351 }
352 }
353
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;
357
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]);
362 }
363 return CFBridgingRetain(value);
364 }
365
366 Boolean SecTaskEntitlementsValidated(SecTaskRef task) {
367 return currentEntitlementsValidated;
368 }
369
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
376 }
377
378 - (void)resetEntitlements {
379 currentEntitlements = nil;
380 currentEntitlementsValidated = YES;
381 }
382
383 @end
384
385 #endif