]> git.saurik.com Git - apple/security.git/blob - OSX/sec/securityd/SecOCSPCache.c
Security-57740.60.18.tar.gz
[apple/security.git] / OSX / sec / securityd / SecOCSPCache.c
1 /*
2 * Copyright (c) 2009-2010,2012-2015 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 * SecOCSPCache.c - securityd
26 */
27
28 #include <CoreFoundation/CFUtilities.h>
29 #include <CoreFoundation/CFString.h>
30 #include <securityd/SecOCSPCache.h>
31 #include <utilities/debugging.h>
32 #include <Security/SecCertificateInternal.h>
33 #include <Security/SecFramework.h>
34 #include <Security/SecInternal.h>
35 #include <AssertMacros.h>
36 #include <stdlib.h>
37 #include <limits.h>
38 #include <sys/stat.h>
39 #include <asl.h>
40 #include "utilities/SecDb.h"
41 #include "utilities/SecFileLocations.h"
42 #include "utilities/iOSforOSX.h"
43
44 /* Note that lastUsed is actually time of insert because we don't
45 refresh lastUsed on each SELECT. */
46
47 #define expireSQL CFSTR("DELETE FROM responses WHERE expires<?")
48 #define insertResponseSQL CFSTR("INSERT INTO responses " \
49 "(ocspResponse,responderURI,expires,lastUsed) VALUES (?,?,?,?)")
50 #define insertLinkSQL CFSTR("INSERT INTO ocsp (hashAlgorithm," \
51 "issuerNameHash,issuerPubKeyHash,serialNum,responseId) VALUES (?,?,?,?,?)")
52 #define deleteResponseSQL CFSTR("DELETE FROM responses WHERE responseId=?")
53 #define selectHashAlgorithmSQL CFSTR("SELECT DISTINCT hashAlgorithm " \
54 "FROM ocsp WHERE serialNum=?")
55 #define selectResponseSQL CFSTR("SELECT ocspResponse,responseId FROM " \
56 "responses WHERE lastUsed>? AND responseId=(SELECT responseId FROM ocsp WHERE " \
57 "issuerNameHash=? AND issuerPubKeyHash=? AND serialNum=? AND hashAlgorithm=?)" \
58 " ORDER BY expires DESC")
59
60 #define kSecOCSPCacheFileName CFSTR("ocspcache.sqlite3")
61
62
63 // MARK; -
64 // MARK: SecOCSPCacheDb
65
66 static SecDbRef SecOCSPCacheDbCreate(CFStringRef path) {
67 return SecDbCreate(path, ^bool (SecDbConnectionRef dbconn, bool didCreate, bool *callMeAgainForNextConnection, CFErrorRef *error) {
68 __block bool ok;
69 ok = (SecDbExec(dbconn, CFSTR("PRAGMA auto_vacuum = FULL"), error) &&
70 SecDbExec(dbconn, CFSTR("PRAGMA journal_mode = WAL"), error));
71 CFErrorRef localError = NULL;
72 if (ok && !SecDbWithSQL(dbconn, selectHashAlgorithmSQL /* expireSQL */, &localError, NULL) && CFErrorGetCode(localError) == SQLITE_ERROR) {
73 /* SecDbWithSQL returns SQLITE_ERROR if the table we are preparing the above statement for doesn't exist. */
74 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, error, ^(bool *commit) {
75 ok = SecDbExec(dbconn,
76 CFSTR("CREATE TABLE ocsp("
77 "issuerNameHash BLOB NOT NULL,"
78 "issuerPubKeyHash BLOB NOT NULL,"
79 "serialNum BLOB NOT NULL,"
80 "hashAlgorithm BLOB NOT NULL,"
81 "responseId INTEGER NOT NULL"
82 ");"
83 "CREATE INDEX iResponseId ON ocsp(responseId);"
84 "CREATE INDEX iserialNum ON ocsp(serialNum);"
85 "CREATE INDEX iSNumDAlg ON ocsp(serialNum,hashAlgorithm);"
86 "CREATE TABLE responses("
87 "responseId INTEGER PRIMARY KEY,"
88 "ocspResponse BLOB NOT NULL,"
89 "responderURI BLOB,"
90 "expires DOUBLE NOT NULL,"
91 "lastUsed DOUBLE NOT NULL"
92 ");"
93 "CREATE INDEX iexpires ON responses(expires);"
94 "CREATE TRIGGER tocspdel BEFORE DELETE ON responses FOR EACH ROW "
95 "BEGIN "
96 "DELETE FROM ocsp WHERE responseId=OLD.responseId;"
97 " END;"), error);
98 *commit = ok;
99 });
100 }
101 CFReleaseSafe(localError);
102 if (!ok)
103 secerror("%s failed: %@", didCreate ? "Create" : "Open", error ? *error : NULL);
104 return ok;
105 });
106 }
107
108 // MARK; -
109 // MARK: SecOCSPCache
110
111 typedef struct __SecOCSPCache *SecOCSPCacheRef;
112 struct __SecOCSPCache {
113 SecDbRef db;
114 };
115
116 static dispatch_once_t kSecOCSPCacheOnce;
117 static SecOCSPCacheRef kSecOCSPCache = NULL;
118
119 static SecOCSPCacheRef SecOCSPCacheCreate(CFStringRef db_name) {
120 SecOCSPCacheRef this;
121
122 require(this = (SecOCSPCacheRef)malloc(sizeof(struct __SecOCSPCache)), errOut);
123 require(this->db = SecOCSPCacheDbCreate(db_name), errOut);
124
125 return this;
126
127 errOut:
128 if (this) {
129 CFReleaseSafe(this->db);
130 free(this);
131 }
132
133 return NULL;
134 }
135
136 static CFStringRef SecOCSPCacheCopyPath(void) {
137 CFStringRef ocspRelPath = kSecOCSPCacheFileName;
138 CFURLRef ocspURL = SecCopyURLForFileInKeychainDirectory(ocspRelPath);
139 if (!ocspURL) {
140 ocspURL = SecCopyURLForFileInUserCacheDirectory(ocspRelPath);
141 }
142 CFStringRef ocspPath = NULL;
143 if (ocspURL) {
144 ocspPath = CFURLCopyFileSystemPath(ocspURL, kCFURLPOSIXPathStyle);
145 CFRelease(ocspURL);
146 }
147 return ocspPath;
148 }
149
150 static void SecOCSPCacheWith(void(^cacheJob)(SecOCSPCacheRef cache)) {
151 dispatch_once(&kSecOCSPCacheOnce, ^{
152 CFStringRef dbPath = SecOCSPCacheCopyPath();
153 if (dbPath) {
154 kSecOCSPCache = SecOCSPCacheCreate(dbPath);
155 CFRelease(dbPath);
156 }
157 });
158 // Do pre job run work here (cancel idle timers etc.)
159 cacheJob(kSecOCSPCache);
160 // Do post job run work here (gc timer, etc.)
161 }
162
163 static bool _SecOCSPCacheExpireWithTransaction(SecDbConnectionRef dbconn, CFAbsoluteTime now, CFErrorRef *error) {
164 //if (now > nextExpireTime)
165 {
166 return SecDbWithSQL(dbconn, expireSQL, error, ^bool(sqlite3_stmt *expire) {
167 return SecDbBindDouble(expire, 1, now, error) &&
168 SecDbStep(dbconn, expire, error, NULL);
169 });
170 // TODO: Write now + expireDelay to nextExpireTime;
171 // currently we try to expire entries on each cache write
172 }
173 }
174
175 /* Instance implementation. */
176
177 static void _SecOCSPCacheReplaceResponse(SecOCSPCacheRef this,
178 SecOCSPResponseRef oldResponse, SecOCSPResponseRef ocspResponse,
179 CFURLRef localResponderURI, CFAbsoluteTime verifyTime) {
180 secdebug("ocspcache", "adding response from %@", localResponderURI);
181 /* responses.ocspResponse */
182
183 // TODO: Update a latestProducedAt value using date in new entry, to ensure forward movement of time.
184 // Set "now" to the new producedAt we are receiving here if localTime is before this date.
185 // In addition whenever we run though here, check to see if "now" is more than past
186 // the nextCacheExpireDate and expire the cache if it is.
187 CFDataRef responseData = SecOCSPResponseGetData(ocspResponse);
188 __block CFErrorRef localError = NULL;
189 __block bool ok = true;
190 ok &= SecDbPerformWrite(this->db, &localError, ^(SecDbConnectionRef dbconn) {
191 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
192 __block sqlite3_int64 responseId;
193 if (oldResponse && (responseId = SecOCSPResponseGetID(oldResponse)) >= 0) {
194 ok = SecDbWithSQL(dbconn, deleteResponseSQL, &localError, ^bool(sqlite3_stmt *deleteResponse) {
195 ok = SecDbBindInt64(deleteResponse, 1, responseId, &localError);
196 /* Execute the delete statement. */
197 if (ok)
198 ok = SecDbStep(dbconn, deleteResponse, &localError, NULL);
199 return ok;
200 });
201 }
202
203 if (ok) ok = SecDbWithSQL(dbconn, insertResponseSQL, &localError, ^bool(sqlite3_stmt *insertResponse) {
204 if (ok)
205 ok = SecDbBindBlob(insertResponse, 1,
206 CFDataGetBytePtr(responseData),
207 CFDataGetLength(responseData),
208 SQLITE_TRANSIENT, &localError);
209
210 /* responses.responderURI */
211 if (ok) {
212 CFDataRef uriData = NULL;
213 if (localResponderURI) {
214 uriData = CFURLCreateData(kCFAllocatorDefault, localResponderURI,
215 kCFStringEncodingUTF8, false);
216 }
217 if (uriData) {
218 ok = SecDbBindBlob(insertResponse, 2,
219 CFDataGetBytePtr(uriData),
220 CFDataGetLength(uriData),
221 SQLITE_TRANSIENT, &localError);
222 CFRelease(uriData);
223 } else {
224 // Since we use SecDbClearBindings this shouldn't be needed.
225 //ok = SecDbBindNull(insertResponse, 2, &localError);
226 }
227 }
228 /* responses.expires */
229 if (ok)
230 ok = SecDbBindDouble(insertResponse, 3,
231 SecOCSPResponseGetExpirationTime(ocspResponse),
232 &localError);
233 /* responses.lastUsed */
234 if (ok)
235 ok = SecDbBindDouble(insertResponse, 4,
236 verifyTime,
237 &localError);
238
239 /* Execute the insert statement. */
240 if (ok)
241 ok = SecDbStep(dbconn, insertResponse, &localError, NULL);
242
243 responseId = sqlite3_last_insert_rowid(SecDbHandle(dbconn));
244 return ok;
245 });
246
247 /* Now add a link record for every singleResponse in the ocspResponse. */
248 if (ok) ok = SecDbWithSQL(dbconn, insertLinkSQL, &localError, ^bool(sqlite3_stmt *insertLink) {
249 SecAsn1OCSPSingleResponse **responses;
250 for (responses = ocspResponse->responseData.responses;
251 *responses; ++responses) {
252 SecAsn1OCSPSingleResponse *resp = *responses;
253 SecAsn1OCSPCertID *certId = &resp->certID;
254 if (ok) ok = SecDbBindBlob(insertLink, 1,
255 certId->algId.algorithm.Data,
256 certId->algId.algorithm.Length,
257 SQLITE_TRANSIENT, &localError);
258 if (ok) ok = SecDbBindBlob(insertLink, 2,
259 certId->issuerNameHash.Data,
260 certId->issuerNameHash.Length,
261 SQLITE_TRANSIENT, &localError);
262 if (ok) ok = SecDbBindBlob(insertLink, 3,
263 certId->issuerPubKeyHash.Data,
264 certId->issuerPubKeyHash.Length,
265 SQLITE_TRANSIENT, &localError);
266 if (ok) ok = SecDbBindBlob(insertLink, 4,
267 certId->serialNumber.Data,
268 certId->serialNumber.Length,
269 SQLITE_TRANSIENT, &localError);
270 if (ok) ok = SecDbBindInt64(insertLink, 5, responseId, &localError);
271
272 /* Execute the insert statement. */
273 if (ok) ok = SecDbStep(dbconn, insertLink, &localError, NULL);
274 if (ok) ok = SecDbReset(insertLink, &localError);
275 }
276 return ok;
277 });
278
279 // Remove expired entries here.
280 // TODO: Consider only doing this once per 24 hours or something.
281 if (ok) ok = _SecOCSPCacheExpireWithTransaction(dbconn, verifyTime, &localError);
282 if (!ok)
283 *commit = false;
284 });
285 });
286 if (!ok) {
287 secerror("_SecOCSPCacheAddResponse failed: %@", localError);
288 }
289 CFReleaseSafe(localError);
290 }
291
292 static SecOCSPResponseRef _SecOCSPCacheCopyMatching(SecOCSPCacheRef this,
293 SecOCSPRequestRef request, CFURLRef responderURI, CFAbsoluteTime minInsertTime) {
294 const DERItem *publicKey;
295 CFDataRef issuer = NULL;
296 CFDataRef serial = NULL;
297 __block SecOCSPResponseRef response = NULL;
298 __block CFErrorRef localError = NULL;
299 __block bool ok = true;
300
301 require(publicKey = SecCertificateGetPublicKeyData(request->issuer), errOut);
302 require(issuer = SecCertificateCopyIssuerSequence(request->certificate), errOut);
303 #if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE))
304 require(serial = SecCertificateCopySerialNumber(request->certificate, NULL), errOut);
305 #else
306 require(serial = SecCertificateCopySerialNumber(request->certificate), errOut);
307 #endif
308
309 ok &= SecDbPerformRead(this->db, &localError, ^(SecDbConnectionRef dbconn) {
310 ok &= SecDbWithSQL(dbconn, selectHashAlgorithmSQL, &localError, ^bool(sqlite3_stmt *selectHash) {
311 ok = SecDbBindBlob(selectHash, 1, CFDataGetBytePtr(serial), CFDataGetLength(serial), SQLITE_TRANSIENT, &localError);
312 ok &= SecDbStep(dbconn, selectHash, &localError, ^(bool *stopHash) {
313 SecAsn1Oid algorithm;
314 algorithm.Data = (uint8_t *)sqlite3_column_blob(selectHash, 0);
315 algorithm.Length = sqlite3_column_bytes(selectHash, 0);
316
317 /* Calculate the issuerKey and issuerName digests using the returned
318 hashAlgorithm. */
319 CFDataRef issuerNameHash = SecDigestCreate(kCFAllocatorDefault,
320 &algorithm, NULL, CFDataGetBytePtr(issuer), CFDataGetLength(issuer));
321 CFDataRef issuerPubKeyHash = SecDigestCreate(kCFAllocatorDefault,
322 &algorithm, NULL, publicKey->data, publicKey->length);
323
324 if (issuerNameHash && issuerPubKeyHash && ok) ok &= SecDbWithSQL(dbconn, selectResponseSQL, &localError, ^bool(sqlite3_stmt *selectResponse) {
325 /* Now we have the serial, algorithm, issuerNameHash and
326 issuerPubKeyHash so let's lookup the db entry. */
327 if (ok) ok = SecDbBindDouble(selectResponse, 1, minInsertTime, &localError);
328 if (ok) ok = SecDbBindBlob(selectResponse, 2, CFDataGetBytePtr(issuerNameHash),
329 CFDataGetLength(issuerNameHash), SQLITE_TRANSIENT, &localError);
330 if (ok) ok = SecDbBindBlob(selectResponse, 3, CFDataGetBytePtr(issuerPubKeyHash),
331 CFDataGetLength(issuerPubKeyHash), SQLITE_TRANSIENT, &localError);
332 if (ok) ok = SecDbBindBlob(selectResponse, 4, CFDataGetBytePtr(serial),
333 CFDataGetLength(serial), SQLITE_TRANSIENT, &localError);
334 if (ok) ok = SecDbBindBlob(selectResponse, 5, algorithm.Data,
335 algorithm.Length, SQLITE_TRANSIENT, &localError);
336 if (ok) ok &= SecDbStep(dbconn, selectResponse, &localError, ^(bool *stopResponse) {
337 /* Found an entry! */
338 secdebug("ocspcache", "found cached response");
339 CFDataRef resp = CFDataCreate(kCFAllocatorDefault,
340 sqlite3_column_blob(selectResponse, 0),
341 sqlite3_column_bytes(selectResponse, 0));
342 sqlite3_int64 responseID = sqlite3_column_int64(selectResponse, 1);
343 if (resp) {
344 SecOCSPResponseRef new_response = SecOCSPResponseCreateWithID(resp, responseID);
345 if (response) {
346 if (SecOCSPResponseProducedAt(response) < SecOCSPResponseProducedAt(new_response)) {
347 SecOCSPResponseFinalize(response);
348 response = new_response;
349 } else {
350 SecOCSPResponseFinalize(new_response);
351 }
352 } else {
353 response = new_response;
354 }
355 CFRelease(resp);
356 }
357 });
358 return ok;
359 });
360
361 CFReleaseSafe(issuerNameHash);
362 CFReleaseSafe(issuerPubKeyHash);
363 });
364 return ok;
365 });
366 });
367
368 errOut:
369 CFReleaseSafe(serial);
370 CFReleaseSafe(issuer);
371
372 if (!ok) {
373 secerror("ocsp cache lookup failed: %@", localError);
374 if (response) {
375 SecOCSPResponseFinalize(response);
376 response = NULL;
377 }
378 }
379 CFReleaseSafe(localError);
380
381 secdebug("ocspcache", "returning %s", (response ? "cached response" : "NULL"));
382
383 return response;
384 }
385
386
387 /* Public API */
388
389 void SecOCSPCacheReplaceResponse(SecOCSPResponseRef old_response, SecOCSPResponseRef response,
390 CFURLRef localResponderURI, CFAbsoluteTime verifyTime) {
391 SecOCSPCacheWith(^(SecOCSPCacheRef cache) {
392 _SecOCSPCacheReplaceResponse(cache, old_response, response, localResponderURI, verifyTime);
393 });
394 }
395
396 SecOCSPResponseRef SecOCSPCacheCopyMatching(SecOCSPRequestRef request,
397 CFURLRef localResponderURI /* may be NULL */) {
398 __block SecOCSPResponseRef response = NULL;
399 SecOCSPCacheWith(^(SecOCSPCacheRef cache) {
400 response = _SecOCSPCacheCopyMatching(cache, request, localResponderURI, 0.0);
401 });
402 return response;
403 }
404
405 SecOCSPResponseRef SecOCSPCacheCopyMatchingWithMinInsertTime(SecOCSPRequestRef request,
406 CFURLRef localResponderURI, CFAbsoluteTime minInsertTime) {
407 __block SecOCSPResponseRef response = NULL;
408 SecOCSPCacheWith(^(SecOCSPCacheRef cache) {
409 response = _SecOCSPCacheCopyMatching(cache, request, localResponderURI, minInsertTime);
410 });
411 return response;
412 }