2 * Copyright (c) 2009-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 * SecCAIssuerRequest.m - asynchronous CAIssuer request fetching engine.
29 #include "SecCAIssuerRequest.h"
30 #include "SecCAIssuerCache.h"
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>
43 #include <mach/mach_time.h>
45 #define MAX_CA_ISSUERS 3
46 #define CA_ISSUERS_REQUEST_THRESHOLD 10
48 typedef void (*CompletionHandler)(void *context, CFArrayRef parents);
50 /* CA Issuer lookup code. */
51 @interface CAIssuerDelegate: TrustURLSessionDelegate
52 @property (assign) CompletionHandler callback;
55 static NSArray *certificatesFromData(NSData *data) {
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]." */
62 /* DER-encoded certificate */
63 SecCertificateRef parent = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)data);
65 NSArray *result = @[(__bridge id)parent];
66 CFReleaseNull(parent);
70 /* "certs-only" CMS Message */
71 CFArrayRef certificates = SecCMSCertificatesOnlyMessageCopyCertificates((__bridge CFDataRef)data);
73 return CFBridgingRelease(certificates);
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);
80 NSArray *result = @[(__bridge id)parent];
81 CFReleaseNull(parent);
87 @implementation CAIssuerDelegate
88 - (BOOL)fetchNext:(NSURLSession *)session {
89 SecPathBuilderRef builder = (SecPathBuilderRef)self.context;
90 TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(builder);
93 if (!(result = [super fetchNext:session]) && analytics) {
94 analytics->ca_issuer_fetches++;
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];
103 __block SecPathBuilderRef builder =(SecPathBuilderRef)self.context;
105 /* We already returned to the PathBuilder state machine. */
109 TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(builder);
110 __block NSArray *parents = nil;
113 secnotice("caissuer", "Failed to download issuer %@, with error %@", task.originalRequest.URL, error);
115 analytics->ca_issuer_fetch_failed++;
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;
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);
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);
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;
154 TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(builder);
156 analytics->ca_issuer_fetch_time += (uint64_t)(taskMetrics.taskInterval.duration * NSEC_PER_SEC);
162 /* Releases parent unconditionally, and return a CFArrayRef containing
163 parent if the normalized subject of parent matches the normalized issuer
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];
176 if ([parents count] > 0) {
177 return CFBridgingRetain(parents);
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);
190 if (CFEqual(CFSTR("http"), scheme)) {
191 CFArrayRef parents = SecCAIssuerConvertToParents(cert,
192 SecCAIssuerCacheCopyMatching(issuer));
194 secdebug("caissuer", "cache hit, for %@. no request issued", issuer);
205 bool SecCAIssuerCopyParents(SecCertificateRef certificate, void *context, void (*callback)(void *, CFArrayRef)) {
207 CFArrayRef issuers = CFRetainSafe(SecCertificateGetCAIssuers(certificate));
208 NSArray *nsIssuers = CFBridgingRelease(issuers);
210 /* certificate has no caissuer urls, we're done. */
211 callback(context, NULL);
215 SecPathBuilderRef builder = (SecPathBuilderRef)context;
216 TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(builder);
217 CFArrayRef parents = SecCAIssuerRequestCacheCopyParents(certificate, (__bridge CFArrayRef)nsIssuers);
220 /* We found parents in the cache */
221 analytics->ca_issuer_cache_hit = true;
223 callback(context, parents);
224 CFReleaseSafe(parents);
228 /* We're going to have to make a network call */
229 analytics->ca_issuer_network = true;
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);
239 NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
240 config.timeoutIntervalForResource = TrustURLSessionGetResourceTimeout();
241 config.HTTPAdditionalHeaders = @{@"User-Agent" : @"com.apple.trustd/2.0"};
243 NSData *auditToken = CFBridgingRelease(SecPathBuilderCopyClientAuditToken(builder));
245 config._sourceApplicationAuditTokenData = auditToken;
248 CAIssuerDelegate *delegate = [[CAIssuerDelegate alloc] init];
249 delegate.context = context;
250 delegate.callback = callback;
251 delegate.URIs = nsIssuers;
254 NSOperationQueue *queue = [[NSOperationQueue alloc] init];
256 NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:delegate delegateQueue:queue];
257 secdebug("caissuer", "created URLSession for %@", certificate);
260 if ((result = [delegate fetchNext:session])) {
261 /* no fetch scheduled, close the session */
262 [session invalidateAndCancel];