]> git.saurik.com Git - apple/security.git/blob - tests/TrustTests/TrustEvaluationTestHelpers.m
Security-59306.61.1.tar.gz
[apple/security.git] / tests / TrustTests / TrustEvaluationTestHelpers.m
1 //
2 // TrustEvaluationTestHelpers.m
3 // Security
4 //
5 //
6
7 #include <AssertMacros.h>
8 #import <Foundation/Foundation.h>
9 #import <Security/Security.h>
10
11 #include <utilities/SecInternalReleasePriv.h>
12 #include <utilities/SecCFRelease.h>
13 #include <Security/SecCertificate.h>
14 #include <Security/SecCertificatePriv.h>
15 #include <Security/SecPolicyPriv.h>
16 #include <Security/SecTrust.h>
17 #include <Security/SecTrustPriv.h>
18
19 #include "TrustEvaluationTestHelpers.h"
20
21 // Want a class for running trust evaluations
22 // Want a dictionary-driven test with callback
23
24 @interface TestTrustEvaluation ()
25 @property NSString *directory;
26 @property NSMutableArray *certificates;
27 @property NSMutableArray *policies;
28 @property BOOL enableTestCertificates;
29 @end
30
31 @implementation TestTrustEvaluation
32 @synthesize ocspResponses = _ocspResponses;
33 @synthesize presentedSCTs = _presentedSCTs;
34 @synthesize trustedCTLogs = _trustedCTLogs;
35
36
37 - (instancetype)initWithCertificates:(NSArray *)certs policies:(NSArray *)policies {
38 if (self = [super init]) {
39 if (errSecSuccess != SecTrustCreateWithCertificates((__bridge CFArrayRef)certs, (__bridge CFArrayRef)policies, &_trust)) {
40 return NULL;
41 }
42 }
43 return self;
44 }
45
46 - (void)addAnchor:(SecCertificateRef)certificate
47 {
48 CFArrayRef anchors = NULL;
49 SecTrustCopyCustomAnchorCertificates(_trust, &anchors);
50
51 NSMutableArray *newAnchors = [NSMutableArray array];
52 if (anchors) {
53 [newAnchors addObjectsFromArray:CFBridgingRelease(anchors)];
54 }
55 [newAnchors addObject:(__bridge id)certificate];
56 (void)SecTrustSetAnchorCertificates(_trust, (__bridge CFArrayRef)newAnchors);
57 }
58
59 - (void)setAnchors:(NSArray *)anchorArray {
60 (void)SecTrustSetAnchorCertificates(_trust, (__bridge CFArrayRef)anchorArray);
61 }
62
63 - (NSArray *)anchors {
64 CFArrayRef anchors = NULL;
65 SecTrustCopyCustomAnchorCertificates(_trust, &anchors);
66 return CFBridgingRelease(anchors);
67 }
68
69 - (void)setOcspResponses:(NSArray *)ocspResponsesArray {
70 if (ocspResponsesArray != _ocspResponses) {
71 _ocspResponses = nil;
72 _ocspResponses = ocspResponsesArray;
73 (void)SecTrustSetOCSPResponse(_trust, (__bridge CFArrayRef)ocspResponsesArray);
74 }
75 }
76
77 - (void)setPresentedSCTs:(NSArray *)presentedSCTsArray {
78 if (presentedSCTsArray != _presentedSCTs) {
79 _presentedSCTs = nil;
80 _presentedSCTs = presentedSCTsArray;
81 (void)SecTrustSetSignedCertificateTimestamps(_trust, (__bridge CFArrayRef)presentedSCTsArray);
82 }
83 }
84
85 - (void)setTrustedCTLogs:(NSArray *)trustedCTLogsArray {
86 if (trustedCTLogsArray != _trustedCTLogs) {
87 _trustedCTLogs = nil;
88 _trustedCTLogs = trustedCTLogsArray;
89 (void)SecTrustSetTrustedLogs(_trust, (__bridge CFArrayRef)trustedCTLogsArray);
90 }
91 }
92
93 - (void)setVerifyDate:(NSDate *)aVerifyDate {
94 if (aVerifyDate != _verifyDate) {
95 _verifyDate = nil;
96 _verifyDate = aVerifyDate;
97 (void)SecTrustSetVerifyDate(_trust, (__bridge CFDateRef)aVerifyDate);
98 }
99 }
100
101 - (bool)evaluate:(out NSError * _Nullable __autoreleasing *)outError {
102 CFErrorRef localError = nil;
103 _trustResult = kSecTrustResultInvalid;
104 _resultDictionary = nil;
105 bool result = SecTrustEvaluateWithError(_trust, &localError);
106 if (outError && localError) {
107 *outError = CFBridgingRelease(localError);
108 }
109 (void)SecTrustGetTrustResult(_trust, &_trustResult);
110 _resultDictionary = CFBridgingRelease(SecTrustCopyResult(_trust));
111 return result;
112 }
113
114 - (void) dealloc {
115 CFReleaseNull(_trust);
116 }
117
118 /* MARK: -
119 * MARK: Dictionary-driven trust objects
120 */
121
122 /* INSTRUCTIONS FOR ADDING NEW DICTIONARY-DRIVEN TRUSTS:
123 * 1. Add the certificates, as DER-encoded files with the 'cer' extension to a directory included in the test Resources
124 * (e.g. OSX/shared_regressions/si-20-sectrust-policies-data/)
125 * NOTE: If your cert needs to be named with "(i[Pp]hone|i[Pp]ad|i[Pp]od)", you need to make two copies -- one named properly
126 * and another named such that it doesn't match that regex. Use the regex trick below for TARGET_OS_TV to make sure your test
127 * works.
128 * 2. The input dictionary must include: (see constants below)
129 * MajorTestName
130 * MinorTestName
131 * Policies
132 * Leaf
133 * ExpectedResult
134 * CertDirectory
135 * It is strongly recommended that all test dictionaries include the Anchors and VerifyDate keys.
136 * Addtional optional keys are defined below.
137 */
138
139 /* Key Constants for Test Dictionaries */
140 const NSString *kSecTrustTestMajorTestName = @"MajorTestName"; /* Required; value: string */
141 const NSString *kSecTrustTestMinorTestName = @"MinorTestName"; /* Required; value: string */
142 const NSString *kSecTrustTestPolicies = @"Policies"; /* Required; value: dictionary or array of dictionaries */
143 const NSString *kSecTrustTestLeaf = @"Leaf"; /* Required; value: string */
144 const NSString *kSecTrustTestIntermediates = @"Intermediates"; /* Optional; value: string or array of strings */
145 const NSString *kSecTrustTestAnchors = @"Anchors"; /* Recommended; value: string or array of strings */
146 const NSString *kSecTrustTestVerifyDate = @"VerifyDate"; /* Recommended; value: date */
147 const NSString *kSecTrustTestExpectedResult = @"ExpectedResult"; /* Required; value: number */
148 const NSString *kSecTrustTestChainLength = @"ChainLength"; /* Optional; value: number */
149 const NSString *kSecTrustTestEnableTestCerts= @"EnableTestCertificates"; /* Optional; value: string */
150 const NSString *kSecTrustTestDisableBridgeOS= @"BridgeOSDisable"; /* Optional; value: boolean */
151 const NSString *kSecTrustTestDirectory = @"CertDirectory"; /* Required; value: string */
152
153 /* Key Constants for Policies Dictionaries */
154 const NSString *kSecTrustTestPolicyOID = @"PolicyIdentifier"; /* Required; value: string */
155 const NSString *kSecTrustTestPolicyProperties = @"Properties"; /* Optional; value: dictionary, see Policy Value Constants, SecPolicy.h */
156
157 - (void)setMajorTestName:(NSString *)majorTestName minorTestName:(NSString *)minorTestName {
158 self.fullTestName = [[majorTestName stringByAppendingString:@"-"] stringByAppendingString:minorTestName];
159 }
160
161 #if TARGET_OS_TV
162 /* Mastering removes all files named i[Pp]hone, so dynamically replace any i[Pp]hone's with
163 * iPh0ne. We have two copies in the resources directory. */
164 - (NSString *)replaceiPhoneNamedFiles:(NSString *)filename {
165 NSRegularExpression *regularExpression = [NSRegularExpression regularExpressionWithPattern:@"iphone"
166 options:NSRegularExpressionCaseInsensitive
167 error:nil];
168 NSString *newfilename = [regularExpression stringByReplacingMatchesInString:filename
169 options:0
170 range:NSMakeRange(0, [filename length])
171 withTemplate:@"iPh0ne"];
172 return newfilename;
173 }
174 #endif
175
176 - (bool)addLeafToCertificates:(NSString *)leafName {
177 SecCertificateRef cert;
178 NSString *path = nil, *filename = nil;
179 require_string(leafName, errOut, "%@: failed to get leaf for test");
180 #if TARGET_OS_TV
181 filename = [self replaceiPhoneNamedFiles:leafName];
182 #else
183 filename = leafName;
184 #endif
185
186 path = [[NSBundle bundleForClass:[self class]]
187 pathForResource:filename
188 ofType:@"cer"
189 inDirectory:self.directory];
190 require_string(path, errOut, "failed to get path for leaf");
191 cert = SecCertificateCreateWithData(NULL, (CFDataRef)[NSData dataWithContentsOfFile:path]);
192 require_string(cert, errOut, "failed to create leaf certificate from path");
193 self.certificates = [[NSMutableArray alloc] initWithObjects:(__bridge id)cert, nil];
194 CFReleaseNull(cert);
195 require_string(self.certificates, errOut, "failed to initialize certificates array");
196 return true;
197
198 errOut:
199 return false;
200 }
201
202 - (bool)addCertsToArray:(id)pathsObj outputArray:(NSMutableArray *)outArray {
203 __block SecCertificateRef cert = NULL;
204 __block NSString* path = nil, *filename = nil;
205 require_string(pathsObj, errOut,
206 "failed to get certificate paths for test");
207
208 if ([pathsObj isKindOfClass:[NSString class]]) {
209 /* Only one cert path */
210 #if TARGET_OS_TV
211 filename = [self replaceiPhoneNamedFiles:pathsObj];
212 #else
213 filename = pathsObj;
214 #endif
215 path = [[NSBundle bundleForClass:[self class]]
216 pathForResource:filename
217 ofType:@"cer"
218 inDirectory:self.directory];
219 require_string(path, errOut, "failed to get path for cert");
220 cert = SecCertificateCreateWithData(NULL, (CFDataRef)[NSData dataWithContentsOfFile:path]);
221 require_string(cert, errOut, "failed to create certificate from path");
222 [outArray addObject:(__bridge id)cert];
223 CFReleaseNull(cert);
224 } else if ([pathsObj isKindOfClass:[NSArray class]]) {
225 /* Test has more than one intermediate */
226 [(NSArray *)pathsObj enumerateObjectsUsingBlock:^(NSString *resource, NSUInteger idx, BOOL *stop) {
227 #if TARGET_OS_TV
228 filename = [self replaceiPhoneNamedFiles:resource];
229 #else
230 filename = resource;
231 #endif
232 path = [[NSBundle bundleForClass:[self class]]
233 pathForResource:filename
234 ofType:@"cer"
235 inDirectory:self.directory];
236 require_string(path, blockOut, "failed to get path for cert");
237 cert = SecCertificateCreateWithData(NULL, (CFDataRef)[NSData dataWithContentsOfFile:path]);
238 require_string(cert, blockOut, "failed to create certificate %ld from path %@");
239 [outArray addObject:(__bridge id)cert];
240
241 CFReleaseNull(cert);
242 return;
243
244 blockOut:
245 CFReleaseNull(cert);
246 *stop = YES;
247 }];
248 } else {
249 require_string(false, errOut, "unexpected type for intermediates or anchors value");
250 }
251
252 return true;
253
254 errOut:
255 CFReleaseNull(cert);
256 return false;
257 }
258
259 - (bool)addIntermediatesToCertificates:(id)intermediatesObj {
260 require_string(intermediatesObj, errOut, "failed to get intermediates for test");
261
262 require_string([self addCertsToArray:intermediatesObj outputArray:self.certificates], errOut,
263 "failed to add intermediates to certificates array");
264
265 if ([intermediatesObj isKindOfClass:[NSString class]]) {
266 require_string([self.certificates count] == 2, errOut,
267 "failed to add all intermediates");
268 } else if ([intermediatesObj isKindOfClass:[NSArray class]]) {
269 require_string([self.certificates count] == [(NSArray *)intermediatesObj count] + 1, errOut,
270 "failed to add all intermediates");
271 }
272
273 return true;
274
275 errOut:
276 return false;
277 }
278
279 - (bool)addPolicy:(NSDictionary *)policyDict
280 {
281 SecPolicyRef policy = NULL;
282 NSString *policyIdentifier = [(NSDictionary *)policyDict objectForKey:kSecTrustTestPolicyOID];
283 NSDictionary *policyProperties = [(NSDictionary *)policyDict objectForKey:kSecTrustTestPolicyProperties];
284 require_string(policyIdentifier, errOut, "failed to get policy OID");
285
286 policy = SecPolicyCreateWithProperties((__bridge CFStringRef)policyIdentifier,
287 (__bridge CFDictionaryRef)policyProperties);
288 require_string(policy, errOut, "failed to create properties for policy OID");
289 [self.policies addObject:(__bridge id)policy];
290 CFReleaseNull(policy);
291
292 return true;
293 errOut:
294 return false;
295 }
296
297 - (bool)addPolicies:(id)policiesObj {
298 require_string(policiesObj, errOut,
299 "failed to get policies for test");
300
301 self.policies = [[NSMutableArray alloc] init];
302 require_string(self.policies, errOut,
303 "failed to initialize policies array");
304 if ([policiesObj isKindOfClass:[NSDictionary class]]) {
305 /* Test has only one policy */
306 require_string([self addPolicy:policiesObj], errOut, "failed to add policy");
307 } else if ([policiesObj isKindOfClass:[NSArray class]]) {
308 /* Test more than one policy */
309 [(NSArray *)policiesObj enumerateObjectsUsingBlock:^(NSDictionary *policyDict, NSUInteger idx, BOOL *stop) {
310 if (![self addPolicy:policyDict]) {
311 *stop = YES;
312 }
313 }];
314
315 require_string([(NSArray *)policiesObj count] == [self.policies count], errOut, "failed to add all policies");
316 } else {
317 require_string(false, errOut, "unexpected type for Policies value");
318 }
319
320 return true;
321
322 errOut:
323 return false;
324 }
325
326 - (bool)setAnchorsFromPlist:(id)anchorsObj {
327 NSMutableArray *anchors = [NSMutableArray array];
328 require_string(anchorsObj, errOut, "failed to get anchors for test");
329 require_string([self addCertsToArray:anchorsObj outputArray:anchors], errOut, "failed to add anchors to anchors array");
330
331 if ([anchorsObj isKindOfClass:[NSString class]]) {
332 require_string([anchors count] == 1, errOut, "failed to add all anchors");
333 } else if ([anchorsObj isKindOfClass:[NSArray class]]) {
334 require_string([anchors count] == [(NSArray *)anchorsObj count], errOut, "failed to add all anchors");
335 }
336
337 // set the anchors in the SecTrustRef
338 self.anchors = anchors;
339 return true;
340
341 errOut:
342 return false;
343 }
344
345 - (instancetype _Nullable) initWithTrustDictionary:(NSDictionary *)testDict
346 {
347 if (!(self = [super init])) {
348 return self;
349 }
350
351 NSString *majorTestName = nil, *minorTestName = nil;
352 SecTrustRef trust = NULL;
353
354 #if TARGET_OS_BRIDGE
355 /* Some of the tests don't work on bridgeOS because there is no Certificates bundle. Skip them. */
356 if([testDict[kSecTrustTestDisableBridgeOS] boolValue]) {
357 self.bridgeOSDisabled = YES;
358 }
359 #endif
360
361 /* Test certificates work by default on internal builds. We still need this to
362 * determine whether to expect failure for production devices. */
363 self.enableTestCertificates = [testDict[kSecTrustTestEnableTestCerts] boolValue];
364
365 /* Test name, for documentation purposes */
366 majorTestName = testDict[kSecTrustTestMajorTestName];
367 minorTestName = testDict[kSecTrustTestMinorTestName];
368 require_string(majorTestName && minorTestName, errOut, "Failed to create test names for test");
369 [self setMajorTestName:majorTestName minorTestName:minorTestName];
370
371 #if DEBUG
372 fprintf(stderr, "BEGIN trust creation for %s", [self.fullTestName cStringUsingEncoding:NSUTF8StringEncoding]);
373 #endif
374
375 /* Cert directory */
376 self.directory = testDict[kSecTrustTestDirectory];
377 require_string(self.directory, errOut, "No directory for test!");
378
379 /* Populate the certificates array */
380 require_quiet([self addLeafToCertificates:testDict[kSecTrustTestLeaf]], errOut);
381
382 /* Add optional intermediates to certificates array */
383 if (testDict[kSecTrustTestIntermediates]) {
384 require_quiet([self addIntermediatesToCertificates:testDict[kSecTrustTestIntermediates]], errOut);
385 }
386
387 /* Create the policies */
388 #if !TARGET_OS_BRIDGE
389 require_quiet([self addPolicies:testDict[kSecTrustTestPolicies]], errOut);
390 #else // TARGET_OS_BRIDGE
391 if (![self addPolicies:testDict[kSecTrustTestPolicies]]) {
392 /* Some policies aren't available on bridgeOS (because there is no Certificates bundle on bridgeOS).
393 * If we fail to add the policies for a disabled test, let SecTrustCreate fall back to the Basic policy.
394 * We'll skip the evaluation and other tests by honoring bridgeOSDisabled, but we need to return a
395 * TestTrustEvaluation object so that the test continues. */
396 if (self.bridgeOSDisabled) {
397 self.policies = nil;
398 } else {
399 goto errOut;
400 }
401 }
402 #endif // TARGET_OS_BRIDGE
403
404
405 /* Create the trust object */
406 require_noerr_string(SecTrustCreateWithCertificates((__bridge CFArrayRef)self.certificates,
407 (__bridge CFArrayRef)self.policies,
408 &trust),
409 errOut,
410 "failed to create trust ref");
411 self.trust = trust;
412
413 /* Optionally set anchors in trust object */
414 if (testDict[kSecTrustTestAnchors]) {
415 require_quiet([self setAnchorsFromPlist:testDict[kSecTrustTestAnchors]], errOut);
416 }
417
418 /* Set optional date in trust object */
419 if (testDict[kSecTrustTestVerifyDate]) {
420 self.verifyDate = testDict[kSecTrustTestVerifyDate];
421 }
422
423 /* Set expected results */
424 self.expectedResult = testDict[kSecTrustTestExpectedResult];
425 self.expectedChainLength = testDict[kSecTrustTestChainLength];
426
427 #if DEBUG
428 fprintf(stderr, "END trust creation for %s", [self.fullTestName cStringUsingEncoding:NSUTF8StringEncoding]);
429 #endif
430
431 return self;
432
433 errOut:
434 return nil;
435 }
436
437 - (bool)evaluateForExpectedResults:(out NSError * _Nullable __autoreleasing *)outError
438 {
439 #if TARGET_OS_BRIDGE
440 // Artificially skip tests for bridgeOS. To prevent test errors these need to be reported as a pass.
441 if (self.bridgeOSDisabled) {
442 return true;
443 }
444 #endif
445
446 if (!self.expectedResult) {
447 if (outError) {
448 NSString *errorDescription = [NSString stringWithFormat:@"Test %@: no expected results set",
449 self.fullTestName];
450 *outError = [NSError errorWithDomain:@"TrustTestsError" code:(-1)
451 userInfo:@{ NSLocalizedFailureReasonErrorKey : errorDescription}];
452 }
453 return false;
454 }
455
456 SecTrustResultType trustResult = kSecTrustResultInvalid;
457 if (errSecSuccess != SecTrustGetTrustResult(self.trust, &trustResult)) {
458 if (outError) {
459 NSString *errorDescription = [NSString stringWithFormat:@"Test %@: Failed to get trust result",
460 self.fullTestName];
461 *outError = [NSError errorWithDomain:@"TrustTestsError" code:(-2)
462 userInfo:@{ NSLocalizedFailureReasonErrorKey : errorDescription}];
463 }
464 return false;
465 }
466
467 bool result = false;
468
469 /* If we enabled test certificates on a non-internal device, expect a failure instead of success. */
470 if (self.enableTestCertificates && !SecIsInternalRelease() && ([self.expectedResult unsignedIntValue] == 4)) {
471 if (trustResult == kSecTrustResultRecoverableTrustFailure) {
472 result = true;
473 }
474 } else if (trustResult == [self.expectedResult unsignedIntValue]) {
475 result = true;
476 }
477
478 if (!result) {
479 if (outError) {
480 NSString *errorDescription = [NSString stringWithFormat:@"Test %@: Expected result %@ %s does not match actual result %u %s",
481 self.fullTestName, self.expectedResult,
482 (self.enableTestCertificates ? "for test cert" : ""),
483 trustResult,
484 SecIsInternalRelease() ? "" : "on prod device"];
485 *outError = [NSError errorWithDomain:@"TrustTestsError" code:(-3)
486 userInfo:@{ NSLocalizedFailureReasonErrorKey : errorDescription}];
487 }
488 return result;
489 }
490
491 /* expected chain length is optional, but if we have it, verify */
492 if (self.expectedChainLength && (SecTrustGetCertificateCount(self.trust) != [self.expectedChainLength longValue])) {
493 if (outError) {
494 NSString *errorDescription = [NSString stringWithFormat:@"Test %@: Expected chain length %@ does not match actual chain length %ld",
495 self.fullTestName, self.expectedChainLength, SecTrustGetCertificateCount(self.trust)];
496 *outError = [NSError errorWithDomain:@"TrustTestsError" code:(-4)
497 userInfo:@{ NSLocalizedFailureReasonErrorKey : errorDescription}];
498 }
499 return false;
500 }
501 return true;
502 }
503
504 @end