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 #include <AssertMacros.h>
25 #import <Foundation/Foundation.h>
26 #include <stdatomic.h>
28 #include <Security/Security.h>
29 #include <Security/SecTrustSettingsPriv.h>
30 #include <Security/SecPolicyPriv.h>
31 #include <utilities/SecFileLocations.h>
32 #include <utilities/SecCFWrappers.h>
33 #import "OTATrustUtilities.h"
34 #include "SecTrustStoreServer.h"
36 typedef bool(*exceptionsArrayValueChecker)(id _Nonnull obj);
38 static bool checkDomainsValuesCompliance(id _Nonnull obj) {
39 if (![obj isKindOfClass:[NSString class]]) {
42 if (SecDNSIsTLD((__bridge CFStringRef)obj)) {
48 static bool checkCAsValuesCompliance(id _Nonnull obj) {
49 if (![obj isKindOfClass:[NSDictionary class]]) {
52 if (2 != [(NSDictionary*)obj count]) {
55 if (nil == ((NSDictionary*)obj)[(__bridge NSString*)kSecCTExceptionsHashAlgorithmKey] ||
56 nil == ((NSDictionary*)obj)[(__bridge NSString*)kSecCTExceptionsSPKIHashKey]) {
59 if (![((NSDictionary*)obj)[(__bridge NSString*)kSecCTExceptionsHashAlgorithmKey] isKindOfClass:[NSString class]] ||
60 ![((NSDictionary*)obj)[(__bridge NSString*)kSecCTExceptionsSPKIHashKey] isKindOfClass:[NSData class]]) {
63 if (![((NSDictionary*)obj)[(__bridge NSString*)kSecCTExceptionsHashAlgorithmKey] isEqualToString:@"sha256"]) {
69 static bool checkExceptionsValues(NSString *key, id value, exceptionsArrayValueChecker checker, CFErrorRef *error) {
70 if (![value isKindOfClass:[NSArray class]]) {
71 return SecError(errSecParam, error, CFSTR("value for %@ is not an array in exceptions dictionary"), key);
74 __block bool result = true;
75 [(NSArray*)value enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
77 result = SecError(errSecParam, error, CFSTR("value %lu for %@ is not the expected type"), (unsigned long)idx, key);
84 static bool checkInputExceptionsAndSetAppExceptions(NSDictionary *inExceptions, NSMutableDictionary *appExceptions, CFErrorRef *error) {
85 __block bool result = true;
86 [inExceptions enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
87 if ([key isEqualToString:(__bridge NSString*)kSecCTExceptionsDomainsKey]) {
88 if (!checkExceptionsValues(key, obj, checkDomainsValuesCompliance, error)) {
93 } else if ([key isEqualToString:(__bridge NSString*)kSecCTExceptionsCAsKey]) {
94 if (!checkExceptionsValues(key, obj, checkCAsValuesCompliance, error)) {
100 result = SecError(errSecParam, error, CFSTR("unknown key (%@) in exceptions dictionary"), key);
105 if ([(NSArray*)obj count] == 0) {
106 [appExceptions removeObjectForKey:key];
108 appExceptions[key] = obj;
114 static _Atomic bool gHasCTExceptions = false;
115 #define kSecCTExceptionsChanged "com.apple.trustd.ct.exceptions-changed"
117 bool _SecTrustStoreSetCTExceptions(CFStringRef appID, CFDictionaryRef exceptions, CFErrorRef *error) {
118 if (!SecOTAPKIIsSystemTrustd()) {
119 return SecError(errSecWrPerm, error, CFSTR("Unable to write CT exceptions from user agent"));
123 return SecError(errSecParam, error, CFSTR("application-identifier required to set exceptions"));
128 NSURL *keychainsDirectory = CFBridgingRelease(SecCopyURLForFileInKeychainDirectory(nil));
130 NSURL *keychainsDirectory = [NSURL fileURLWithFileSystemRepresentation:"/Library/Keychains/" isDirectory:YES relativeToURL:nil];
132 NSURL *ctExceptionsFile = [keychainsDirectory URLByAppendingPathComponent:@"CTExceptions.plist"];
133 NSMutableDictionary *allExceptions = [NSMutableDictionary dictionaryWithContentsOfURL:ctExceptionsFile];
134 NSMutableDictionary *appExceptions = NULL;
135 if (allExceptions && allExceptions[(__bridge NSString*)appID]) {
136 appExceptions = [allExceptions[(__bridge NSString*)appID] mutableCopy];
138 appExceptions = [NSMutableDictionary dictionary];
139 if (!allExceptions) {
140 allExceptions = [NSMutableDictionary dictionary];
144 if (exceptions && (CFDictionaryGetCount(exceptions) > 0)) {
145 NSDictionary *inExceptions = (__bridge NSDictionary*)exceptions;
146 if (!checkInputExceptionsAndSetAppExceptions(inExceptions, appExceptions, error)) {
151 if (!exceptions || [appExceptions count] == 0) {
152 [allExceptions removeObjectForKey:(__bridge NSString*)appID];
154 allExceptions[(__bridge NSString*)appID] = appExceptions;
157 NSError *nserror = nil;
158 if (![allExceptions writeToURL:ctExceptionsFile error:&nserror] && error) {
159 *error = CFRetainSafe((__bridge CFErrorRef)nserror);
162 atomic_store(&gHasCTExceptions, [allExceptions count] != 0);
163 notify_post(kSecCTExceptionsChanged);
168 CFDictionaryRef _SecTrustStoreCopyCTExceptions(CFStringRef appID, CFErrorRef *error) {
170 static int notify_token = 0;
172 if (!SecOTAPKIIsSystemTrustd()) {
173 /* Check whether we got a notification. If we didn't, and there are no exceptions set, return NULL.
174 * Otherwise, we need to read from disk */
175 uint32_t check_status = notify_check(notify_token, &check);
176 if (check_status == NOTIFY_STATUS_OK && check == 0 && !atomic_load(&gHasCTExceptions)) {
180 if (!atomic_load(&gHasCTExceptions)) {
186 NSURL *keychainsDirectory = CFBridgingRelease(SecCopyURLForFileInKeychainDirectory(nil));
188 NSURL *keychainsDirectory = [NSURL fileURLWithFileSystemRepresentation:"/Library/Keychains/" isDirectory:YES relativeToURL:nil];
190 NSURL *ctExceptionsFile = [keychainsDirectory URLByAppendingPathComponent:@"CTExceptions.plist"];
191 NSDictionary <NSString*,NSDictionary*> *allExceptions = [NSDictionary dictionaryWithContentsOfURL:ctExceptionsFile];
193 /* Set us up for not reading the disk when there are never exceptions */
194 static dispatch_once_t onceToken;
195 dispatch_once(&onceToken, ^{
196 atomic_init(&gHasCTExceptions, allExceptions && [allExceptions count] == 0);
197 if (!SecOTAPKIIsSystemTrustd()) {
198 uint32_t status = notify_register_check(kSecCTExceptionsChanged, ¬ify_token);
199 if (status == NOTIFY_STATUS_OK) {
200 status = notify_check(notify_token, NULL);
202 if (status != NOTIFY_STATUS_OK) {
203 secerror("failed to establish notification for CT exceptions: %ud", status);
204 notify_cancel(notify_token);
210 if (!allExceptions || [allExceptions count] == 0) {
211 atomic_store(&gHasCTExceptions, false);
215 /* If the caller specified an appID, return only the exceptions for that appID */
217 return CFBridgingRetain(allExceptions[(__bridge NSString*)appID]);
220 /* Otherwise, combine all the exceptions into one array */
221 NSMutableArray *domainExceptions = [NSMutableArray array];
222 NSMutableArray *caExceptions = [NSMutableArray array];
223 [allExceptions enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull __unused key, NSDictionary * _Nonnull appExceptions,
224 BOOL * _Nonnull __unused stop) {
225 if (appExceptions[(__bridge NSString*)kSecCTExceptionsDomainsKey] &&
226 checkExceptionsValues((__bridge NSString*)kSecCTExceptionsDomainsKey, appExceptions[(__bridge NSString*)kSecCTExceptionsDomainsKey],
227 checkDomainsValuesCompliance, error)) {
228 [domainExceptions addObjectsFromArray:appExceptions[(__bridge NSString*)kSecCTExceptionsDomainsKey]];
230 if (appExceptions[(__bridge NSString*)kSecCTExceptionsCAsKey] &&
231 checkExceptionsValues((__bridge NSString*)kSecCTExceptionsCAsKey, appExceptions[(__bridge NSString*)kSecCTExceptionsCAsKey],
232 checkCAsValuesCompliance, error)) {
233 [caExceptions addObjectsFromArray:appExceptions[(__bridge NSString*)kSecCTExceptionsCAsKey]];
236 NSMutableDictionary *exceptions = [NSMutableDictionary dictionaryWithCapacity:2];
237 if ([domainExceptions count] > 0) {
238 exceptions[(__bridge NSString*)kSecCTExceptionsDomainsKey] = domainExceptions;
240 if ([caExceptions count] > 0) {
241 exceptions[(__bridge NSString*)kSecCTExceptionsCAsKey] = caExceptions;
243 if ([exceptions count] > 0) {
244 atomic_store(&gHasCTExceptions, true);
245 return CFBridgingRetain(exceptions);