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