1/*
2 *  SecCAIssuerRequest.c
3 *  Security
4 *
5 *  Copyright (c) 2009-2014 Apple Inc.. All Rights Reserved.
6 *
7 */
8/*
9 * Copyright (c) 2009-2014 Apple Inc. All Rights Reserved.
10 *
11 * @APPLE_LICENSE_HEADER_START@
12 *
13 * This file contains Original Code and/or Modifications of Original Code
14 * as defined in and that are subject to the Apple Public Source License
15 * Version 2.0 (the 'License'). You may not use this file except in
16 * compliance with the License. Please obtain a copy of the License at
17 * http://www.opensource.apple.com/apsl/ and read it before using this
18 * file.
19 *
20 * The Original Code and all software distributed under the License are
21 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
22 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
23 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
24 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
25 * Please see the License for the specific language governing rights and
26 * limitations under the License.
27 *
28 * @APPLE_LICENSE_HEADER_END@
29 */
30
31/*
32 * SecCAIssuerRequest.c - asynchronous CAIssuer request fetching engine.
33 */
34
35
36#include "SecCAIssuerRequest.h"
37#include "SecCAIssuerCache.h"
38
39#include <Security/SecInternal.h>
40#include <CoreFoundation/CFURL.h>
41#include <CFNetwork/CFHTTPMessage.h>
42#include <utilities/debugging.h>
43#include <Security/SecCertificateInternal.h>
44#include <securityd/asynchttp.h>
45#include <stdlib.h>
46
47/* CA Issuer lookup code. */
48
49typedef struct SecCAIssuerRequest *SecCAIssuerRequestRef;
50struct SecCAIssuerRequest {
51    asynchttp_t http;   /* Must be first field. */
52    SecCertificateRef certificate;
53    CFArrayRef issuers; /* NONRETAINED */
54    CFIndex issuerIX;
55    void *context;
56    void (*callback)(void *, CFArrayRef);
57};
58
59static void SecCAIssuerRequestRelease(SecCAIssuerRequestRef request) {
60    CFRelease(request->certificate);
61    asynchttp_free(&request->http);
62    free(request);
63}
64
65static bool SecCAIssuerRequestIssue(SecCAIssuerRequestRef request) {
66    while (request->issuerIX < CFArrayGetCount(request->issuers)) {
67        CFURLRef issuer = CFArrayGetValueAtIndex(request->issuers,
68                                                 request->issuerIX++);
69        CFStringRef scheme = CFURLCopyScheme(issuer);
70        if (scheme) {
71            if (CFEqual(CFSTR("http"), scheme)) {
72                CFHTTPMessageRef msg = CFHTTPMessageCreateRequest(kCFAllocatorDefault,
73                                                                  CFSTR("GET"), issuer, kCFHTTPVersion1_1);
74                if (msg) {
75                    secdebug("caissuer", "%@", msg);
76                    bool done = asynchttp_request(msg, &request->http);
77                    CFRelease(msg);
78                    if (done == false) {
79                        CFRelease(scheme);
80                        return done;
81                    }
82                }
83                secdebug("caissuer", "failed to get %@", issuer);
84            } else {
85                secdebug("caissuer", "skipping unsupported uri %@", issuer);
86            }
87            CFRelease(scheme);
88        }
89    }
90
91    /* No more issuers left to try, we're done. */
92    secdebug("caissuer", "no request issued");
93    request->callback(request->context, NULL);
94    SecCAIssuerRequestRelease(request);
95    return true;
96}
97
98/* Releases parent unconditionally, and return a CFArrayRef containing
99   parent if the normalized subject of parent matches the normalized issuer
100   of certificate. */
101static CFArrayRef SecCAIssuerConvertToParents(SecCertificateRef certificate,
102    SecCertificateRef parent) {
103    CFDataRef nic = SecCertificateGetNormalizedIssuerContent(certificate);
104    CFArrayRef parents = NULL;
105    if (parent) {
106        if (CFEqual(nic, SecCertificateGetNormalizedSubjectContent(parent))) {
107            const void *ventry = parent;
108            parents = CFArrayCreate(NULL, &ventry, 1, &kCFTypeArrayCallBacks);
109        }
110        CFRelease(parent);
111    }
112    return parents;
113}
114
115#define SECONDS_PER_DAY (86400.0)
116static void SecCAIssuerRequestCompleted(asynchttp_t *http,
117    CFTimeInterval maxAge) {
118    /* Cast depends on http being first field in struct SecCAIssuerRequest. */
119    SecCAIssuerRequestRef request = (SecCAIssuerRequestRef)http;
120    CFDataRef data = (request->http.response ?
121        CFHTTPMessageCopyBody(request->http.response) : NULL);
122    if (data) {
123        SecCertificateRef parent = SecCertificateCreateWithData(NULL, data);
124        CFRelease(data);
125        if (parent) {
126            /* We keep responses in the cache for at least 7 days, or longer
127             if the http response tells us to keep it around for more. */
128            if (maxAge < SECONDS_PER_DAY * 7)
129                maxAge = SECONDS_PER_DAY * 7;
130            CFAbsoluteTime expires = CFAbsoluteTimeGetCurrent() + maxAge;
131            CFURLRef issuer = CFArrayGetValueAtIndex(request->issuers,
132                                                     request->issuerIX - 1);
133            SecCAIssuerCacheAddCertificate(parent, issuer, expires);
134            CFArrayRef parents = SecCAIssuerConvertToParents(
135                request->certificate, parent);
136            if (parents) {
137                secdebug("caissuer", "response: %@ good", http->response);
138                request->callback(request->context, parents);
139                CFRelease(parents);
140                SecCAIssuerRequestRelease(request);
141                return;
142            }
143        }
144    }
145
146    secdebug("caissuer", "response: %@ not parent, trying next caissuer",
147        http->response);
148    SecCAIssuerRequestIssue(request);
149}
150
151static CFArrayRef SecCAIssuerRequestCacheCopyParents(SecCertificateRef cert,
152    CFArrayRef issuers) {
153    CFIndex ix = 0, ex = CFArrayGetCount(issuers);
154    for (;ix < ex; ++ix) {
155        CFURLRef issuer = CFArrayGetValueAtIndex(issuers, ix);
156        CFStringRef scheme = CFURLCopyScheme(issuer);
157        if (scheme) {
158            if (CFEqual(CFSTR("http"), scheme)) {
159                CFArrayRef parents = SecCAIssuerConvertToParents(cert,
160                    SecCAIssuerCacheCopyMatching(issuer));
161                if (parents) {
162                    secdebug("caissuer", "cache hit, for %@ no request issued", issuer);
163		    CFRelease(scheme);
164                    return parents;
165                }
166            }
167            CFRelease(scheme);
168        }
169    }
170    return NULL;
171}
172
173bool SecCAIssuerCopyParents(SecCertificateRef certificate, dispatch_queue_t queue,
174    void *context, void (*callback)(void *, CFArrayRef)) {
175    CFArrayRef issuers = SecCertificateGetCAIssuers(certificate);
176    if (!issuers) {
177        /* certificate has no caissuer urls, we're done. */
178        callback(context, NULL);
179        return true;
180    }
181
182    CFArrayRef parents = SecCAIssuerRequestCacheCopyParents(certificate, issuers);
183    if (parents) {
184        callback(context, parents);
185        CFReleaseSafe(parents);
186        return true;
187    }
188
189    /* Cache miss, let's issue a network request. */
190    SecCAIssuerRequestRef request =
191        (SecCAIssuerRequestRef)calloc(1, sizeof(*request));
192    request->http.queue = queue;
193    request->http.completed = SecCAIssuerRequestCompleted;
194    CFRetain(certificate);
195    request->certificate = certificate;
196    request->issuers = issuers;
197    request->issuerIX = 0;
198    request->context = context;
199    request->callback = callback;
200
201    return SecCAIssuerRequestIssue(request);
202}
203
204