]> git.saurik.com Git - apple/security.git/blob - sec/securityd/SecOCSPCache.c
Security-55178.0.1.tar.gz
[apple/security.git] / sec / securityd / SecOCSPCache.c
1 /*
2 * Copyright (c) 2009-2010 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 <securityd/SecOCSPCache.h>
29 #include <security_utilities/debugging.h>
30 #include <Security/SecCertificateInternal.h>
31 #include <Security/SecFramework.h>
32 #include <Security/SecInternal.h>
33 #include <sqlite3.h>
34 #include <AssertMacros.h>
35 #include <stdlib.h>
36 #include <limits.h>
37 #include <string.h>
38 #include <fcntl.h>
39 #include <sys/stat.h>
40 #include <errno.h>
41 #include <pthread.h>
42 #include <asl.h>
43 #include "sqlutils.h"
44
45 #define ocspErrorLog(args...) asl_log(NULL, NULL, ASL_LEVEL_ERR, ## args)
46
47 static const char expireSQL[] = "DELETE FROM responses WHERE expires<?";
48 static const char beginTxnSQL[] = "BEGIN EXCLUSIVE TRANSACTION";
49 static const char endTxnSQL[] = "COMMIT TRANSACTION";
50 static const char insertResponseSQL[] = "INSERT INTO responses "
51 "(ocspResponse,responderURI,expires,lastUsed) VALUES (?,?,?,?)";
52 static const char insertLinkSQL[] = "INSERT INTO ocsp (hashAlgorithm,"
53 "issuerNameHash,issuerPubKeyHash,serialNum,responseId) VALUES (?,?,?,?,?)";
54 static const char selectHashAlgorithmSQL[] = "SELECT DISTINCT hashAlgorithm "
55 "FROM ocsp WHERE serialNum=?";
56 static const char selectResponseSQL[] = "SELECT ocspResponse,responseId FROM "
57 "responses WHERE responseId=(SELECT responseId FROM ocsp WHERE "
58 "issuerNameHash=? AND issuerPubKeyHash=? AND serialNum=? AND hashAlgorithm=?)"
59 " ORDER BY expires DESC";
60
61 #if NO_SERVER
62 CF_EXPORT
63 CFURLRef CFCopyHomeDirectoryURLForUser(CFStringRef uName); /* Pass NULL for the current user's home directory */
64 #endif
65
66 #define kSecOCSPCachePath "/Library/Keychains/ocspcache.sqlite3";
67
68 typedef struct __SecOCSPCache *SecOCSPCacheRef;
69 struct __SecOCSPCache {
70 sqlite3 *s3h;
71 sqlite3_stmt *expire;
72 sqlite3_stmt *beginTxn;
73 sqlite3_stmt *endTxn;
74 sqlite3_stmt *insertResponse;
75 sqlite3_stmt *insertLink;
76 sqlite3_stmt *selectHashAlgorithm;
77 sqlite3_stmt *selectResponse;
78 bool in_transaction;
79 };
80
81 static pthread_once_t kSecOCSPCacheOnce = PTHREAD_ONCE_INIT;
82 static SecOCSPCacheRef kSecOCSPCache = NULL;
83
84 /* @@@ Duplicated from SecTrustStore.c */
85 static int sec_create_path(const char *path)
86 {
87 char pathbuf[PATH_MAX];
88 size_t pos, len = strlen(path);
89 if (len == 0 || len > PATH_MAX)
90 return SQLITE_CANTOPEN;
91 memcpy(pathbuf, path, len);
92 for (pos = len-1; pos > 0; --pos)
93 {
94 /* Search backwards for trailing '/'. */
95 if (pathbuf[pos] == '/')
96 {
97 pathbuf[pos] = '\0';
98 /* Attempt to create parent directories of the database. */
99 if (!mkdir(pathbuf, 0777))
100 break;
101 else
102 {
103 int err = errno;
104 if (err == EEXIST)
105 return 0;
106 if (err == ENOTDIR)
107 return SQLITE_CANTOPEN;
108 if (err == EROFS)
109 return SQLITE_READONLY;
110 if (err == EACCES)
111 return SQLITE_PERM;
112 if (err == ENOSPC || err == EDQUOT)
113 return SQLITE_FULL;
114 if (err == EIO)
115 return SQLITE_IOERR;
116
117 /* EFAULT || ELOOP | ENAMETOOLONG || something else */
118 return SQLITE_INTERNAL;
119 }
120 }
121 }
122 return SQLITE_OK;
123 }
124
125 static int sec_sqlite3_open(const char *db_name, sqlite3 **s3h,
126 bool create_path)
127 {
128 int s3e;
129 s3e = sqlite3_open(db_name, s3h);
130 if (s3e == SQLITE_CANTOPEN && create_path) {
131 /* Make sure the path to db_name exists and is writable, then
132 try again. */
133 s3e = sec_create_path(db_name);
134 if (!s3e)
135 s3e = sqlite3_open(db_name, s3h);
136 }
137
138 return s3e;
139 }
140
141 static int sec_sqlite3_reset(sqlite3_stmt *stmt, int s3e) {
142 int s3e2;
143 if (s3e == SQLITE_ROW || s3e == SQLITE_DONE)
144 s3e = SQLITE_OK;
145 s3e2 = sqlite3_reset(stmt);
146 if (s3e2 && !s3e)
147 s3e = s3e2;
148 s3e2 = sqlite3_clear_bindings(stmt);
149 if (s3e2 && !s3e)
150 s3e = s3e2;
151 return s3e;
152 }
153
154 static int SecOCSPCacheEnsureTxn(SecOCSPCacheRef this) {
155 int s3e, s3e2;
156
157 if (this->in_transaction)
158 return SQLITE_OK;
159
160 s3e = sqlite3_step(this->beginTxn);
161 if (s3e == SQLITE_DONE) {
162 this->in_transaction = true;
163 s3e = SQLITE_OK;
164 } else {
165 secdebug("ocspcache", "sqlite3_step returned [%d]: %s", s3e,
166 sqlite3_errmsg(this->s3h));
167 }
168 s3e2 = sqlite3_reset(this->beginTxn);
169 if (s3e2 && !s3e)
170 s3e = s3e2;
171
172 return s3e;
173 }
174
175 static int SecOCSPCacheCommitTxn(SecOCSPCacheRef this) {
176 int s3e, s3e2;
177
178 if (!this->in_transaction)
179 return SQLITE_OK;
180
181 s3e = sqlite3_step(this->endTxn);
182 if (s3e == SQLITE_DONE) {
183 this->in_transaction = false;
184 s3e = SQLITE_OK;
185 } else {
186 secdebug("ocspcache", "sqlite3_step returned [%d]: %s", s3e,
187 sqlite3_errmsg(this->s3h));
188 }
189 s3e2 = sqlite3_reset(this->endTxn);
190 if (s3e2 && !s3e)
191 s3e = s3e2;
192
193 return s3e;
194 }
195
196 static SecOCSPCacheRef SecOCSPCacheCreate(const char *db_name) {
197 SecOCSPCacheRef this;
198 int s3e;
199 bool create = true;
200
201 require(this = (SecOCSPCacheRef)malloc(sizeof(struct __SecOCSPCache)), errOut);
202 require_noerr(s3e = sec_sqlite3_open(db_name, &this->s3h, create), errOut);
203 this->in_transaction = false;
204
205 s3e = sqlite3_prepare_v2(this->s3h, beginTxnSQL, sizeof(beginTxnSQL),
206 &this->beginTxn, NULL);
207 require_noerr(s3e, errOut);
208 s3e = sqlite3_prepare_v2(this->s3h, endTxnSQL, sizeof(endTxnSQL),
209 &this->endTxn, NULL);
210 require_noerr(s3e, errOut);
211
212 s3e = sqlite3_prepare_v2(this->s3h, expireSQL, sizeof(expireSQL),
213 &this->expire, NULL);
214 if (create && s3e == SQLITE_ERROR) {
215 s3e = SecOCSPCacheEnsureTxn(this);
216 require_noerr(s3e, errOut);
217
218 /* sqlite3_prepare returns SQLITE_ERROR if the table we are
219 compiling this statement for doesn't exist. */
220 char *errmsg = NULL;
221 s3e = sqlite3_exec(this->s3h,
222 "CREATE TABLE ocsp("
223 "issuerNameHash BLOB NOT NULL,"
224 "issuerPubKeyHash BLOB NOT NULL,"
225 "serialNum BLOB NOT NULL,"
226 "hashAlgorithm BLOB NOT NULL,"
227 "responseId INTEGER NOT NULL"
228 ");"
229 "CREATE INDEX iResponseId ON ocsp(responseId);"
230 "CREATE INDEX iserialNum ON ocsp(serialNum);"
231 "CREATE INDEX iSNumDAlg ON ocsp(serialNum,hashAlgorithm);"
232 "CREATE TABLE responses("
233 "responseId INTEGER PRIMARY KEY,"
234 "ocspResponse BLOB NOT NULL,"
235 "responderURI BLOB,"
236 "expires DOUBLE NOT NULL,"
237 "lastUsed DOUBLE NOT NULL"
238 ");"
239 "CREATE INDEX iexpires ON responses(expires);"
240 "CREATE TRIGGER tocspdel BEFORE DELETE ON responses FOR EACH ROW "
241 "BEGIN "
242 "DELETE FROM ocsp WHERE responseId=OLD.responseId;"
243 " END;"
244 , NULL, NULL, &errmsg);
245 if (errmsg) {
246 ocspErrorLog("ocsp db CREATE TABLES: %s", errmsg);
247 sqlite3_free(errmsg);
248 }
249 require_noerr(s3e, errOut);
250 s3e = sqlite3_prepare_v2(this->s3h, expireSQL, sizeof(expireSQL),
251 &this->expire, NULL);
252 }
253 require_noerr(s3e, errOut);
254 s3e = sqlite3_prepare_v2(this->s3h, insertResponseSQL, sizeof(insertResponseSQL),
255 &this->insertResponse, NULL);
256 require_noerr(s3e, errOut);
257 s3e = sqlite3_prepare_v2(this->s3h, insertLinkSQL, sizeof(insertLinkSQL),
258 &this->insertLink, NULL);
259 require_noerr(s3e, errOut);
260 s3e = sqlite3_prepare_v2(this->s3h, selectHashAlgorithmSQL, sizeof(selectHashAlgorithmSQL),
261 &this->selectHashAlgorithm, NULL);
262 require_noerr(s3e, errOut);
263 s3e = sqlite3_prepare_v2(this->s3h, selectResponseSQL, sizeof(selectResponseSQL),
264 &this->selectResponse, NULL);
265 require_noerr(s3e, errOut);
266
267 return this;
268
269 errOut:
270 if (this) {
271 sqlite3_close(this->s3h);
272 free(this);
273 }
274
275 return NULL;
276 }
277
278 static void SecOCSPCacheInit(void) {
279 static const char *path = kSecOCSPCachePath;
280 #if NO_SERVER
281 /* Added this block of code back to keep the tests happy for now. */
282 const char *home = getenv("HOME");
283 char buffer[PATH_MAX];
284 size_t homeLen;
285 size_t pathLen = strlen(path);
286 if (home) {
287 homeLen = strlen(home);
288 if (homeLen + pathLen >= sizeof(buffer)) {
289 return;
290 }
291
292 strlcpy(buffer, home, sizeof(buffer));
293 } else {
294 CFURLRef homeURL = CFCopyHomeDirectoryURLForUser(NULL);
295 if (!homeURL)
296 return;
297
298 CFURLGetFileSystemRepresentation(homeURL, true, (uint8_t *)buffer,
299 sizeof(buffer));
300 CFRelease(homeURL);
301 homeLen = strlen(buffer);
302 buffer[homeLen] = '\0';
303 if (homeLen + pathLen >= sizeof(buffer)) {
304 return;
305 }
306 }
307
308 strlcat(buffer, path, sizeof(buffer));
309
310 path = buffer;
311
312 #endif
313
314 kSecOCSPCache = SecOCSPCacheCreate(path);
315 if (kSecOCSPCache)
316 atexit(SecOCSPCacheGC);
317 }
318
319 /* Instance implemenation. */
320
321 static void _SecOCSPCacheAddResponse(SecOCSPCacheRef this,
322 SecOCSPResponseRef ocspResponse, CFURLRef localResponderURI) {
323 int s3e;
324
325 secdebug("ocspcache", "adding response from %@", localResponderURI);
326 require_noerr(s3e = SecOCSPCacheEnsureTxn(this), errOut);
327
328 /* responses.ocspResponse */
329 CFDataRef responseData = SecOCSPResponseGetData(ocspResponse);
330 s3e = sqlite3_bind_blob_wrapper(this->insertResponse, 1,
331 CFDataGetBytePtr(responseData),
332 CFDataGetLength(responseData), SQLITE_TRANSIENT);
333
334 /* responses.responderURI */
335 if (!s3e) {
336 CFDataRef uriData = NULL;
337 if (localResponderURI) {
338 uriData = CFURLCreateData(kCFAllocatorDefault, localResponderURI,
339 kCFStringEncodingUTF8, false);
340 }
341 if (uriData) {
342 s3e = sqlite3_bind_blob_wrapper(this->insertResponse, 2,
343 CFDataGetBytePtr(uriData),
344 CFDataGetLength(uriData), SQLITE_TRANSIENT);
345 CFRelease(uriData);
346 } else {
347 s3e = sqlite3_bind_null(this->insertResponse, 2);
348 }
349 }
350 /* responses.expires */
351 if (!s3e) s3e = sqlite3_bind_double(this->insertResponse, 3,
352 SecOCSPResponseGetExpirationTime(ocspResponse));
353 /* responses.lastUsed */
354 if (!s3e) s3e = sqlite3_bind_double(this->insertResponse, 4,
355 SecOCSPResponseVerifyTime(ocspResponse));
356
357 /* Execute the insert statement. */
358 if (!s3e) s3e = sqlite3_step(this->insertResponse);
359 require_noerr(s3e = sec_sqlite3_reset(this->insertResponse, s3e), errOut);
360
361 sqlite3_int64 responseId = sqlite3_last_insert_rowid(this->s3h);
362
363 /* Now add a link record for every singleResponse in the ocspResponse. */
364 SecAsn1OCSPSingleResponse **responses;
365 for (responses = ocspResponse->responseData.responses;
366 *responses; ++responses) {
367 SecAsn1OCSPSingleResponse *resp = *responses;
368 SecAsn1OCSPCertID *certId = &resp->certID;
369
370 s3e = sqlite3_bind_blob_wrapper(this->insertLink, 1,
371 certId->algId.algorithm.Data, certId->algId.algorithm.Length,
372 SQLITE_TRANSIENT);
373 if (!s3e) s3e = sqlite3_bind_blob_wrapper(this->insertLink, 2,
374 certId->issuerNameHash.Data, certId->issuerNameHash.Length,
375 SQLITE_TRANSIENT);
376 if (!s3e) s3e = sqlite3_bind_blob_wrapper(this->insertLink, 3,
377 certId->issuerPubKeyHash.Data, certId->issuerPubKeyHash.Length,
378 SQLITE_TRANSIENT);
379 if (!s3e) s3e = sqlite3_bind_blob_wrapper(this->insertLink, 4,
380 certId->serialNumber.Data, certId->serialNumber.Length,
381 SQLITE_TRANSIENT);
382 if (!s3e) s3e = sqlite3_bind_int64(this->insertLink, 5,
383 responseId);
384
385 /* Execute the insert statement. */
386 if (!s3e) s3e = sqlite3_step(this->insertLink);
387 require_noerr(s3e = sec_sqlite3_reset(this->insertLink, s3e), errOut);
388 }
389
390 errOut:
391 if (s3e) {
392 ocspErrorLog("ocsp cache add failed: %s", sqlite3_errmsg(this->s3h));
393 /* @@@ Blow away the cache and create a new db. */
394 }
395 }
396
397 static SecOCSPResponseRef _SecOCSPCacheCopyMatching(SecOCSPCacheRef this,
398 SecOCSPRequestRef request, CFURLRef responderURI) {
399 SecOCSPResponseRef response = NULL;
400 const DERItem *publicKey;
401 CFDataRef issuer = NULL;
402 CFDataRef serial = NULL;
403 int s3e = SQLITE_ERROR;
404
405 require(publicKey = SecCertificateGetPublicKeyData(request->issuer), errOut);
406 require(issuer = SecCertificateCopyIssuerSequence(request->certificate), errOut);
407 require(serial = SecCertificateCopySerialNumber(request->certificate), errOut);
408 s3e = sqlite3_bind_blob_wrapper(this->selectHashAlgorithm, 1,
409 CFDataGetBytePtr(serial), CFDataGetLength(serial), SQLITE_TRANSIENT);
410 while (!s3e && !response &&
411 (s3e = sqlite3_step(this->selectHashAlgorithm)) == SQLITE_ROW) {
412 SecAsn1Oid algorithm;
413 algorithm.Data = (uint8_t *)sqlite3_column_blob(this->selectHashAlgorithm, 0);
414 algorithm.Length = sqlite3_column_bytes(this->selectHashAlgorithm, 0);
415
416 /* Calcluate the issuerKey and issuerName digests using the returned
417 hashAlgorithm. */
418 CFDataRef issuerNameHash = SecDigestCreate(kCFAllocatorDefault,
419 &algorithm, NULL, CFDataGetBytePtr(issuer), CFDataGetLength(issuer));
420 CFDataRef issuerPubKeyHash = SecDigestCreate(kCFAllocatorDefault,
421 &algorithm, NULL, publicKey->data, publicKey->length);
422
423 require(issuerNameHash && issuerPubKeyHash, nextResponse);
424
425 /* Now we have the serial, algorithm, issuerNameHash and
426 issuerPubKeyHash so let's lookup the db entry. */
427 s3e = sqlite3_bind_blob_wrapper(this->selectResponse, 1, CFDataGetBytePtr(issuerNameHash),
428 CFDataGetLength(issuerNameHash), SQLITE_TRANSIENT);
429 if (!s3e) s3e = sqlite3_bind_blob_wrapper(this->selectResponse, 2, CFDataGetBytePtr(issuerPubKeyHash),
430 CFDataGetLength(issuerPubKeyHash), SQLITE_TRANSIENT);
431 if (!s3e) s3e = sqlite3_bind_blob_wrapper(this->selectResponse, 3, CFDataGetBytePtr(serial),
432 CFDataGetLength(serial), SQLITE_TRANSIENT);
433 if (!s3e) s3e = sqlite3_bind_blob_wrapper(this->selectResponse, 4, algorithm.Data,
434 algorithm.Length, SQLITE_TRANSIENT);
435
436 if (!s3e) s3e = sqlite3_step(this->selectResponse);
437 if (s3e == SQLITE_ROW) {
438 /* Found an entry! */
439 secdebug("ocspcache", "found cached response");
440
441 const void *respData = sqlite3_column_blob(this->selectResponse, 0);
442 int respLen = sqlite3_column_bytes(this->selectResponse, 0);
443 CFDataRef resp = CFDataCreate(kCFAllocatorDefault, respData, respLen);
444 if (resp) {
445 response = SecOCSPResponseCreate(resp, NULL_TIME);
446 CFRelease(resp);
447 }
448 if (response) {
449 //sqlite3_int64 responseId = sqlite3_column_int64(this->selectResponse, 1);
450 /* @@@ Update the lastUsed field in the db. */
451 }
452 }
453
454 nextResponse:
455 s3e = sec_sqlite3_reset(this->selectResponse, s3e);
456 CFReleaseSafe(issuerNameHash);
457 CFReleaseSafe(issuerPubKeyHash);
458 }
459 require_noerr(s3e = sec_sqlite3_reset(this->selectHashAlgorithm, s3e), errOut);
460
461 errOut:
462 CFReleaseSafe(serial);
463 CFReleaseSafe(issuer);
464
465 if (s3e) {
466 ocspErrorLog("ocsp cache lookup failed: %s", sqlite3_errmsg(this->s3h));
467 /* @@@ Blow away the cache and create a new db. */
468
469 if (response) {
470 SecOCSPResponseFinalize(response);
471 response = NULL;
472 }
473 }
474
475 secdebug("ocspcache", "returning %s", (response ? "cached response" : "NULL"));
476
477 return response;
478 }
479
480 static void _SecOCSPCacheGC(SecOCSPCacheRef this) {
481 int s3e;
482
483 require_noerr(s3e = SecOCSPCacheEnsureTxn(this), errOut);
484 secdebug("ocspcache", "expiring stale responses");
485 s3e = sqlite3_bind_double(this->expire, 1, CFAbsoluteTimeGetCurrent());
486 if (!s3e) s3e = sqlite3_step(this->expire);
487 require_noerr(s3e = sec_sqlite3_reset(this->expire, s3e), errOut);
488 require_noerr(s3e = SecOCSPCacheCommitTxn(this), errOut);
489
490 errOut:
491 if (s3e) {
492 ocspErrorLog("ocsp cache expire failed: %s", sqlite3_errmsg(this->s3h));
493 /* @@@ Blow away the cache and create a new db. */
494 }
495 }
496
497 static void _SecOCSPCacheFlush(SecOCSPCacheRef this) {
498 int s3e;
499 secdebug("ocspcache", "flushing pending changes");
500 s3e = SecOCSPCacheCommitTxn(this);
501
502 if (s3e) {
503 ocspErrorLog("ocsp cache flush failed: %s", sqlite3_errmsg(this->s3h));
504 /* @@@ Blow away the cache and create a new db. */
505 }
506 }
507
508 /* Public API */
509
510 void SecOCSPCacheAddResponse(SecOCSPResponseRef response,
511 CFURLRef localResponderURI) {
512 pthread_once(&kSecOCSPCacheOnce, SecOCSPCacheInit);
513 if (!kSecOCSPCache)
514 return;
515
516 _SecOCSPCacheAddResponse(kSecOCSPCache, response, localResponderURI);
517 }
518
519 SecOCSPResponseRef SecOCSPCacheCopyMatching(SecOCSPRequestRef request,
520 CFURLRef localResponderURI /* may be NULL */) {
521 pthread_once(&kSecOCSPCacheOnce, SecOCSPCacheInit);
522 if (!kSecOCSPCache)
523 return NULL;
524
525 return _SecOCSPCacheCopyMatching(kSecOCSPCache, request, localResponderURI);
526 }
527
528 /* This should be called on a normal non emergency exit. This function
529 effectively does a SecOCSPCacheFlush.
530 Currently this is called from our atexit handeler.
531 This function expires any records that are stale and commits.
532
533 Idea for future cache management policies:
534 Expire old cache entires from database if:
535 - The time to do so has arrived based on the nextExpire date in the
536 policy table.
537 - If the size of the database exceeds the limit set in the maxSize field
538 in the policy table, vacuum the db. If the database is still too
539 big, expire records on a LRU basis.
540 */
541 void SecOCSPCacheGC(void) {
542 if (kSecOCSPCache)
543 _SecOCSPCacheGC(kSecOCSPCache);
544 }
545
546 /* Call this periodically or perhaps when we are exiting due to low memory. */
547 void SecOCSPCacheFlush(void) {
548 if (kSecOCSPCache)
549 _SecOCSPCacheFlush(kSecOCSPCache);
550 }