2 * Copyright (c) 2011 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@
23 * Created by Michael Brouwer on 6/17/11.
27 * SecCAIssuerCache.c - securityd
30 #include <securityd/SecCAIssuerCache.h>
31 #include <utilities/debugging.h>
32 #include <Security/SecCertificateInternal.h>
33 #include <Security/SecFramework.h>
34 #include <Security/SecInternal.h>
36 #include <AssertMacros.h>
43 #include <dispatch/dispatch.h>
45 #include "utilities/sqlutils.h"
46 #include "utilities/iOSforOSX.h"
48 #include <CoreFoundation/CFUtilities.h>
49 #include <utilities/SecFileLocations.h>
51 #define caissuerErrorLog(args...) asl_log(NULL, NULL, ASL_LEVEL_ERR, ## args)
53 static const char expireSQL
[] = "DELETE FROM issuers WHERE expires<?";
54 static const char beginTxnSQL
[] = "BEGIN EXCLUSIVE TRANSACTION";
55 static const char endTxnSQL
[] = "COMMIT TRANSACTION";
56 static const char insertIssuerSQL
[] = "INSERT OR REPLACE INTO issuers "
57 "(uri,expires,certificate) VALUES (?,?,?)";
58 static const char selectIssuerSQL
[] = "SELECT certificate FROM "
59 "issuers WHERE uri=?";
61 #define kSecCAIssuerFileName "caissuercache.sqlite3"
63 typedef struct __SecCAIssuerCache
*SecCAIssuerCacheRef
;
64 struct __SecCAIssuerCache
{
65 dispatch_queue_t queue
;
68 sqlite3_stmt
*beginTxn
;
70 sqlite3_stmt
*insertIssuer
;
71 sqlite3_stmt
*selectIssuer
;
75 static dispatch_once_t kSecCAIssuerCacheOnce
;
76 static SecCAIssuerCacheRef kSecCAIssuerCache
;
78 /* @@@ Duplicated from SecTrustStore.c */
79 static int sec_create_path(const char *path
)
81 char pathbuf
[PATH_MAX
];
82 size_t pos
, len
= strlen(path
);
83 if (len
== 0 || len
> PATH_MAX
)
84 return SQLITE_CANTOPEN
;
85 memcpy(pathbuf
, path
, len
);
86 for (pos
= len
-1; pos
> 0; --pos
)
88 /* Search backwards for trailing '/'. */
89 if (pathbuf
[pos
] == '/')
92 /* Attempt to create parent directories of the database. */
93 if (!mkdir(pathbuf
, 0777))
101 return SQLITE_CANTOPEN
;
103 return SQLITE_READONLY
;
106 if (err
== ENOSPC
|| err
== EDQUOT
)
111 /* EFAULT || ELOOP | ENAMETOOLONG || something else */
112 return SQLITE_INTERNAL
;
119 static int sec_sqlite3_open(const char *db_name
, sqlite3
**s3h
,
123 s3e
= sqlite3_open(db_name
, s3h
);
124 if (s3e
== SQLITE_CANTOPEN
&& create_path
) {
125 /* Make sure the path to db_name exists and is writable, then
127 s3e
= sec_create_path(db_name
);
129 s3e
= sqlite3_open(db_name
, s3h
);
135 static int sec_sqlite3_reset(sqlite3_stmt
*stmt
, int s3e
) {
137 if (s3e
== SQLITE_ROW
|| s3e
== SQLITE_DONE
)
139 s3e2
= sqlite3_reset(stmt
);
142 s3e2
= sqlite3_clear_bindings(stmt
);
148 static int SecCAIssuerCacheEnsureTxn(SecCAIssuerCacheRef
this) {
151 if (this->in_transaction
)
154 s3e
= sqlite3_step(this->beginTxn
);
155 if (s3e
== SQLITE_DONE
) {
156 this->in_transaction
= true;
159 secdebug("caissuercache", "sqlite3_step returned [%d]: %s", s3e
,
160 sqlite3_errmsg(this->s3h
));
162 s3e2
= sqlite3_reset(this->beginTxn
);
169 static int SecCAIssuerCacheCommitTxn(SecCAIssuerCacheRef
this) {
172 if (!this->in_transaction
)
175 s3e
= sqlite3_step(this->endTxn
);
176 if (s3e
== SQLITE_DONE
) {
177 this->in_transaction
= false;
180 secdebug("caissuercache", "sqlite3_step returned [%d]: %s", s3e
,
181 sqlite3_errmsg(this->s3h
));
183 s3e2
= sqlite3_reset(this->endTxn
);
190 static SecCAIssuerCacheRef
SecCAIssuerCacheCreate(const char *db_name
) {
191 SecCAIssuerCacheRef
this;
195 require(this = (SecCAIssuerCacheRef
)calloc(sizeof(struct __SecCAIssuerCache
), 1), errOut
);
196 require_action_quiet((this->queue
= dispatch_queue_create("caissuercache", 0)), errOut
, s3e
= errSecAllocate
);
197 require_noerr(s3e
= sec_sqlite3_open(db_name
, &this->s3h
, create
), errOut
);
198 this->in_transaction
= false;
200 s3e
= sqlite3_prepare_v2(this->s3h
, beginTxnSQL
, sizeof(beginTxnSQL
),
201 &this->beginTxn
, NULL
);
202 require_noerr(s3e
, errOut
);
203 s3e
= sqlite3_prepare_v2(this->s3h
, endTxnSQL
, sizeof(endTxnSQL
),
204 &this->endTxn
, NULL
);
205 require_noerr(s3e
, errOut
);
207 s3e
= sqlite3_prepare_v2(this->s3h
, expireSQL
, sizeof(expireSQL
),
208 &this->expire
, NULL
);
209 if (create
&& s3e
== SQLITE_ERROR
) {
210 s3e
= SecCAIssuerCacheEnsureTxn(this);
211 require_noerr(s3e
, errOut
);
213 /* sqlite3_prepare returns SQLITE_ERROR if the table we are
214 compiling this statement for doesn't exist. */
216 s3e
= sqlite3_exec(this->s3h
,
217 "CREATE TABLE issuers("
218 "uri BLOB PRIMARY KEY,"
219 "expires DOUBLE NOT NULL,"
220 "certificate BLOB NOT NULL"
222 "CREATE INDEX iexpires ON issuers(expires);"
223 , NULL
, NULL
, &errmsg
);
225 caissuerErrorLog("caissuer db CREATE TABLES: %s", errmsg
);
226 sqlite3_free(errmsg
);
228 require_noerr(s3e
, errOut
);
229 s3e
= sqlite3_prepare_v2(this->s3h
, expireSQL
, sizeof(expireSQL
),
230 &this->expire
, NULL
);
232 require_noerr(s3e
, errOut
);
233 s3e
= sqlite3_prepare_v2(this->s3h
, insertIssuerSQL
, sizeof(insertIssuerSQL
),
234 &this->insertIssuer
, NULL
);
235 require_noerr(s3e
, errOut
);
236 s3e
= sqlite3_prepare_v2(this->s3h
, selectIssuerSQL
, sizeof(selectIssuerSQL
),
237 &this->selectIssuer
, NULL
);
238 require_noerr(s3e
, errOut
);
245 dispatch_release(this->queue
);
247 sqlite3_close(this->s3h
);
254 static void SecCAIssuerCacheInit(void) {
255 WithPathInKeychainDirectory(CFSTR(kSecCAIssuerFileName
), ^(const char *utf8String
) {
256 kSecCAIssuerCache
= SecCAIssuerCacheCreate(utf8String
);
259 if (kSecCAIssuerCache
)
260 atexit(SecCAIssuerCacheGC
);
263 /* Instance implemenation. */
265 static void _SecCAIssuerCacheAddCertificate(SecCAIssuerCacheRef
this,
266 SecCertificateRef certificate
,
267 CFURLRef uri
, CFAbsoluteTime expires
) {
270 secdebug("caissuercache", "adding certificate from %@", uri
);
271 require_noerr(s3e
= SecCAIssuerCacheEnsureTxn(this), errOut
);
275 require_action(uriData
= CFURLCreateData(kCFAllocatorDefault
, uri
,
276 kCFStringEncodingUTF8
, false), errOut
, s3e
= SQLITE_NOMEM
);
277 s3e
= sqlite3_bind_blob_wrapper(this->insertIssuer
, 1,
278 CFDataGetBytePtr(uriData
), CFDataGetLength(uriData
), SQLITE_TRANSIENT
);
282 if (!s3e
) s3e
= sqlite3_bind_double(this->insertIssuer
, 2, expires
);
284 /* issuer.certificate */
285 if (!s3e
) s3e
= sqlite3_bind_blob_wrapper(this->insertIssuer
, 3,
286 SecCertificateGetBytePtr(certificate
),
287 SecCertificateGetLength(certificate
), SQLITE_TRANSIENT
);
289 /* Execute the insert statement. */
290 if (!s3e
) s3e
= sqlite3_step(this->insertIssuer
);
291 require_noerr(s3e
= sec_sqlite3_reset(this->insertIssuer
, s3e
), errOut
);
295 caissuerErrorLog("caissuer cache add failed: %s", sqlite3_errmsg(this->s3h
));
296 /* TODO: Blow away the cache and create a new db. */
300 static SecCertificateRef
_SecCAIssuerCacheCopyMatching(SecCAIssuerCacheRef
this,
302 SecCertificateRef certificate
= NULL
;
305 CFDataRef uriData
= NULL
;
306 require(uriData
= CFURLCreateData(kCFAllocatorDefault
, uri
,
307 kCFStringEncodingUTF8
, false), errOut
);
308 s3e
= sqlite3_bind_blob_wrapper(this->selectIssuer
, 1, CFDataGetBytePtr(uriData
),
309 CFDataGetLength(uriData
), SQLITE_TRANSIENT
);
312 if (!s3e
) s3e
= sqlite3_step(this->selectIssuer
);
313 if (s3e
== SQLITE_ROW
) {
314 /* Found an entry! */
315 secdebug("caissuercache", "found cached response for %@", uri
);
317 const void *respData
= sqlite3_column_blob(this->selectIssuer
, 0);
318 int respLen
= sqlite3_column_bytes(this->selectIssuer
, 0);
319 certificate
= SecCertificateCreateWithBytes(NULL
, respData
, respLen
);
322 require_noerr(s3e
= sec_sqlite3_reset(this->selectIssuer
, s3e
), errOut
);
326 if (s3e
!= SQLITE_DONE
) {
327 caissuerErrorLog("caissuer cache lookup failed: %s", sqlite3_errmsg(this->s3h
));
328 /* TODO: Blow away the cache and create a new db. */
332 CFRelease(certificate
);
337 secdebug("caissuercache", "returning %s for %@", (certificate
? "cached response" : "NULL"), uri
);
342 static void _SecCAIssuerCacheGC(void *context
) {
343 SecCAIssuerCacheRef
this = context
;
346 require_noerr(s3e
= SecCAIssuerCacheEnsureTxn(this), errOut
);
347 secdebug("caissuercache", "expiring stale responses");
348 s3e
= sqlite3_bind_double(this->expire
, 1, CFAbsoluteTimeGetCurrent());
349 if (!s3e
) s3e
= sqlite3_step(this->expire
);
350 require_noerr(s3e
= sec_sqlite3_reset(this->expire
, s3e
), errOut
);
351 require_noerr(s3e
= SecCAIssuerCacheCommitTxn(this), errOut
);
355 caissuerErrorLog("caissuer cache expire failed: %s", sqlite3_errmsg(this->s3h
));
356 /* TODO: Blow away the cache and create a new db. */
360 static void _SecCAIssuerCacheFlush(void *context
) {
361 SecCAIssuerCacheRef
this = context
;
364 secdebug("caissuercache", "flushing pending changes");
365 s3e
= SecCAIssuerCacheCommitTxn(this);
368 caissuerErrorLog("caissuer cache flush failed: %s", sqlite3_errmsg(this->s3h
));
369 /* TODO: Blow away the cache and create a new db. */
375 void SecCAIssuerCacheAddCertificate(SecCertificateRef certificate
,
376 CFURLRef uri
, CFAbsoluteTime expires
) {
377 dispatch_once(&kSecCAIssuerCacheOnce
, ^{
378 SecCAIssuerCacheInit();
380 if (!kSecCAIssuerCache
)
383 dispatch_sync(kSecCAIssuerCache
->queue
, ^{
384 _SecCAIssuerCacheAddCertificate(kSecCAIssuerCache
, certificate
, uri
, expires
);
388 SecCertificateRef
SecCAIssuerCacheCopyMatching(CFURLRef uri
) {
389 dispatch_once(&kSecCAIssuerCacheOnce
, ^{
390 SecCAIssuerCacheInit();
392 __block SecCertificateRef cert
= NULL
;
393 if (kSecCAIssuerCache
)
394 dispatch_sync(kSecCAIssuerCache
->queue
, ^{
395 cert
= _SecCAIssuerCacheCopyMatching(kSecCAIssuerCache
, uri
);
400 /* This should be called on a normal non emergency exit. This function
401 effectively does a SecCAIssuerCacheFlush.
402 Currently this is called from our atexit handeler.
403 This function expires any records that are stale and commits.
405 Idea for future cache management policies:
406 Expire old cache entires from database if:
407 - The time to do so has arrived based on the nextExpire date in the
409 - If the size of the database exceeds the limit set in the maxSize field
410 in the policy table, vacuum the db. If the database is still too
411 big, expire records on a LRU basis.
413 void SecCAIssuerCacheGC(void) {
414 if (kSecCAIssuerCache
)
415 dispatch_sync(kSecCAIssuerCache
->queue
, ^{
416 _SecCAIssuerCacheGC(kSecCAIssuerCache
);
420 /* Call this periodically or perhaps when we are exiting due to low memory. */
421 void SecCAIssuerCacheFlush(void) {
422 if (kSecCAIssuerCache
)
423 dispatch_sync(kSecCAIssuerCache
->queue
, ^{
424 _SecCAIssuerCacheFlush(kSecCAIssuerCache
);