]> git.saurik.com Git - apple/security.git/blob - trust/trustd/SecTrustStoreServer.m
Security-59754.80.3.tar.gz
[apple/security.git] / trust / trustd / SecTrustStoreServer.m
1 /*
2 * Copyright (c) 2018-2020 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 //
37 // MARK : CT Exceptions
38 //
39 typedef bool(*exceptionsArrayValueChecker)(id _Nonnull obj);
40
41 static bool checkDomainsValuesCompliance(id _Nonnull obj) {
42 if (![obj isKindOfClass:[NSString class]]) {
43 return false;
44 }
45 if (SecDNSIsTLD((__bridge CFStringRef)obj)) {
46 return false;
47 }
48 return true;
49 }
50
51 static bool checkCAsValuesCompliance(id _Nonnull obj) {
52 if (![obj isKindOfClass:[NSDictionary class]]) {
53 return false;
54 }
55 if (2 != [(NSDictionary*)obj count]) {
56 return false;
57 }
58 if (nil == ((NSDictionary*)obj)[(__bridge NSString*)kSecCTExceptionsHashAlgorithmKey] ||
59 nil == ((NSDictionary*)obj)[(__bridge NSString*)kSecCTExceptionsSPKIHashKey]) {
60 return false;
61 }
62 if (![((NSDictionary*)obj)[(__bridge NSString*)kSecCTExceptionsHashAlgorithmKey] isKindOfClass:[NSString class]] ||
63 ![((NSDictionary*)obj)[(__bridge NSString*)kSecCTExceptionsSPKIHashKey] isKindOfClass:[NSData class]]) {
64 return false;
65 }
66 if (![((NSDictionary*)obj)[(__bridge NSString*)kSecCTExceptionsHashAlgorithmKey] isEqualToString:@"sha256"]) {
67 return false;
68 }
69 return true;
70 }
71
72 static bool checkExceptionsValues(NSString *key, id value, exceptionsArrayValueChecker checker, CFErrorRef *error) {
73 if (![value isKindOfClass:[NSArray class]]) {
74 return SecError(errSecParam, error, CFSTR("value for %@ is not an array in exceptions dictionary"), key);
75 }
76
77 __block bool result = true;
78 [(NSArray*)value enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
79 if (!checker(obj)) {
80 result = SecError(errSecParam, error, CFSTR("value %lu for %@ is not the expected type"), (unsigned long)idx, key);
81 *stop = true;
82 }
83 }];
84 return result;
85 }
86
87 static bool checkInputExceptionsAndSetAppExceptions(NSDictionary *inExceptions, NSMutableDictionary *appExceptions, CFErrorRef *error) {
88 __block bool result = true;
89 [inExceptions enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
90 if ([key isEqualToString:(__bridge NSString*)kSecCTExceptionsDomainsKey]) {
91 if (!checkExceptionsValues(key, obj, checkDomainsValuesCompliance, error)) {
92 *stop = YES;
93 result = false;
94 return;
95 }
96 } else if ([key isEqualToString:(__bridge NSString*)kSecCTExceptionsCAsKey]) {
97 if (!checkExceptionsValues(key, obj, checkCAsValuesCompliance, error)) {
98 *stop = YES;
99 result = false;
100 return;
101 }
102 } else {
103 result = SecError(errSecParam, error, CFSTR("unknown key (%@) in exceptions dictionary"), key);
104 *stop = YES;
105 result = false;
106 return;
107 }
108 if ([(NSArray*)obj count] == 0) {
109 [appExceptions removeObjectForKey:key];
110 } else {
111 appExceptions[key] = obj;
112 }
113 }];
114 return result;
115 }
116
117 static _Atomic bool gHasCTExceptions = false;
118 #define kSecCTExceptionsChanged "com.apple.trustd.ct.exceptions-changed"
119
120 static NSURL *CTExceptionsFileURL() {
121 return CFBridgingRelease(SecCopyURLForFileInSystemKeychainDirectory(CFSTR("CTExceptions.plist")));
122 }
123
124 static NSDictionary <NSString*,NSDictionary*> *readExceptionsFromDisk(NSError **error) {
125 secdebug("ct", "reading CT exceptions from disk");
126 NSDictionary <NSString*,NSDictionary*> *allExceptions = [NSDictionary dictionaryWithContentsOfURL:CTExceptionsFileURL()
127 error:error];
128 return allExceptions;
129 }
130
131 bool _SecTrustStoreSetCTExceptions(CFStringRef appID, CFDictionaryRef exceptions, CFErrorRef *error) {
132 if (!SecOTAPKIIsSystemTrustd()) {
133 secerror("Unable to write CT exceptions from user agent");
134 return SecError(errSecWrPerm, error, CFSTR("Unable to write CT exceptions from user agent"));
135 }
136
137 if (!appID) {
138 secerror("application-identifier required to set exceptions");
139 return SecError(errSecParam, error, CFSTR("application-identifier required to set exceptions"));
140 }
141
142 @autoreleasepool {
143 NSError *nserror = nil;
144 NSMutableDictionary *allExceptions = [readExceptionsFromDisk(&nserror) mutableCopy];
145 NSMutableDictionary *appExceptions = NULL;
146 if (allExceptions && allExceptions[(__bridge NSString*)appID]) {
147 appExceptions = [allExceptions[(__bridge NSString*)appID] mutableCopy];
148 } else {
149 appExceptions = [NSMutableDictionary dictionary];
150 if (!allExceptions) {
151 allExceptions = [NSMutableDictionary dictionary];
152 }
153 }
154
155 if (exceptions && (CFDictionaryGetCount(exceptions) > 0)) {
156 NSDictionary *inExceptions = (__bridge NSDictionary*)exceptions;
157 if (!checkInputExceptionsAndSetAppExceptions(inExceptions, appExceptions, error)) {
158 secerror("input exceptions have error: %@", error ? *error : nil);
159 return false;
160 }
161 }
162
163 if (!exceptions || [appExceptions count] == 0) {
164 [allExceptions removeObjectForKey:(__bridge NSString*)appID];
165 } else {
166 allExceptions[(__bridge NSString*)appID] = appExceptions;
167 }
168
169 if (![allExceptions writeToURL:CTExceptionsFileURL() error:&nserror]) {
170 secerror("failed to write CT exceptions: %@", nserror);
171 if (error) {
172 *error = CFRetainSafe((__bridge CFErrorRef)nserror);
173 }
174 return false;
175 }
176 secnotice("ct", "wrote %lu CT exceptions", (unsigned long)[allExceptions count]);
177 atomic_store(&gHasCTExceptions, [allExceptions count] != 0);
178 notify_post(kSecCTExceptionsChanged);
179 return true;
180 }
181 }
182
183 CFDictionaryRef _SecTrustStoreCopyCTExceptions(CFStringRef appID, CFErrorRef *error) {
184 @autoreleasepool {
185 /* Set us up for not reading the disk when there are never exceptions */
186 static int notify_token = 0;
187 int check = 0;
188 static dispatch_once_t onceToken;
189 dispatch_once(&onceToken, ^{
190 /* initialize gHasCTExceptions cache */
191 NSError *read_error = nil;
192 NSDictionary <NSString*,NSDictionary*> *allExceptions = readExceptionsFromDisk(&read_error);
193 if (!allExceptions || [allExceptions count] == 0) {
194 secnotice("ct", "skipping further reads. no CT exceptions found: %@", read_error);
195 atomic_store(&gHasCTExceptions, false);
196 } else {
197 secnotice("ct", "have CT exceptions. will need to read.");
198 atomic_store(&gHasCTExceptions, true);
199 }
200
201 /* read-only trustds register for notfications from the read-write trustd */
202 if (!SecOTAPKIIsSystemTrustd()) {
203 uint32_t status = notify_register_check(kSecCTExceptionsChanged, &notify_token);
204 if (status == NOTIFY_STATUS_OK) {
205 status = notify_check(notify_token, NULL);
206 }
207 if (status != NOTIFY_STATUS_OK) {
208 secerror("failed to establish notification for CT exceptions: %u", status);
209 notify_cancel(notify_token);
210 notify_token = 0;
211 }
212 }
213 });
214
215 /* Read the negative cached value as to whether there are any exceptions to read */
216 if (!SecOTAPKIIsSystemTrustd()) {
217 /* Check whether we got a notification. If we didn't, and there are no exceptions set, return NULL.
218 * Otherwise, we need to read from disk */
219 uint32_t check_status = notify_check(notify_token, &check);
220 if (check_status == NOTIFY_STATUS_OK && check == 0 && !atomic_load(&gHasCTExceptions)) {
221 return NULL;
222 }
223 } else if (!atomic_load(&gHasCTExceptions)) {
224 return NULL;
225 }
226
227 /* We need to read the exceptions from disk */
228 NSError *read_error = nil;
229 NSDictionary <NSString*,NSDictionary*> *allExceptions = readExceptionsFromDisk(&read_error);
230 if (!allExceptions || [allExceptions count] == 0) {
231 secnotice("ct", "skipping further reads. no CT exceptions found: %@", read_error);
232 atomic_store(&gHasCTExceptions, false);
233 return NULL;
234 }
235
236 /* If the caller specified an appID, return only the exceptions for that appID */
237 if (appID) {
238 return CFBridgingRetain(allExceptions[(__bridge NSString*)appID]);
239 }
240
241 /* Otherwise, combine all the exceptions into one array */
242 NSMutableArray *domainExceptions = [NSMutableArray array];
243 NSMutableArray *caExceptions = [NSMutableArray array];
244 [allExceptions enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull __unused key, NSDictionary * _Nonnull appExceptions,
245 BOOL * _Nonnull __unused stop) {
246 if (appExceptions[(__bridge NSString*)kSecCTExceptionsDomainsKey] &&
247 checkExceptionsValues((__bridge NSString*)kSecCTExceptionsDomainsKey, appExceptions[(__bridge NSString*)kSecCTExceptionsDomainsKey],
248 checkDomainsValuesCompliance, error)) {
249 [domainExceptions addObjectsFromArray:appExceptions[(__bridge NSString*)kSecCTExceptionsDomainsKey]];
250 }
251 if (appExceptions[(__bridge NSString*)kSecCTExceptionsCAsKey] &&
252 checkExceptionsValues((__bridge NSString*)kSecCTExceptionsCAsKey, appExceptions[(__bridge NSString*)kSecCTExceptionsCAsKey],
253 checkCAsValuesCompliance, error)) {
254 [caExceptions addObjectsFromArray:appExceptions[(__bridge NSString*)kSecCTExceptionsCAsKey]];
255 }
256 }];
257 NSMutableDictionary *exceptions = [NSMutableDictionary dictionaryWithCapacity:2];
258 if ([domainExceptions count] > 0) {
259 exceptions[(__bridge NSString*)kSecCTExceptionsDomainsKey] = domainExceptions;
260 }
261 if ([caExceptions count] > 0) {
262 exceptions[(__bridge NSString*)kSecCTExceptionsCAsKey] = caExceptions;
263 }
264 if ([exceptions count] > 0) {
265 secdebug("ct", "found %lu CT exceptions on disk", (unsigned long)[exceptions count]);
266 atomic_store(&gHasCTExceptions, true);
267 return CFBridgingRetain(exceptions);
268 }
269 return NULL;
270 }
271 }
272
273 //
274 // MARK: CA Revocation Additions
275 //
276 typedef bool(*additionsArrayValueChecker)(id _Nonnull obj);
277
278 static bool checkCARevocationValuesCompliance(id _Nonnull obj) {
279 if (![obj isKindOfClass:[NSDictionary class]]) {
280 return false;
281 }
282 if (2 != [(NSDictionary*)obj count]) {
283 return false;
284 }
285 if (nil == ((NSDictionary*)obj)[(__bridge NSString*)kSecCARevocationHashAlgorithmKey] ||
286 nil == ((NSDictionary*)obj)[(__bridge NSString*)kSecCARevocationSPKIHashKey]) {
287 return false;
288 }
289 if (![((NSDictionary*)obj)[(__bridge NSString*)kSecCARevocationHashAlgorithmKey] isKindOfClass:[NSString class]] ||
290 ![((NSDictionary*)obj)[(__bridge NSString*)kSecCARevocationSPKIHashKey] isKindOfClass:[NSData class]]) {
291 return false;
292 }
293 if (![((NSDictionary*)obj)[(__bridge NSString*)kSecCARevocationHashAlgorithmKey] isEqualToString:@"sha256"]) {
294 return false;
295 }
296 return true;
297 }
298
299 static bool checkCARevocationValues(NSString *key, id value, additionsArrayValueChecker checker, CFErrorRef *error) {
300 if (![value isKindOfClass:[NSArray class]]) {
301 return SecError(errSecParam, error, CFSTR("value for %@ is not an array in revocation additions dictionary"), key);
302 }
303
304 __block bool result = true;
305 [(NSArray*)value enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
306 if (!checker(obj)) {
307 result = SecError(errSecParam, error, CFSTR("value %lu for %@ is not the expected type"), (unsigned long)idx, key);
308 *stop = true;
309 }
310 }];
311 return result;
312 }
313
314 static bool checkInputAdditionsAndSetAppAdditions(NSDictionary *inAdditions, NSMutableDictionary *appAdditions, CFErrorRef *error) {
315 __block bool result = true;
316 [inAdditions enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
317 if ([key isEqualToString:(__bridge NSString*)kSecCARevocationAdditionsKey]) {
318 if (!checkCARevocationValues(key, obj, checkCARevocationValuesCompliance, error)) {
319 *stop = YES;
320 result = false;
321 return;
322 }
323 } else {
324 result = SecError(errSecParam, error, CFSTR("unknown key (%@) in additions dictionary"), key);
325 *stop = YES;
326 result = false;
327 return;
328 }
329 if ([(NSArray*)obj count] == 0) {
330 [appAdditions removeObjectForKey:key];
331 } else {
332 appAdditions[key] = obj;
333 }
334 }];
335 return result;
336 }
337
338 static _Atomic bool gHasCARevocationAdditions = false;
339 #define kSecCARevocationChanged "com.apple.trustd.ca.revocation-changed"
340
341 static NSURL *CARevocationFileURL() {
342 return CFBridgingRelease(SecCopyURLForFileInSystemKeychainDirectory(CFSTR("CARevocation.plist")));
343 }
344
345 static NSDictionary <NSString*,NSDictionary*> *readRevocationAdditionsFromDisk(NSError **error) {
346 secdebug("ocsp", "reading CA revocation additions from disk");
347 NSDictionary <NSString*,NSDictionary*> *allAdditions = [NSDictionary dictionaryWithContentsOfURL:CARevocationFileURL()
348 error:error];
349 return allAdditions;
350 }
351
352 bool _SecTrustStoreSetCARevocationAdditions(CFStringRef appID, CFDictionaryRef additions, CFErrorRef *error) {
353 if (!SecOTAPKIIsSystemTrustd()) {
354 secerror("Unable to write CA revocation additions from user agent");
355 return SecError(errSecWrPerm, error, CFSTR("Unable to write CA revocation additions from user agent"));
356 }
357
358 if (!appID) {
359 secerror("application-identifier required to set CA revocation additions");
360 return SecError(errSecParam, error, CFSTR("application-identifier required to set CA revocation additions"));
361 }
362
363 @autoreleasepool {
364 NSError *nserror = nil;
365 NSMutableDictionary *allAdditions = [readRevocationAdditionsFromDisk(&nserror) mutableCopy];
366 NSMutableDictionary *appAdditions = NULL;
367 if (allAdditions && allAdditions[(__bridge NSString*)appID]) {
368 appAdditions = [allAdditions[(__bridge NSString*)appID] mutableCopy];
369 } else {
370 appAdditions = [NSMutableDictionary dictionary];
371 if (!allAdditions) {
372 allAdditions = [NSMutableDictionary dictionary];
373 }
374 }
375
376 if (additions && (CFDictionaryGetCount(additions) > 0)) {
377 NSDictionary *inAdditions = (__bridge NSDictionary*)additions;
378 if (!checkInputAdditionsAndSetAppAdditions(inAdditions, appAdditions, error)) {
379 secerror("input additions have error: %@", error ? *error : nil);
380 return false;
381 }
382 }
383
384 if (!additions || [appAdditions count] == 0) {
385 [allAdditions removeObjectForKey:(__bridge NSString*)appID];
386 } else {
387 allAdditions[(__bridge NSString*)appID] = appAdditions;
388 }
389
390 if (![allAdditions writeToURL:CARevocationFileURL() error:&nserror]) {
391 secerror("failed to write CA revocation additions: %@", nserror);
392 if (error) {
393 *error = CFRetainSafe((__bridge CFErrorRef)nserror);
394 }
395 return false;
396 }
397 secnotice("ocsp", "wrote %lu CA revocation additions", (unsigned long)[allAdditions count]);
398 atomic_store(&gHasCARevocationAdditions, [allAdditions count] != 0);
399 notify_post(kSecCARevocationChanged);
400 return true;
401 }
402 }
403
404 CFDictionaryRef _SecTrustStoreCopyCARevocationAdditions(CFStringRef appID, CFErrorRef *error) {
405 @autoreleasepool {
406 /* Set us up for not reading the disk when there are never exceptions */
407 static int notify_token = 0;
408 int check = 0;
409 static dispatch_once_t onceToken;
410 dispatch_once(&onceToken, ^{
411 /* initialize gHasCARevocation cache */
412 NSError *read_error = nil;
413 NSDictionary <NSString*,NSDictionary*> *allAdditions = readRevocationAdditionsFromDisk(&read_error);
414 if (!allAdditions || [allAdditions count] == 0) {
415 secnotice("ocsp", "skipping further reads. no CA revocation additions found: %@", read_error);
416 atomic_store(&gHasCARevocationAdditions, false);
417 } else {
418 secnotice("ocsp", "have CA revocation additions. will need to read.");
419 atomic_store(&gHasCARevocationAdditions, true);
420 }
421
422 /* read-only trustds register for notfications from the read-write trustd */
423 if (!SecOTAPKIIsSystemTrustd()) {
424 uint32_t status = notify_register_check(kSecCARevocationChanged, &notify_token);
425 if (status == NOTIFY_STATUS_OK) {
426 status = notify_check(notify_token, NULL);
427 }
428 if (status != NOTIFY_STATUS_OK) {
429 secerror("failed to establish notification for CA revocation additions: %u", status);
430 notify_cancel(notify_token);
431 notify_token = 0;
432 }
433 }
434 });
435
436 /* Read the negative cached value as to whether there are any revocation additions to read */
437 if (!SecOTAPKIIsSystemTrustd()) {
438 /* Check whether we got a notification. If we didn't, and there are no additions set, return NULL.
439 * Otherwise, we need to read from disk */
440 uint32_t check_status = notify_check(notify_token, &check);
441 if (check_status == NOTIFY_STATUS_OK && check == 0 && !atomic_load(&gHasCARevocationAdditions)) {
442 return NULL;
443 }
444 } else if (!atomic_load(&gHasCARevocationAdditions)) {
445 return NULL;
446 }
447
448 /* We need to read the exceptions from disk */
449 NSError *read_error = nil;
450 NSDictionary <NSString*,NSDictionary*> *allAdditions = readRevocationAdditionsFromDisk(&read_error);
451 if (!allAdditions || [allAdditions count] == 0) {
452 secnotice("ocsp", "skipping further reads. no CA revocation additions found: %@", read_error);
453 atomic_store(&gHasCARevocationAdditions, false);
454 return NULL;
455 }
456
457 /* If the caller specified an appID, return only the exceptions for that appID */
458 if (appID) {
459 return CFBridgingRetain(allAdditions[(__bridge NSString*)appID]);
460 }
461
462 /* Otherwise, combine all the revocation additions into one array */
463 NSMutableArray *caAdditions = [NSMutableArray array];
464 [allAdditions enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull __unused key, NSDictionary * _Nonnull appAdditions,
465 BOOL * _Nonnull __unused stop) {
466 if (appAdditions[(__bridge NSString*)kSecCARevocationAdditionsKey] &&
467 checkCARevocationValues((__bridge NSString*)kSecCARevocationAdditionsKey,
468 appAdditions[(__bridge NSString*)kSecCARevocationAdditionsKey],
469 checkCARevocationValuesCompliance, error)) {
470 [caAdditions addObjectsFromArray:appAdditions[(__bridge NSString*)kSecCARevocationAdditionsKey]];
471 }
472 }];
473 NSMutableDictionary *additions = [NSMutableDictionary dictionaryWithCapacity:2];
474 if ([caAdditions count] > 0) {
475 additions[(__bridge NSString*)kSecCARevocationAdditionsKey] = caAdditions;
476 }
477 if ([additions count] > 0) {
478 secdebug("ocsp", "found %lu CA revocation additions on disk", (unsigned long)[additions count]);
479 atomic_store(&gHasCARevocationAdditions, true);
480 return CFBridgingRetain(additions);
481 }
482 return NULL;
483 }
484 }