2 * Copyright (c) 2016-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@
25 /* INSTRUCTIONS FOR ADDING NEW SUBTESTS:
26 * 1. Add the certificates, as DER-encoded files with the 'cer' extension, to OSX/shared_regressions/si-20-sectrust-policies-data/
27 * 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
28 * 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
30 * 2. Add a new dictionary to the test plist (OSX/shared_regressions/si-20-sectrust-policies-data/PinningPolicyTrustTest.plist).
31 * This dictionary must include: (see constants below)
38 * It is strongly recommended that all test dictionaries include the Anchors and VerifyDate keys.
39 * Addtional optional keys are defined below.
42 /* INSTRUCTIONS FOR DEBUGGING SUBTESTS:
43 * Add a debugging.plist to OSX/shared_regressions/si-20-sectrust-policies-data/ containing only those subtest dictionaries
47 #include <AssertMacros.h>
48 #import <XCTest/XCTest.h>
49 #import <Foundation/Foundation.h>
51 #include <utilities/SecInternalReleasePriv.h>
52 #include <utilities/SecCFRelease.h>
53 #include <utilities/SecCFWrappers.h>
54 #include <Security/SecCertificate.h>
55 #include <Security/SecCertificatePriv.h>
56 #include <Security/SecPolicyPriv.h>
57 #include <Security/SecPolicyInternal.h>
58 #include <Security/SecTrust.h>
59 #include <Security/SecTrustPriv.h>
61 #import "TrustEvaluationTestCase.h"
62 #include "../TestMacroConversions.h"
63 #include "../TrustEvaluationTestHelpers.h"
65 const NSString *kSecTrustTestPinningPolicyResources = @"si-20-sectrust-policies-data";
66 const NSString *kSecTrustTestPinnningTest = @"PinningPolicyTrustTest";
68 @interface PolicyTests : TrustEvaluationTestCase
71 @implementation PolicyTests
73 - (void)testPolicies {
74 NSURL *testPlist = nil;
75 NSArray *testsArray = nil;
77 testPlist = [[NSBundle bundleForClass:[self class]] URLForResource:@"debugging" withExtension:@"plist"
78 subdirectory:(NSString *)kSecTrustTestPinningPolicyResources];
80 testPlist = [[NSBundle bundleForClass:[self class]] URLForResource:(NSString *)kSecTrustTestPinnningTest withExtension:@"plist"
81 subdirectory:(NSString *)kSecTrustTestPinningPolicyResources];
84 fail("Failed to get tests plist from %@", kSecTrustTestPinningPolicyResources);
88 testsArray = [NSArray arrayWithContentsOfURL: testPlist];
90 fail("Failed to create array from plist");
94 [testsArray enumerateObjectsUsingBlock:^(NSDictionary *testDict, NSUInteger idx, BOOL * _Nonnull stop) {
95 TestTrustEvaluation *testObj = [[TestTrustEvaluation alloc] initWithTrustDictionary:testDict];
96 XCTAssertNotNil(testObj, "failed to create test object for %lu", (unsigned long)idx);
98 NSError *testError = nil;
99 XCTAssert([testObj evaluateForExpectedResults:&testError], "Test %@ failed: %@", testObj.fullTestName, testError);
103 - (void)testPinningDisable
105 SecCertificateRef baltimoreRoot = NULL, appleISTCA2 = NULL, pinnedNonCT = NULL;
106 SecTrustRef trust = NULL;
107 SecPolicyRef policy = SecPolicyCreateSSL(true, CFSTR("caldav.icloud.com"));
108 NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:580000000.0]; // May 19, 2019 at 4:06:40 PM PDT
109 NSArray *certs = nil, *enforcement_anchors = nil;
110 NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:@"com.apple.security"];
112 require_action(baltimoreRoot = (__bridge SecCertificateRef)[self SecCertificateCreateFromResource:@"BaltimoreCyberTrustRoot"
113 subdirectory:(NSString *)kSecTrustTestPinningPolicyResources],
114 errOut, fail("failed to create geotrust root"));
115 require_action(appleISTCA2 = (__bridge SecCertificateRef)[self SecCertificateCreateFromResource:@"AppleISTCA2G1-Baltimore"
116 subdirectory:(NSString *)kSecTrustTestPinningPolicyResources],
117 errOut, fail("failed to create apple IST CA"));
118 require_action(pinnedNonCT = (__bridge SecCertificateRef)[self SecCertificateCreateFromResource:@"caldav"
119 subdirectory:(NSString *)kSecTrustTestPinningPolicyResources],
120 errOut, fail("failed to create deprecated SSL Server cert"));
122 certs = @[ (__bridge id)pinnedNonCT, (__bridge id)appleISTCA2 ];
123 enforcement_anchors = @[ (__bridge id)baltimoreRoot ];
124 require_noerr_action(SecTrustCreateWithCertificates((__bridge CFArrayRef)certs, policy, &trust), errOut, fail("failed to create trust"));
125 require_noerr_action(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)date), errOut, fail("failed to set verify date"));
126 require_noerr_action(SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)enforcement_anchors), errOut, fail("failed to set anchors"));
127 require_noerr_action(SecTrustSetPinningPolicyName(trust, kSecPolicyNameAppleAMPService), errOut, fail("failed to set policy name"));
128 #if !TARGET_OS_BRIDGE
129 XCTAssertFalse(SecTrustEvaluateWithError(trust, NULL), "pinning against wrong profile succeeded");
132 // Test with pinning disabled
133 [defaults setBool:YES forKey:@"AppleServerAuthenticationNoPinning"];
134 [defaults synchronize];
135 SecTrustSetNeedsEvaluation(trust);
136 XCTAssert(SecTrustEvaluateWithError(trust, NULL), "failed to disable pinning");
137 [defaults setBool:NO forKey:@"AppleServerAuthenticationNoPinning"];
138 [defaults synchronize];
141 CFReleaseNull(baltimoreRoot);
142 CFReleaseNull(appleISTCA2);
143 CFReleaseNull(pinnedNonCT);
144 CFReleaseNull(policy);
145 CFReleaseNull(trust);
148 - (void)testSecPolicyReconcilePinningRequiredIfInfoSpecified
150 SecPolicyRef policy = SecPolicyCreateSSL(true, CFSTR("www.example.org"));
151 CFMutableArrayRef emptySPKISHA256 = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
153 CFMutableArrayRef nonemtpySPKISHA256 = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
154 uint8_t _random_data256[256/sizeof(uint8_t)];
155 (void)SecRandomCopyBytes(NULL, sizeof(_random_data256), _random_data256);
156 CFDataRef random_data256 = CFDataCreate(kCFAllocatorDefault, _random_data256, sizeof(_random_data256));
157 CFArrayAppendValue(nonemtpySPKISHA256, random_data256);
158 CFReleaseNull(random_data256);
160 // kSecPolicyCheckPinningRequired should be unset after reconciliation.
161 // Empty values for both SPKI policies signal a pinning exception.
162 SecPolicySetOptionsValue(policy, kSecPolicyCheckPinningRequired, kCFBooleanTrue);
163 SecPolicySetOptionsValue(policy, kSecPolicyCheckLeafSPKISHA256, emptySPKISHA256);
164 SecPolicySetOptionsValue(policy, kSecPolicyCheckCAspkiSHA256, emptySPKISHA256);
165 CFDictionaryRef policyOptions = SecPolicyGetOptions(policy);
166 is(CFDictionaryContainsKey(policyOptions, kSecPolicyCheckPinningRequired), true);
167 is(CFDictionaryContainsKey(policyOptions, kSecPolicyCheckLeafSPKISHA256), true);
168 is(CFDictionaryContainsKey(policyOptions, kSecPolicyCheckCAspkiSHA256), true);
169 SecPolicyReconcilePinningRequiredIfInfoSpecified((CFMutableDictionaryRef)policyOptions);
170 is(CFDictionaryContainsKey(policyOptions, kSecPolicyCheckPinningRequired), false);
171 is(CFDictionaryContainsKey(policyOptions, kSecPolicyCheckLeafSPKISHA256), false);
172 is(CFDictionaryContainsKey(policyOptions, kSecPolicyCheckCAspkiSHA256), false);
174 // kSecPolicyCheckPinningRequired overrules the other two policies.
175 SecPolicySetOptionsValue(policy, kSecPolicyCheckPinningRequired, kCFBooleanTrue);
176 SecPolicySetOptionsValue(policy, kSecPolicyCheckLeafSPKISHA256, nonemtpySPKISHA256);
177 SecPolicySetOptionsValue(policy, kSecPolicyCheckCAspkiSHA256, emptySPKISHA256);
178 policyOptions = SecPolicyGetOptions(policy);
179 is(CFDictionaryContainsKey(policyOptions, kSecPolicyCheckPinningRequired), true);
180 is(CFDictionaryContainsKey(policyOptions, kSecPolicyCheckLeafSPKISHA256), true);
181 is(CFDictionaryContainsKey(policyOptions, kSecPolicyCheckCAspkiSHA256), true);
182 SecPolicyReconcilePinningRequiredIfInfoSpecified((CFMutableDictionaryRef)policyOptions);
183 is(CFDictionaryContainsKey(policyOptions, kSecPolicyCheckPinningRequired), true);
184 is(CFDictionaryContainsKey(policyOptions, kSecPolicyCheckLeafSPKISHA256), false);
185 is(CFDictionaryContainsKey(policyOptions, kSecPolicyCheckCAspkiSHA256), false);
187 // kSecPolicyCheckPinningRequired overrules the other two policies.
188 SecPolicySetOptionsValue(policy, kSecPolicyCheckPinningRequired, kCFBooleanTrue);
189 SecPolicySetOptionsValue(policy, kSecPolicyCheckLeafSPKISHA256, emptySPKISHA256);
190 SecPolicySetOptionsValue(policy, kSecPolicyCheckCAspkiSHA256, nonemtpySPKISHA256);
191 policyOptions = SecPolicyGetOptions(policy);
192 is(CFDictionaryContainsKey(policyOptions, kSecPolicyCheckPinningRequired), true);
193 is(CFDictionaryContainsKey(policyOptions, kSecPolicyCheckLeafSPKISHA256), true);
194 is(CFDictionaryContainsKey(policyOptions, kSecPolicyCheckCAspkiSHA256), true);
195 SecPolicyReconcilePinningRequiredIfInfoSpecified((CFMutableDictionaryRef)policyOptions);
196 is(CFDictionaryContainsKey(policyOptions, kSecPolicyCheckPinningRequired), true);
197 is(CFDictionaryContainsKey(policyOptions, kSecPolicyCheckLeafSPKISHA256), false);
198 is(CFDictionaryContainsKey(policyOptions, kSecPolicyCheckCAspkiSHA256), false);
200 // In the absence of kSecPolicyCheckPinningRequired there is nothing to reconcile.
201 CFReleaseNull(policy);
202 policy = SecPolicyCreateSSL(true, CFSTR("www.example.org"));
203 SecPolicySetOptionsValue(policy, kSecPolicyCheckLeafSPKISHA256, emptySPKISHA256);
204 SecPolicySetOptionsValue(policy, kSecPolicyCheckCAspkiSHA256, emptySPKISHA256);
205 policyOptions = SecPolicyGetOptions(policy);
206 is(CFDictionaryContainsKey(policyOptions, kSecPolicyCheckPinningRequired), false);
207 is(CFDictionaryContainsKey(policyOptions, kSecPolicyCheckLeafSPKISHA256), true);
208 is(CFDictionaryContainsKey(policyOptions, kSecPolicyCheckCAspkiSHA256), true);
209 SecPolicyReconcilePinningRequiredIfInfoSpecified((CFMutableDictionaryRef)policyOptions);
210 is(CFDictionaryContainsKey(policyOptions, kSecPolicyCheckPinningRequired), false);
211 is(CFDictionaryContainsKey(policyOptions, kSecPolicyCheckLeafSPKISHA256), true);
212 is(CFDictionaryContainsKey(policyOptions, kSecPolicyCheckCAspkiSHA256), true);
214 CFReleaseNull(policy);
215 CFReleaseNull(emptySPKISHA256);
216 CFReleaseNull(nonemtpySPKISHA256);
219 - (CFDictionaryRef)getNSPinnedDomainsFromDictionaryInfoFile:(NSString *)fileName
221 NSURL *infoPlist = [[NSBundle bundleForClass:[self class]] URLForResource:fileName withExtension:@"plist"
222 subdirectory:(NSString *)kSecTrustTestPinningPolicyResources];
224 fail("Failed to access plist file \"%@\"", fileName);
228 NSDictionary *infoDictionary = [NSDictionary dictionaryWithContentsOfURL:infoPlist];
229 if (!infoDictionary) {
230 fail("Failed to create dictionary from plist file \"%@\"", fileName);
234 CFTypeRef nsAppTransportSecurityDict = CFDictionaryGetValue((__bridge CFDictionaryRef)infoDictionary, CFSTR("NSAppTransportSecurity"));
235 if (!isDictionary(nsAppTransportSecurityDict)) {
236 fail("NSAppTransportSecurity dictionary entry is missing from plist file \"%@\"", fileName);
240 CFDictionaryRef nsPinnedDomainsDict = CFDictionaryGetValue(nsAppTransportSecurityDict, CFSTR("NSPinnedDomains"));
241 if (!isDictionary(nsPinnedDomainsDict)) {
242 fail("NSPinnedDomains dictionary entry is missing from plist file \"%@\"", fileName);
246 return nsPinnedDomainsDict;
249 - (void)testParseNSPinnedDomains
251 NSURL *testPlist = nil;
252 NSArray *testsArray = nil;
255 testPlist = [[NSBundle bundleForClass:[self class]] URLForResource:@"NSPinnedDomainsParsingTest_debugging" withExtension:@"plist"
256 subdirectory:(NSString *)kSecTrustTestPinningPolicyResources];
258 testPlist = [[NSBundle bundleForClass:[self class]] URLForResource:@"NSPinnedDomainsParsingTest" withExtension:@"plist"
259 subdirectory:(NSString *)kSecTrustTestPinningPolicyResources];
262 fail("Failed to get tests plist from \"%@\"", kSecTrustTestPinningPolicyResources);
266 testsArray = [NSArray arrayWithContentsOfURL: testPlist];
268 fail("Failed to create array from plist");
272 [testsArray enumerateObjectsUsingBlock:^(NSDictionary *testDict, NSUInteger idx, BOOL * _Nonnull stop) {
273 NSString *plistFileName = testDict[@"PlistFileName"];
274 if (!plistFileName) {
275 fail("Failed to read plist file name from \"%@\":%lu", plistFileName, (unsigned long)idx);
279 NSDictionary *expectedProperties = testDict[@"ExpectedProperties"];
280 if (!expectedProperties) {
281 fail("Missing the expected results in \"%@\"", plistFileName);
284 bool hasNSPinnedLeafIdentities = [expectedProperties[@"NSPinnedLeafIdentities"] boolValue];
285 int NSPinnedLeafIdentitiesCount = [expectedProperties[@"NSPinnedLeafIdentitiesCount"] intValue];
286 bool hasNSPinnedCAIdentities = [expectedProperties[@"NSPinnedCAIdentities"] boolValue];
287 int NSPinnedCAIdentitiesCount = [expectedProperties[@"NSPinnedCAIdentitiesCount"] intValue];
288 bool hasNSIncludesSubdomains = [expectedProperties[@"NSIncludesSubdomains"] boolValue];
290 CFDictionaryRef nsPinnedDomainsDict = [self getNSPinnedDomainsFromDictionaryInfoFile:plistFileName];
291 if (!isDictionary(nsPinnedDomainsDict)) {
292 fail("Unable to read %@", plistFileName);
295 CFArrayRef leafSPKISHA256 = parseNSPinnedDomains(nsPinnedDomainsDict, CFSTR("example.org"), CFSTR("NSPinnedLeafIdentities"));
296 is(leafSPKISHA256 != NULL, hasNSPinnedLeafIdentities, "leafSPKISHA256 test failed in \"%@\"", plistFileName);
297 is(leafSPKISHA256 != NULL && CFArrayGetCount(leafSPKISHA256) != 0, NSPinnedLeafIdentitiesCount != 0, "leafSPKISHA256 count test failed in \"%@\"", plistFileName);
299 CFArrayRef caSPKISHA256 = parseNSPinnedDomains(nsPinnedDomainsDict, CFSTR("example.org"), CFSTR("NSPinnedCAIdentities"));
300 is(caSPKISHA256 != NULL, hasNSPinnedCAIdentities, "caSPKISHA256 test failed in \"%@\"", plistFileName);
301 is(caSPKISHA256 != NULL && CFArrayGetCount(caSPKISHA256) != 0, NSPinnedCAIdentitiesCount != 0, "caSPKISHA256 count test failed in \"%@\"", plistFileName);
303 caSPKISHA256 = parseNSPinnedDomains(nsPinnedDomainsDict, CFSTR("subdomain.example.org."), CFSTR("NSPinnedCAIdentities"));
304 is(caSPKISHA256 != NULL, hasNSIncludesSubdomains, "caSPKISHA256 subdomain test failed in \"%@\"", plistFileName);