2 * Copyright (c) 2009-2010 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
25 * SecOCSPCache.c - securityd
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>
34 #include <AssertMacros.h>
45 #define ocspErrorLog(args...) asl_log(NULL, NULL, ASL_LEVEL_ERR, ## args)
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";
63 CFURLRef
CFCopyHomeDirectoryURLForUser(CFStringRef uName
); /* Pass NULL for the current user's home directory */
66 #define kSecOCSPCachePath "/Library/Keychains/ocspcache.sqlite3";
68 typedef struct __SecOCSPCache
*SecOCSPCacheRef
;
69 struct __SecOCSPCache
{
72 sqlite3_stmt
*beginTxn
;
74 sqlite3_stmt
*insertResponse
;
75 sqlite3_stmt
*insertLink
;
76 sqlite3_stmt
*selectHashAlgorithm
;
77 sqlite3_stmt
*selectResponse
;
81 static pthread_once_t kSecOCSPCacheOnce
= PTHREAD_ONCE_INIT
;
82 static SecOCSPCacheRef kSecOCSPCache
= NULL
;
84 /* @@@ Duplicated from SecTrustStore.c */
85 static int sec_create_path(const char *path
)
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
)
94 /* Search backwards for trailing '/'. */
95 if (pathbuf
[pos
] == '/')
98 /* Attempt to create parent directories of the database. */
99 if (!mkdir(pathbuf
, 0777))
107 return SQLITE_CANTOPEN
;
109 return SQLITE_READONLY
;
112 if (err
== ENOSPC
|| err
== EDQUOT
)
117 /* EFAULT || ELOOP | ENAMETOOLONG || something else */
118 return SQLITE_INTERNAL
;
125 static int sec_sqlite3_open(const char *db_name
, sqlite3
**s3h
,
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
133 s3e
= sec_create_path(db_name
);
135 s3e
= sqlite3_open(db_name
, s3h
);
141 static int sec_sqlite3_reset(sqlite3_stmt
*stmt
, int s3e
) {
143 if (s3e
== SQLITE_ROW
|| s3e
== SQLITE_DONE
)
145 s3e2
= sqlite3_reset(stmt
);
148 s3e2
= sqlite3_clear_bindings(stmt
);
154 static int SecOCSPCacheEnsureTxn(SecOCSPCacheRef
this) {
157 if (this->in_transaction
)
160 s3e
= sqlite3_step(this->beginTxn
);
161 if (s3e
== SQLITE_DONE
) {
162 this->in_transaction
= true;
165 secdebug("ocspcache", "sqlite3_step returned [%d]: %s", s3e
,
166 sqlite3_errmsg(this->s3h
));
168 s3e2
= sqlite3_reset(this->beginTxn
);
175 static int SecOCSPCacheCommitTxn(SecOCSPCacheRef
this) {
178 if (!this->in_transaction
)
181 s3e
= sqlite3_step(this->endTxn
);
182 if (s3e
== SQLITE_DONE
) {
183 this->in_transaction
= false;
186 secdebug("ocspcache", "sqlite3_step returned [%d]: %s", s3e
,
187 sqlite3_errmsg(this->s3h
));
189 s3e2
= sqlite3_reset(this->endTxn
);
196 static SecOCSPCacheRef
SecOCSPCacheCreate(const char *db_name
) {
197 SecOCSPCacheRef
this;
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;
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
);
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
);
218 /* sqlite3_prepare returns SQLITE_ERROR if the table we are
219 compiling this statement for doesn't exist. */
221 s3e
= sqlite3_exec(this->s3h
,
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"
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,"
236 "expires DOUBLE NOT NULL,"
237 "lastUsed DOUBLE NOT NULL"
239 "CREATE INDEX iexpires ON responses(expires);"
240 "CREATE TRIGGER tocspdel BEFORE DELETE ON responses FOR EACH ROW "
242 "DELETE FROM ocsp WHERE responseId=OLD.responseId;"
244 , NULL
, NULL
, &errmsg
);
246 ocspErrorLog("ocsp db CREATE TABLES: %s", errmsg
);
247 sqlite3_free(errmsg
);
249 require_noerr(s3e
, errOut
);
250 s3e
= sqlite3_prepare_v2(this->s3h
, expireSQL
, sizeof(expireSQL
),
251 &this->expire
, NULL
);
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
);
271 sqlite3_close(this->s3h
);
278 static void SecOCSPCacheInit(void) {
279 static const char *path
= kSecOCSPCachePath
;
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
];
285 size_t pathLen
= strlen(path
);
287 homeLen
= strlen(home
);
288 if (homeLen
+ pathLen
>= sizeof(buffer
)) {
292 strlcpy(buffer
, home
, sizeof(buffer
));
294 CFURLRef homeURL
= CFCopyHomeDirectoryURLForUser(NULL
);
298 CFURLGetFileSystemRepresentation(homeURL
, true, (uint8_t *)buffer
,
301 homeLen
= strlen(buffer
);
302 buffer
[homeLen
] = '\0';
303 if (homeLen
+ pathLen
>= sizeof(buffer
)) {
308 strlcat(buffer
, path
, sizeof(buffer
));
314 kSecOCSPCache
= SecOCSPCacheCreate(path
);
316 atexit(SecOCSPCacheGC
);
319 /* Instance implemenation. */
321 static void _SecOCSPCacheAddResponse(SecOCSPCacheRef
this,
322 SecOCSPResponseRef ocspResponse
, CFURLRef localResponderURI
) {
325 secdebug("ocspcache", "adding response from %@", localResponderURI
);
326 require_noerr(s3e
= SecOCSPCacheEnsureTxn(this), errOut
);
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
);
334 /* responses.responderURI */
336 CFDataRef uriData
= NULL
;
337 if (localResponderURI
) {
338 uriData
= CFURLCreateData(kCFAllocatorDefault
, localResponderURI
,
339 kCFStringEncodingUTF8
, false);
342 s3e
= sqlite3_bind_blob_wrapper(this->insertResponse
, 2,
343 CFDataGetBytePtr(uriData
),
344 CFDataGetLength(uriData
), SQLITE_TRANSIENT
);
347 s3e
= sqlite3_bind_null(this->insertResponse
, 2);
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
));
357 /* Execute the insert statement. */
358 if (!s3e
) s3e
= sqlite3_step(this->insertResponse
);
359 require_noerr(s3e
= sec_sqlite3_reset(this->insertResponse
, s3e
), errOut
);
361 sqlite3_int64 responseId
= sqlite3_last_insert_rowid(this->s3h
);
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
;
370 s3e
= sqlite3_bind_blob_wrapper(this->insertLink
, 1,
371 certId
->algId
.algorithm
.Data
, certId
->algId
.algorithm
.Length
,
373 if (!s3e
) s3e
= sqlite3_bind_blob_wrapper(this->insertLink
, 2,
374 certId
->issuerNameHash
.Data
, certId
->issuerNameHash
.Length
,
376 if (!s3e
) s3e
= sqlite3_bind_blob_wrapper(this->insertLink
, 3,
377 certId
->issuerPubKeyHash
.Data
, certId
->issuerPubKeyHash
.Length
,
379 if (!s3e
) s3e
= sqlite3_bind_blob_wrapper(this->insertLink
, 4,
380 certId
->serialNumber
.Data
, certId
->serialNumber
.Length
,
382 if (!s3e
) s3e
= sqlite3_bind_int64(this->insertLink
, 5,
385 /* Execute the insert statement. */
386 if (!s3e
) s3e
= sqlite3_step(this->insertLink
);
387 require_noerr(s3e
= sec_sqlite3_reset(this->insertLink
, s3e
), errOut
);
392 ocspErrorLog("ocsp cache add failed: %s", sqlite3_errmsg(this->s3h
));
393 /* @@@ Blow away the cache and create a new db. */
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
;
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);
416 /* Calcluate the issuerKey and issuerName digests using the returned
418 CFDataRef issuerNameHash
= SecDigestCreate(kCFAllocatorDefault
,
419 &algorithm
, NULL
, CFDataGetBytePtr(issuer
), CFDataGetLength(issuer
));
420 CFDataRef issuerPubKeyHash
= SecDigestCreate(kCFAllocatorDefault
,
421 &algorithm
, NULL
, publicKey
->data
, publicKey
->length
);
423 require(issuerNameHash
&& issuerPubKeyHash
, nextResponse
);
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
);
436 if (!s3e
) s3e
= sqlite3_step(this->selectResponse
);
437 if (s3e
== SQLITE_ROW
) {
438 /* Found an entry! */
439 secdebug("ocspcache", "found cached response");
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
);
445 response
= SecOCSPResponseCreate(resp
, NULL_TIME
);
449 //sqlite3_int64 responseId = sqlite3_column_int64(this->selectResponse, 1);
450 /* @@@ Update the lastUsed field in the db. */
455 s3e
= sec_sqlite3_reset(this->selectResponse
, s3e
);
456 CFReleaseSafe(issuerNameHash
);
457 CFReleaseSafe(issuerPubKeyHash
);
459 require_noerr(s3e
= sec_sqlite3_reset(this->selectHashAlgorithm
, s3e
), errOut
);
462 CFReleaseSafe(serial
);
463 CFReleaseSafe(issuer
);
466 ocspErrorLog("ocsp cache lookup failed: %s", sqlite3_errmsg(this->s3h
));
467 /* @@@ Blow away the cache and create a new db. */
470 SecOCSPResponseFinalize(response
);
475 secdebug("ocspcache", "returning %s", (response
? "cached response" : "NULL"));
480 static void _SecOCSPCacheGC(SecOCSPCacheRef
this) {
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
);
492 ocspErrorLog("ocsp cache expire failed: %s", sqlite3_errmsg(this->s3h
));
493 /* @@@ Blow away the cache and create a new db. */
497 static void _SecOCSPCacheFlush(SecOCSPCacheRef
this) {
499 secdebug("ocspcache", "flushing pending changes");
500 s3e
= SecOCSPCacheCommitTxn(this);
503 ocspErrorLog("ocsp cache flush failed: %s", sqlite3_errmsg(this->s3h
));
504 /* @@@ Blow away the cache and create a new db. */
510 void SecOCSPCacheAddResponse(SecOCSPResponseRef response
,
511 CFURLRef localResponderURI
) {
512 pthread_once(&kSecOCSPCacheOnce
, SecOCSPCacheInit
);
516 _SecOCSPCacheAddResponse(kSecOCSPCache
, response
, localResponderURI
);
519 SecOCSPResponseRef
SecOCSPCacheCopyMatching(SecOCSPRequestRef request
,
520 CFURLRef localResponderURI
/* may be NULL */) {
521 pthread_once(&kSecOCSPCacheOnce
, SecOCSPCacheInit
);
525 return _SecOCSPCacheCopyMatching(kSecOCSPCache
, request
, localResponderURI
);
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.
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
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.
541 void SecOCSPCacheGC(void) {
543 _SecOCSPCacheGC(kSecOCSPCache
);
546 /* Call this periodically or perhaps when we are exiting due to low memory. */
547 void SecOCSPCacheFlush(void) {
549 _SecOCSPCacheFlush(kSecOCSPCache
);