]> git.saurik.com Git - apple/security.git/blob - OSX/sec/securityd/SecOCSPCache.c
Security-57740.1.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 #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 if (!ocspURL) {
138 ocspURL = SecCopyURLForFileInUserCacheDirectory(ocspRelPath);
139 }
140 CFStringRef ocspPath = NULL;
141 if (ocspURL) {
142 ocspPath = CFURLCopyFileSystemPath(ocspURL, kCFURLPOSIXPathStyle);
143 CFRelease(ocspURL);
144 }
145 return ocspPath;
146 }
147
148 static void SecOCSPCacheWith(void(^cacheJob)(SecOCSPCacheRef cache)) {
149 dispatch_once(&kSecOCSPCacheOnce, ^{
150 CFStringRef dbPath = SecOCSPCacheCopyPath();
151 if (dbPath) {
152 kSecOCSPCache = SecOCSPCacheCreate(dbPath);
153 CFRelease(dbPath);
154 }
155 });
156 // Do pre job run work here (cancel idle timers etc.)
157 cacheJob(kSecOCSPCache);
158 // Do post job run work here (gc timer, etc.)
159 }
160
161 static bool _SecOCSPCacheExpireWithTransaction(SecDbConnectionRef dbconn, CFAbsoluteTime now, CFErrorRef *error) {
162 //if (now > nextExpireTime)
163 {
164 return SecDbWithSQL(dbconn, expireSQL, error, ^bool(sqlite3_stmt *expire) {
165 return SecDbBindDouble(expire, 1, now, error) &&
166 SecDbStep(dbconn, expire, error, NULL);
167 });
168 // TODO: Write now + expireDelay to nextExpireTime;
169 // currently we try to expire entries on each cache write
170 }
171 }
172
173 /* Instance implementation. */
174
175 static void _SecOCSPCacheReplaceResponse(SecOCSPCacheRef this,
176 SecOCSPResponseRef oldResponse, SecOCSPResponseRef ocspResponse,
177 CFURLRef localResponderURI, CFAbsoluteTime verifyTime) {
178 secdebug("ocspcache", "adding response from %@", localResponderURI);
179 /* responses.ocspResponse */
180
181 // TODO: Update a latestProducedAt value using date in new entry, to ensure forward movement of time.
182 // Set "now" to the new producedAt we are receiving here if localTime is before this date.
183 // In addition whenever we run though here, check to see if "now" is more than past
184 // the nextCacheExpireDate and expire the cache if it is.
185 CFDataRef responseData = SecOCSPResponseGetData(ocspResponse);
186 __block CFErrorRef localError = NULL;
187 __block bool ok = true;
188 ok &= SecDbPerformWrite(this->db, &localError, ^(SecDbConnectionRef dbconn) {
189 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
190 __block sqlite3_int64 responseId;
191 if (oldResponse && (responseId = SecOCSPResponseGetID(oldResponse)) >= 0) {
192 ok = SecDbWithSQL(dbconn, deleteResponseSQL, &localError, ^bool(sqlite3_stmt *deleteResponse) {
193 ok = SecDbBindInt64(deleteResponse, 1, responseId, &localError);
194 /* Execute the delete statement. */
195 if (ok)
196 ok = SecDbStep(dbconn, deleteResponse, &localError, NULL);
197 return ok;
198 });
199 }
200
201 if (ok) ok = SecDbWithSQL(dbconn, insertResponseSQL, &localError, ^bool(sqlite3_stmt *insertResponse) {
202 if (ok)
203 ok = SecDbBindBlob(insertResponse, 1,
204 CFDataGetBytePtr(responseData),
205 CFDataGetLength(responseData),
206 SQLITE_TRANSIENT, &localError);
207
208 /* responses.responderURI */
209 if (ok) {
210 CFDataRef uriData = NULL;
211 if (localResponderURI) {
212 uriData = CFURLCreateData(kCFAllocatorDefault, localResponderURI,
213 kCFStringEncodingUTF8, false);
214 }
215 if (uriData) {
216 ok = SecDbBindBlob(insertResponse, 2,
217 CFDataGetBytePtr(uriData),
218 CFDataGetLength(uriData),
219 SQLITE_TRANSIENT, &localError);
220 CFRelease(uriData);
221 } else {
222 // Since we use SecDbClearBindings this shouldn't be needed.
223 //ok = SecDbBindNull(insertResponse, 2, &localError);
224 }
225 }
226 /* responses.expires */
227 if (ok)
228 ok = SecDbBindDouble(insertResponse, 3,
229 SecOCSPResponseGetExpirationTime(ocspResponse),
230 &localError);
231 /* responses.lastUsed */
232 if (ok)
233 ok = SecDbBindDouble(insertResponse, 4,
234 verifyTime,
235 &localError);
236
237 /* Execute the insert statement. */
238 if (ok)
239 ok = SecDbStep(dbconn, insertResponse, &localError, NULL);
240
241 responseId = sqlite3_last_insert_rowid(SecDbHandle(dbconn));
242 return ok;
243 });
244
245 /* Now add a link record for every singleResponse in the ocspResponse. */
246 if (ok) ok = SecDbWithSQL(dbconn, insertLinkSQL, &localError, ^bool(sqlite3_stmt *insertLink) {
247 SecAsn1OCSPSingleResponse **responses;
248 for (responses = ocspResponse->responseData.responses;
249 *responses; ++responses) {
250 SecAsn1OCSPSingleResponse *resp = *responses;
251 SecAsn1OCSPCertID *certId = &resp->certID;
252 if (ok) ok = SecDbBindBlob(insertLink, 1,
253 certId->algId.algorithm.Data,
254 certId->algId.algorithm.Length,
255 SQLITE_TRANSIENT, &localError);
256 if (ok) ok = SecDbBindBlob(insertLink, 2,
257 certId->issuerNameHash.Data,
258 certId->issuerNameHash.Length,
259 SQLITE_TRANSIENT, &localError);
260 if (ok) ok = SecDbBindBlob(insertLink, 3,
261 certId->issuerPubKeyHash.Data,
262 certId->issuerPubKeyHash.Length,
263 SQLITE_TRANSIENT, &localError);
264 if (ok) ok = SecDbBindBlob(insertLink, 4,
265 certId->serialNumber.Data,
266 certId->serialNumber.Length,
267 SQLITE_TRANSIENT, &localError);
268 if (ok) ok = SecDbBindInt64(insertLink, 5, responseId, &localError);
269
270 /* Execute the insert statement. */
271 if (ok) ok = SecDbStep(dbconn, insertLink, &localError, NULL);
272 if (ok) ok = SecDbReset(insertLink, &localError);
273 }
274 return ok;
275 });
276
277 // Remove expired entries here.
278 // TODO: Consider only doing this once per 24 hours or something.
279 if (ok) ok = _SecOCSPCacheExpireWithTransaction(dbconn, verifyTime, &localError);
280 if (!ok)
281 *commit = false;
282 });
283 });
284 if (!ok) {
285 secerror("_SecOCSPCacheAddResponse failed: %@", localError);
286 }
287 CFReleaseSafe(localError);
288 }
289
290 static SecOCSPResponseRef _SecOCSPCacheCopyMatching(SecOCSPCacheRef this,
291 SecOCSPRequestRef request, CFURLRef responderURI) {
292 const DERItem *publicKey;
293 CFDataRef issuer = NULL;
294 CFDataRef serial = NULL;
295 __block SecOCSPResponseRef response = NULL;
296 __block CFErrorRef localError = NULL;
297 __block bool ok = true;
298
299 require(publicKey = SecCertificateGetPublicKeyData(request->issuer), errOut);
300 require(issuer = SecCertificateCopyIssuerSequence(request->certificate), errOut);
301 #if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE))
302 require(serial = SecCertificateCopySerialNumber(request->certificate, NULL), errOut);
303 #else
304 require(serial = SecCertificateCopySerialNumber(request->certificate), errOut);
305 #endif
306
307 ok &= SecDbPerformRead(this->db, &localError, ^(SecDbConnectionRef dbconn) {
308 ok &= SecDbWithSQL(dbconn, selectHashAlgorithmSQL, &localError, ^bool(sqlite3_stmt *selectHash) {
309 ok = SecDbBindBlob(selectHash, 1, CFDataGetBytePtr(serial), CFDataGetLength(serial), SQLITE_TRANSIENT, &localError);
310 ok &= SecDbStep(dbconn, selectHash, &localError, ^(bool *stopHash) {
311 SecAsn1Oid algorithm;
312 algorithm.Data = (uint8_t *)sqlite3_column_blob(selectHash, 0);
313 algorithm.Length = sqlite3_column_bytes(selectHash, 0);
314
315 /* Calculate the issuerKey and issuerName digests using the returned
316 hashAlgorithm. */
317 CFDataRef issuerNameHash = SecDigestCreate(kCFAllocatorDefault,
318 &algorithm, NULL, CFDataGetBytePtr(issuer), CFDataGetLength(issuer));
319 CFDataRef issuerPubKeyHash = SecDigestCreate(kCFAllocatorDefault,
320 &algorithm, NULL, publicKey->data, publicKey->length);
321
322 if (issuerNameHash && issuerPubKeyHash && ok) ok &= SecDbWithSQL(dbconn, selectResponseSQL, &localError, ^bool(sqlite3_stmt *selectResponse) {
323 /* Now we have the serial, algorithm, issuerNameHash and
324 issuerPubKeyHash so let's lookup the db entry. */
325 if (ok) ok = SecDbBindBlob(selectResponse, 1, CFDataGetBytePtr(issuerNameHash),
326 CFDataGetLength(issuerNameHash), SQLITE_TRANSIENT, &localError);
327 if (ok) ok = SecDbBindBlob(selectResponse, 2, CFDataGetBytePtr(issuerPubKeyHash),
328 CFDataGetLength(issuerPubKeyHash), SQLITE_TRANSIENT, &localError);
329 if (ok) ok = SecDbBindBlob(selectResponse, 3, CFDataGetBytePtr(serial),
330 CFDataGetLength(serial), SQLITE_TRANSIENT, &localError);
331 if (ok) ok = SecDbBindBlob(selectResponse, 4, algorithm.Data,
332 algorithm.Length, SQLITE_TRANSIENT, &localError);
333 if (ok) ok &= SecDbStep(dbconn, selectResponse, &localError, ^(bool *stopResponse) {
334 /* Found an entry! */
335 secdebug("ocspcache", "found cached response");
336 CFDataRef resp = CFDataCreate(kCFAllocatorDefault,
337 sqlite3_column_blob(selectResponse, 0),
338 sqlite3_column_bytes(selectResponse, 0));
339 sqlite3_int64 responseID = sqlite3_column_int64(selectResponse, 1);
340 if (resp) {
341 SecOCSPResponseRef new_response = SecOCSPResponseCreateWithID(resp, responseID);
342 if (response) {
343 if (SecOCSPResponseProducedAt(response) < SecOCSPResponseProducedAt(new_response)) {
344 SecOCSPResponseFinalize(response);
345 response = new_response;
346 } else {
347 SecOCSPResponseFinalize(new_response);
348 }
349 } else {
350 response = new_response;
351 }
352 CFRelease(resp);
353 }
354 #if 0
355 if (response) {
356 //sqlite3_int64 responseId = sqlite3_column_int64(this->selectResponse, 1);
357 /* @@@ Update the lastUsed field in the db. */
358 }
359 #endif
360 });
361 return ok;
362 });
363
364 CFReleaseSafe(issuerNameHash);
365 CFReleaseSafe(issuerPubKeyHash);
366 });
367 return ok;
368 });
369 });
370
371 errOut:
372 CFReleaseSafe(serial);
373 CFReleaseSafe(issuer);
374
375 if (!ok) {
376 secerror("ocsp cache lookup failed: %@", localError);
377 if (response) {
378 SecOCSPResponseFinalize(response);
379 response = NULL;
380 }
381 }
382 CFReleaseSafe(localError);
383
384 secdebug("ocspcache", "returning %s", (response ? "cached response" : "NULL"));
385
386 return response;
387 }
388
389
390 /* Public API */
391
392 void SecOCSPCacheReplaceResponse(SecOCSPResponseRef old_response, SecOCSPResponseRef response,
393 CFURLRef localResponderURI, CFAbsoluteTime verifyTime) {
394 SecOCSPCacheWith(^(SecOCSPCacheRef cache) {
395 _SecOCSPCacheReplaceResponse(cache, old_response, response, localResponderURI, verifyTime);
396 });
397 }
398
399 SecOCSPResponseRef SecOCSPCacheCopyMatching(SecOCSPRequestRef request,
400 CFURLRef localResponderURI /* may be NULL */) {
401 __block SecOCSPResponseRef response = NULL;
402 SecOCSPCacheWith(^(SecOCSPCacheRef cache) {
403 response = _SecOCSPCacheCopyMatching(cache, request, localResponderURI);
404 });
405 return response;
406 }