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.c - asynchronous CAIssuer request fetching engine.
29 #include "SecCAIssuerRequest.h"
30 #include "SecCAIssuerCache.h"
32 #include <Security/SecInternal.h>
33 #include <Security/SecCMS.h>
34 #include <CoreFoundation/CFURL.h>
35 #include <CFNetwork/CFHTTPMessage.h>
36 #include <utilities/debugging.h>
37 #include <Security/SecCertificateInternal.h>
38 #include <securityd/asynchttp.h>
39 #include <securityd/SecTrustServer.h>
41 #include <mach/mach_time.h>
43 #define MAX_CA_ISSUERS 3
44 #define CA_ISSUERS_REQUEST_THRESHOLD 10
47 /* CA Issuer lookup code. */
49 typedef struct SecCAIssuerRequest
*SecCAIssuerRequestRef
;
50 struct SecCAIssuerRequest
{
51 asynchttp_t http
; /* Must be first field. */
52 SecCertificateRef certificate
;
53 CFArrayRef issuers
; /* NONRETAINED */
56 void (*callback
)(void *, CFArrayRef
);
59 static void SecCAIssuerRequestRelease(SecCAIssuerRequestRef request
) {
60 CFRelease(request
->certificate
);
61 asynchttp_free(&request
->http
);
65 static bool SecCAIssuerRequestIssue(SecCAIssuerRequestRef request
) {
66 CFIndex count
= CFArrayGetCount(request
->issuers
);
67 if (count
>= CA_ISSUERS_REQUEST_THRESHOLD
) {
68 secnotice("caissuer", "too many caIssuer entries (%ld)", (long)count
);
69 request
->callback(request
->context
, NULL
);
70 SecCAIssuerRequestRelease(request
);
74 SecPathBuilderRef builder
= (SecPathBuilderRef
)request
->context
;
75 TrustAnalyticsBuilder
*analytics
= SecPathBuilderGetAnalyticsData(builder
);
77 while (request
->issuerIX
< count
&& request
->issuerIX
< MAX_CA_ISSUERS
) {
78 CFURLRef issuer
= CFArrayGetValueAtIndex(request
->issuers
,
80 CFStringRef scheme
= CFURLCopyScheme(issuer
);
82 if (CFEqual(CFSTR("http"), scheme
)) {
83 CFHTTPMessageRef msg
= CFHTTPMessageCreateRequest(kCFAllocatorDefault
,
84 CFSTR("GET"), issuer
, kCFHTTPVersion1_1
);
86 secinfo("caissuer", "%@", msg
);
87 bool done
= asynchttp_request(msg
, 0, &request
->http
);
89 /* Count each http request we made */
90 analytics
->ca_issuer_fetches
++;
98 secdebug("caissuer", "failed to get %@", issuer
);
100 secnotice("caissuer", "skipping unsupported uri %@", issuer
);
106 /* No more issuers left to try, we're done. */
107 secdebug("caissuer", "no request issued");
108 request
->callback(request
->context
, NULL
);
109 SecCAIssuerRequestRelease(request
);
113 /* Releases parent unconditionally, and return a CFArrayRef containing
114 parent if the normalized subject of parent matches the normalized issuer
116 static CF_RETURNS_RETAINED CFArrayRef
SecCAIssuerConvertToParents(SecCertificateRef certificate
,
117 SecCertificateRef CF_CONSUMED parent
) {
118 CFDataRef nic
= SecCertificateGetNormalizedIssuerContent(certificate
);
119 CFArrayRef parents
= NULL
;
121 CFDataRef parent_nic
= SecCertificateGetNormalizedSubjectContent(parent
);
122 if (nic
&& parent_nic
&& CFEqual(nic
, parent_nic
)) {
123 const void *ventry
= parent
;
124 parents
= CFArrayCreate(NULL
, &ventry
, 1, &kCFTypeArrayCallBacks
);
131 #define SECONDS_PER_DAY (86400.0)
132 static void SecCAIssuerRequestCompleted(asynchttp_t
*http
,
133 CFTimeInterval maxAge
) {
134 /* Cast depends on http being first field in struct SecCAIssuerRequest. */
135 SecCAIssuerRequestRef request
= (SecCAIssuerRequestRef
)http
;
137 SecPathBuilderRef builder
= (SecPathBuilderRef
)request
->context
;
138 TrustAnalyticsBuilder
*analytics
= SecPathBuilderGetAnalyticsData(builder
);
140 /* Add the time this fetch took to complete to the total time */
141 analytics
->ca_issuer_fetch_time
+= (mach_absolute_time() - http
->start_time
);
144 CFDataRef data
= (request
->http
.response
?
145 CFHTTPMessageCopyBody(request
->http
.response
) : NULL
);
148 "accessLocation MUST be a uniformResourceIdentifier and the URI
149 MUST point to either a single DER encoded certificate as speci-
150 fied in [RFC2585] or a collection of certificates in a BER or
151 DER encoded "certs-only" CMS message as specified in [RFC2797]." */
153 /* DER-encoded certificate */
154 SecCertificateRef parent
= SecCertificateCreateWithData(NULL
, data
);
156 /* "certs-only" CMS Message */
158 CFArrayRef certificates
= NULL
;
159 certificates
= SecCMSCertificatesOnlyMessageCopyCertificates(data
);
160 /* @@@ Technically these can have more than one certificate */
161 if (certificates
&& CFArrayGetCount(certificates
) >= 1) {
162 parent
= CFRetainSafe((SecCertificateRef
)CFArrayGetValueAtIndex(certificates
, 0));
163 } else if (certificates
&& CFArrayGetCount(certificates
) > 1) {
165 /* Indicate that this trust evaluation encountered a CAIssuer fetch with multiple certs */
166 analytics
->ca_issuer_multiple_certs
= true;
169 CFReleaseNull(certificates
);
172 /* Retry in case the certificate is in PEM format. Some CAs
173 incorrectly return a PEM encoded cert, despite RFC 5280 4.2.2.1 */
175 parent
= SecCertificateCreateWithPEM(NULL
, data
);
179 /* We keep responses in the cache for at least 7 days, or longer
180 if the http response tells us to keep it around for more. */
181 if (maxAge
< SECONDS_PER_DAY
* 7)
182 maxAge
= SECONDS_PER_DAY
* 7;
183 CFAbsoluteTime expires
= CFAbsoluteTimeGetCurrent() + maxAge
;
184 CFURLRef issuer
= CFArrayGetValueAtIndex(request
->issuers
,
185 request
->issuerIX
- 1);
186 SecCAIssuerCacheAddCertificate(parent
, issuer
, expires
);
187 CFArrayRef parents
= SecCAIssuerConvertToParents(
188 request
->certificate
, parent
); /* note: this releases parent */
190 secdebug("caissuer", "response: %@ good", http
->response
);
191 request
->callback(request
->context
, parents
);
193 SecCAIssuerRequestRelease(request
);
196 } else if (analytics
) {
197 /* We failed to create a SecCertificateRef from the data we got */
198 analytics
->ca_issuer_unsupported_data
= true;
200 } else if (analytics
) {
201 /* We didn't get any data back, so the fetch failed */
202 analytics
->ca_issuer_fetch_failed
++;
205 secdebug("caissuer", "response: %@ not parent, trying next caissuer",
207 /* We're re-using this http object, so we need to free all the old memory. */
208 asynchttp_free(&request
->http
);
209 SecCAIssuerRequestIssue(request
);
212 static CFArrayRef
SecCAIssuerRequestCacheCopyParents(SecCertificateRef cert
,
213 CFArrayRef issuers
) {
214 CFIndex ix
= 0, ex
= CFArrayGetCount(issuers
);
215 for (;ix
< ex
; ++ix
) {
216 CFURLRef issuer
= CFArrayGetValueAtIndex(issuers
, ix
);
217 CFStringRef scheme
= CFURLCopyScheme(issuer
);
219 if (CFEqual(CFSTR("http"), scheme
)) {
220 CFArrayRef parents
= SecCAIssuerConvertToParents(cert
,
221 SecCAIssuerCacheCopyMatching(issuer
));
223 secdebug("caissuer", "cache hit, for %@ no request issued", issuer
);
234 bool SecCAIssuerCopyParents(SecCertificateRef certificate
, dispatch_queue_t queue
,
235 void *context
, void (*callback
)(void *, CFArrayRef
)) {
236 CFArrayRef issuers
= SecCertificateGetCAIssuers(certificate
);
238 /* certificate has no caissuer urls, we're done. */
239 callback(context
, NULL
);
243 SecPathBuilderRef builder
= (SecPathBuilderRef
)context
;
244 TrustAnalyticsBuilder
*analytics
= SecPathBuilderGetAnalyticsData(builder
);
245 CFArrayRef parents
= SecCAIssuerRequestCacheCopyParents(certificate
, issuers
);
248 /* We found parents in the cache */
249 analytics
->ca_issuer_cache_hit
= true;
251 callback(context
, parents
);
252 CFReleaseSafe(parents
);
256 /* We're going to have to make a network call */
257 analytics
->ca_issuer_network
= true;
260 /* Cache miss, let's issue a network request. */
261 SecCAIssuerRequestRef request
=
262 (SecCAIssuerRequestRef
)calloc(1, sizeof(*request
));
263 request
->http
.queue
= queue
;
264 request
->http
.completed
= SecCAIssuerRequestCompleted
;
265 CFRetain(certificate
);
266 request
->certificate
= certificate
;
267 request
->issuers
= issuers
;
268 request
->issuerIX
= 0;
269 request
->context
= context
;
270 request
->callback
= callback
;
272 return SecCAIssuerRequestIssue(request
);