]> git.saurik.com Git - apple/security.git/blob - trust/trustd/SecTrustStoreServer.m
Security-59306.101.1.tar.gz
[apple/security.git] / trust / trustd / SecTrustStoreServer.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 #include <AssertMacros.h>
25 #import <Foundation/Foundation.h>
26 #include <stdatomic.h>
27 #include <notify.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"
35
36 typedef bool(*exceptionsArrayValueChecker)(id _Nonnull obj);
37
38 static bool checkDomainsValuesCompliance(id _Nonnull obj) {
39 if (![obj isKindOfClass:[NSString class]]) {
40 return false;
41 }
42 if (SecDNSIsTLD((__bridge CFStringRef)obj)) {
43 return false;
44 }
45 return true;
46 }
47
48 static bool checkCAsValuesCompliance(id _Nonnull obj) {
49 if (![obj isKindOfClass:[NSDictionary class]]) {
50 return false;
51 }
52 if (2 != [(NSDictionary*)obj count]) {
53 return false;
54 }
55 if (nil == ((NSDictionary*)obj)[(__bridge NSString*)kSecCTExceptionsHashAlgorithmKey] ||
56 nil == ((NSDictionary*)obj)[(__bridge NSString*)kSecCTExceptionsSPKIHashKey]) {
57 return false;
58 }
59 if (![((NSDictionary*)obj)[(__bridge NSString*)kSecCTExceptionsHashAlgorithmKey] isKindOfClass:[NSString class]] ||
60 ![((NSDictionary*)obj)[(__bridge NSString*)kSecCTExceptionsSPKIHashKey] isKindOfClass:[NSData class]]) {
61 return false;
62 }
63 if (![((NSDictionary*)obj)[(__bridge NSString*)kSecCTExceptionsHashAlgorithmKey] isEqualToString:@"sha256"]) {
64 return false;
65 }
66 return true;
67 }
68
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);
72 }
73
74 __block bool result = true;
75 [(NSArray*)value enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
76 if (!checker(obj)) {
77 result = SecError(errSecParam, error, CFSTR("value %lu for %@ is not the expected type"), (unsigned long)idx, key);
78 *stop = true;
79 }
80 }];
81 return result;
82 }
83
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)) {
89 *stop = YES;
90 result = false;
91 return;
92 }
93 } else if ([key isEqualToString:(__bridge NSString*)kSecCTExceptionsCAsKey]) {
94 if (!checkExceptionsValues(key, obj, checkCAsValuesCompliance, error)) {
95 *stop = YES;
96 result = false;
97 return;
98 }
99 } else {
100 result = SecError(errSecParam, error, CFSTR("unknown key (%@) in exceptions dictionary"), key);
101 *stop = YES;
102 result = false;
103 return;
104 }
105 if ([(NSArray*)obj count] == 0) {
106 [appExceptions removeObjectForKey:key];
107 } else {
108 appExceptions[key] = obj;
109 }
110 }];
111 return result;
112 }
113
114 static _Atomic bool gHasCTExceptions = false;
115 #define kSecCTExceptionsChanged "com.apple.trustd.ct.exceptions-changed"
116
117 static NSURL *CTExceptionsFileURL() {
118 return CFBridgingRelease(SecCopyURLForFileInSystemKeychainDirectory(CFSTR("CTExceptions.plist")));
119 }
120
121 static NSDictionary <NSString*,NSDictionary*> *readExceptionsFromDisk(NSError **error) {
122 secdebug("ct", "reading CT exceptions from disk");
123 NSDictionary <NSString*,NSDictionary*> *allExceptions = [NSDictionary dictionaryWithContentsOfURL:CTExceptionsFileURL()
124 error:error];
125 return allExceptions;
126 }
127
128 bool _SecTrustStoreSetCTExceptions(CFStringRef appID, CFDictionaryRef exceptions, CFErrorRef *error) {
129 if (!SecOTAPKIIsSystemTrustd()) {
130 secerror("Unable to write CT exceptions from user agent");
131 return SecError(errSecWrPerm, error, CFSTR("Unable to write CT exceptions from user agent"));
132 }
133
134 if (!appID) {
135 secerror("application-identifier required to set exceptions");
136 return SecError(errSecParam, error, CFSTR("application-identifier required to set exceptions"));
137 }
138
139 @autoreleasepool {
140 NSError *nserror = nil;
141 NSMutableDictionary *allExceptions = [readExceptionsFromDisk(&nserror) mutableCopy];
142 NSMutableDictionary *appExceptions = NULL;
143 if (allExceptions && allExceptions[(__bridge NSString*)appID]) {
144 appExceptions = [allExceptions[(__bridge NSString*)appID] mutableCopy];
145 } else {
146 appExceptions = [NSMutableDictionary dictionary];
147 if (!allExceptions) {
148 allExceptions = [NSMutableDictionary dictionary];
149 }
150 }
151
152 if (exceptions && (CFDictionaryGetCount(exceptions) > 0)) {
153 NSDictionary *inExceptions = (__bridge NSDictionary*)exceptions;
154 if (!checkInputExceptionsAndSetAppExceptions(inExceptions, appExceptions, error)) {
155 secerror("input exceptions have error: %@", error ? *error : nil);
156 return false;
157 }
158 }
159
160 if (!exceptions || [appExceptions count] == 0) {
161 [allExceptions removeObjectForKey:(__bridge NSString*)appID];
162 } else {
163 allExceptions[(__bridge NSString*)appID] = appExceptions;
164 }
165
166 if (![allExceptions writeToURL:CTExceptionsFileURL() error:&nserror]) {
167 secerror("failed to write CT exceptions: %@", nserror);
168 if (error) {
169 *error = CFRetainSafe((__bridge CFErrorRef)nserror);
170 }
171 return false;
172 }
173 secnotice("ct", "wrote %lu CT exceptions", (unsigned long)[allExceptions count]);
174 atomic_store(&gHasCTExceptions, [allExceptions count] != 0);
175 notify_post(kSecCTExceptionsChanged);
176 return true;
177 }
178 }
179
180 CFDictionaryRef _SecTrustStoreCopyCTExceptions(CFStringRef appID, CFErrorRef *error) {
181 @autoreleasepool {
182 /* Set us up for not reading the disk when there are never exceptions */
183 static int notify_token = 0;
184 int check = 0;
185 static dispatch_once_t onceToken;
186 dispatch_once(&onceToken, ^{
187 /* initialize gHashCTExceptions cache */
188 NSError *read_error = nil;
189 NSDictionary <NSString*,NSDictionary*> *allExceptions = readExceptionsFromDisk(&read_error);
190 if (!allExceptions || [allExceptions count] == 0) {
191 secnotice("ct", "skipping further reads. no CT exceptions found: %@", read_error);
192 atomic_store(&gHasCTExceptions, false);
193 } else {
194 secnotice("ct", "have CT exceptions. will need to read.");
195 atomic_store(&gHasCTExceptions, true);
196 }
197
198 /* read-only trustds register for notfications from the read-write trustd */
199 if (!SecOTAPKIIsSystemTrustd()) {
200 uint32_t status = notify_register_check(kSecCTExceptionsChanged, &notify_token);
201 if (status == NOTIFY_STATUS_OK) {
202 status = notify_check(notify_token, NULL);
203 }
204 if (status != NOTIFY_STATUS_OK) {
205 secerror("failed to establish notification for CT exceptions: %ud", status);
206 notify_cancel(notify_token);
207 notify_token = 0;
208 }
209 }
210 });
211
212 /* Read the negative cached value as to whether there are any exceptions to read */
213 if (!SecOTAPKIIsSystemTrustd()) {
214 /* Check whether we got a notification. If we didn't, and there are no exceptions set, return NULL.
215 * Otherwise, we need to read from disk */
216 uint32_t check_status = notify_check(notify_token, &check);
217 if (check_status == NOTIFY_STATUS_OK && check == 0 && !atomic_load(&gHasCTExceptions)) {
218 return NULL;
219 }
220 } else if (!atomic_load(&gHasCTExceptions)) {
221 return NULL;
222 }
223
224 /* We need to read the exceptions from disk */
225 NSError *read_error = nil;
226 NSDictionary <NSString*,NSDictionary*> *allExceptions = readExceptionsFromDisk(&read_error);
227 if (!allExceptions || [allExceptions count] == 0) {
228 secnotice("ct", "skipping further reads. no CT exceptions found: %@", read_error);
229 atomic_store(&gHasCTExceptions, false);
230 return NULL;
231 }
232
233 /* If the caller specified an appID, return only the exceptions for that appID */
234 if (appID) {
235 return CFBridgingRetain(allExceptions[(__bridge NSString*)appID]);
236 }
237
238 /* Otherwise, combine all the exceptions into one array */
239 NSMutableArray *domainExceptions = [NSMutableArray array];
240 NSMutableArray *caExceptions = [NSMutableArray array];
241 [allExceptions enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull __unused key, NSDictionary * _Nonnull appExceptions,
242 BOOL * _Nonnull __unused stop) {
243 if (appExceptions[(__bridge NSString*)kSecCTExceptionsDomainsKey] &&
244 checkExceptionsValues((__bridge NSString*)kSecCTExceptionsDomainsKey, appExceptions[(__bridge NSString*)kSecCTExceptionsDomainsKey],
245 checkDomainsValuesCompliance, error)) {
246 [domainExceptions addObjectsFromArray:appExceptions[(__bridge NSString*)kSecCTExceptionsDomainsKey]];
247 }
248 if (appExceptions[(__bridge NSString*)kSecCTExceptionsCAsKey] &&
249 checkExceptionsValues((__bridge NSString*)kSecCTExceptionsCAsKey, appExceptions[(__bridge NSString*)kSecCTExceptionsCAsKey],
250 checkCAsValuesCompliance, error)) {
251 [caExceptions addObjectsFromArray:appExceptions[(__bridge NSString*)kSecCTExceptionsCAsKey]];
252 }
253 }];
254 NSMutableDictionary *exceptions = [NSMutableDictionary dictionaryWithCapacity:2];
255 if ([domainExceptions count] > 0) {
256 exceptions[(__bridge NSString*)kSecCTExceptionsDomainsKey] = domainExceptions;
257 }
258 if ([caExceptions count] > 0) {
259 exceptions[(__bridge NSString*)kSecCTExceptionsCAsKey] = caExceptions;
260 }
261 if ([exceptions count] > 0) {
262 secdebug("ct", "found %lu CT exceptions on disk", (unsigned long)[exceptions count]);
263 atomic_store(&gHasCTExceptions, true);
264 return CFBridgingRetain(exceptions);
265 }
266 return NULL;
267 }
268 }