]> git.saurik.com Git - apple/security.git/blob - OSX/sec/securityd/SecCAIssuerRequest.m
Security-59306.11.20.tar.gz
[apple/security.git] / OSX / sec / securityd / SecCAIssuerRequest.m
1 /*
2 * Copyright (c) 2009-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 /*
25 * SecCAIssuerRequest.m - asynchronous CAIssuer request fetching engine.
26 */
27
28
29 #include "SecCAIssuerRequest.h"
30 #include "SecCAIssuerCache.h"
31
32 #import <Foundation/Foundation.h>
33 #import <CFNetwork/CFNSURLConnection.h>
34 #include <Security/SecInternal.h>
35 #include <Security/SecCMS.h>
36 #include <CoreFoundation/CFURL.h>
37 #include <CFNetwork/CFHTTPMessage.h>
38 #include <utilities/debugging.h>
39 #include <Security/SecCertificateInternal.h>
40 #include <securityd/SecTrustServer.h>
41 #include <securityd/TrustURLSessionDelegate.h>
42 #include <stdlib.h>
43 #include <mach/mach_time.h>
44
45 #define MAX_CA_ISSUERS 3
46 #define CA_ISSUERS_REQUEST_THRESHOLD 10
47
48 typedef void (*CompletionHandler)(void *context, CFArrayRef parents);
49
50 /* CA Issuer lookup code. */
51 @interface CAIssuerDelegate: TrustURLSessionDelegate
52 @property (assign) CompletionHandler callback;
53 @end
54
55 static NSArray *certificatesFromData(NSData *data) {
56 /* RFC5280 4.2.2.1:
57 "accessLocation MUST be a uniformResourceIdentifier and the URI
58 MUST point to either a single DER encoded certificate as speci-
59 fied in [RFC2585] or a collection of certificates in a BER or
60 DER encoded "certs-only" CMS message as specified in [RFC2797]." */
61
62 /* DER-encoded certificate */
63 SecCertificateRef parent = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)data);
64 if (parent) {
65 NSArray *result = @[(__bridge id)parent];
66 CFReleaseNull(parent);
67 return result;
68 }
69
70 /* "certs-only" CMS Message */
71 CFArrayRef certificates = SecCMSCertificatesOnlyMessageCopyCertificates((__bridge CFDataRef)data);
72 if (certificates) {
73 return CFBridgingRelease(certificates);
74 }
75
76 /* Retry in case the certificate is in PEM format. Some CAs
77 incorrectly return a PEM encoded cert, despite RFC 5280 4.2.2.1 */
78 parent = SecCertificateCreateWithPEM(NULL, (__bridge CFDataRef)data);
79 if (parent) {
80 NSArray *result = @[(__bridge id)parent];
81 CFReleaseNull(parent);
82 return result;
83 }
84 return nil;
85 }
86
87 @implementation CAIssuerDelegate
88 - (BOOL)fetchNext:(NSURLSession *)session {
89 SecPathBuilderRef builder = (SecPathBuilderRef)self.context;
90 TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(builder);
91
92 BOOL result = false;
93 if (!(result = [super fetchNext:session]) && analytics) {
94 analytics->ca_issuer_fetches++;
95 }
96 return result;
97 }
98
99 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
100 /* call the superclass's method to set taskTime and expiration */
101 [super URLSession:session task:task didCompleteWithError:error];
102
103 __block SecPathBuilderRef builder =(SecPathBuilderRef)self.context;
104 if (!builder) {
105 /* We already returned to the PathBuilder state machine. */
106 return;
107 }
108
109 TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(builder);
110 __block NSArray *parents = nil;
111 if (error) {
112 /* Log the error */
113 secnotice("caissuer", "Failed to download issuer %@, with error %@", task.originalRequest.URL, error);
114 if (analytics) {
115 analytics->ca_issuer_fetch_failed++;
116 }
117 } else if (self.response) {
118 /* Get the parent cert from the data */
119 parents = certificatesFromData(self.response);
120 if (analytics && !parents) {
121 analytics->ca_issuer_unsupported_data = true;
122 } else if (analytics && [parents count] > 1) {
123 analytics->ca_issuer_multiple_certs = true;
124 }
125 }
126
127 if (parents) {
128 /* Found some parents, add to cache, close session, and return to SecPathBuilder */
129 secdebug("caissuer", "found parents for %@", task.originalRequest.URL);
130 SecCAIssuerCacheAddCertificates((__bridge CFArrayRef)parents, (__bridge CFURLRef)task.originalRequest.URL, self.expiration);
131 self.context = nil; // set the context to NULL before we call back because the callback may free the builder
132 [session invalidateAndCancel];
133 dispatch_async(SecPathBuilderGetQueue(builder), ^{
134 self.callback(builder, (__bridge CFArrayRef)parents);
135 });
136 } else {
137 secdebug("caissuer", "no parents for %@", task.originalRequest.URL);
138 if ([self fetchNext:session]) { // Try the next CAIssuer URI
139 /* no fetch scheduled, close this session and jump back into the state machine on the builder's queue */
140 secdebug("caissuer", "no more fetches. returning to builder");
141 self.context = nil; // set the context to NULL before we call back because the callback may free the builder
142 [session invalidateAndCancel];
143 dispatch_async(SecPathBuilderGetQueue(builder), ^{
144 self.callback(builder, NULL);
145 });
146 }
147 }
148 }
149
150 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)taskMetrics {
151 secdebug("caissuer", "got metrics with task interval %f", taskMetrics.taskInterval.duration);
152 SecPathBuilderRef builder =(SecPathBuilderRef)self.context;
153 if (builder) {
154 TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(builder);
155 if (analytics) {
156 analytics->ca_issuer_fetch_time += (uint64_t)(taskMetrics.taskInterval.duration * NSEC_PER_SEC);
157 }
158 }
159 }
160 @end
161
162 /* Releases parent unconditionally, and return a CFArrayRef containing
163 parent if the normalized subject of parent matches the normalized issuer
164 of certificate. */
165 static CF_RETURNS_RETAINED CFArrayRef SecCAIssuerConvertToParents(SecCertificateRef certificate, CFArrayRef CF_CONSUMED putativeParents) {
166 CFDataRef nic = SecCertificateGetNormalizedIssuerContent(certificate);
167 NSMutableArray *parents = [NSMutableArray array];
168 NSArray *possibleParents = CFBridgingRelease(putativeParents);
169 for (id parent in possibleParents) {
170 CFDataRef parent_nic = SecCertificateGetNormalizedSubjectContent((__bridge SecCertificateRef)parent);
171 if (nic && parent_nic && CFEqual(nic, parent_nic)) {
172 [parents addObject:parent];
173 }
174 }
175
176 if ([parents count] > 0) {
177 return CFBridgingRetain(parents);
178 } else {
179 return nil;
180 }
181 }
182
183 static CFArrayRef SecCAIssuerRequestCacheCopyParents(SecCertificateRef cert,
184 CFArrayRef issuers) {
185 CFIndex ix = 0, ex = CFArrayGetCount(issuers);
186 for (;ix < ex; ++ix) {
187 CFURLRef issuer = CFArrayGetValueAtIndex(issuers, ix);
188 CFStringRef scheme = CFURLCopyScheme(issuer);
189 if (scheme) {
190 if (CFEqual(CFSTR("http"), scheme)) {
191 CFArrayRef parents = SecCAIssuerConvertToParents(cert,
192 SecCAIssuerCacheCopyMatching(issuer));
193 if (parents) {
194 secdebug("caissuer", "cache hit, for %@. no request issued", issuer);
195 CFRelease(scheme);
196 return parents;
197 }
198 }
199 CFRelease(scheme);
200 }
201 }
202 return NULL;
203 }
204
205 bool SecCAIssuerCopyParents(SecCertificateRef certificate, void *context, void (*callback)(void *, CFArrayRef)) {
206 @autoreleasepool {
207 CFArrayRef issuers = CFRetainSafe(SecCertificateGetCAIssuers(certificate));
208 NSArray *nsIssuers = CFBridgingRelease(issuers);
209 if (!issuers) {
210 /* certificate has no caissuer urls, we're done. */
211 callback(context, NULL);
212 return true;
213 }
214
215 SecPathBuilderRef builder = (SecPathBuilderRef)context;
216 TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(builder);
217 CFArrayRef parents = SecCAIssuerRequestCacheCopyParents(certificate, (__bridge CFArrayRef)nsIssuers);
218 if (parents) {
219 if (analytics) {
220 /* We found parents in the cache */
221 analytics->ca_issuer_cache_hit = true;
222 }
223 callback(context, parents);
224 CFReleaseSafe(parents);
225 return true;
226 }
227 if (analytics) {
228 /* We're going to have to make a network call */
229 analytics->ca_issuer_network = true;
230 }
231
232 NSInteger count = [nsIssuers count];
233 if (count >= CA_ISSUERS_REQUEST_THRESHOLD) {
234 secnotice("caissuer", "too many caIssuer entries (%ld)", (long)count);
235 callback(context, NULL);
236 return true;
237 }
238
239 NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
240 config.timeoutIntervalForResource = TrustURLSessionGetResourceTimeout();
241 config.HTTPAdditionalHeaders = @{@"User-Agent" : @"com.apple.trustd/2.0"};
242
243 NSData *auditToken = CFBridgingRelease(SecPathBuilderCopyClientAuditToken(builder));
244 if (auditToken) {
245 config._sourceApplicationAuditTokenData = auditToken;
246 }
247
248 CAIssuerDelegate *delegate = [[CAIssuerDelegate alloc] init];
249 delegate.context = context;
250 delegate.callback = callback;
251 delegate.URIs = nsIssuers;
252 delegate.URIix = 0;
253
254 NSOperationQueue *queue = [[NSOperationQueue alloc] init];
255
256 NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:delegate delegateQueue:queue];
257 secdebug("caissuer", "created URLSession for %@", certificate);
258
259 bool result = false;
260 if ((result = [delegate fetchNext:session])) {
261 /* no fetch scheduled, close the session */
262 [session invalidateAndCancel];
263 }
264 return result;
265 }
266 }
267