2 * Copyright (c) 2011-2014 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@
26 * SecCAIssuerCache.c - securityd
29 #include <securityd/SecCAIssuerCache.h>
30 #include <utilities/debugging.h>
31 #include <Security/SecCertificateInternal.h>
32 #include <Security/SecFramework.h>
33 #include <Security/SecInternal.h>
35 #include <AssertMacros.h>
42 #include <dispatch/dispatch.h>
44 #include "utilities/sqlutils.h"
45 #include "utilities/iOSforOSX.h"
47 #include <CoreFoundation/CFUtilities.h>
48 #include <utilities/SecFileLocations.h>
50 #define caissuerErrorLog(args...) asl_log(NULL, NULL, ASL_LEVEL_ERR, ## args)
52 static const char expireSQL
[] = "DELETE FROM issuers WHERE expires<?";
53 static const char beginTxnSQL
[] = "BEGIN EXCLUSIVE TRANSACTION";
54 static const char endTxnSQL
[] = "COMMIT TRANSACTION";
55 static const char insertIssuerSQL
[] = "INSERT OR REPLACE INTO issuers "
56 "(uri,expires,certificate) VALUES (?,?,?)";
57 static const char selectIssuerSQL
[] = "SELECT certificate FROM "
58 "issuers WHERE uri=?";
60 #define kSecCAIssuerFileName "caissuercache.sqlite3"
62 typedef struct __SecCAIssuerCache
*SecCAIssuerCacheRef
;
63 struct __SecCAIssuerCache
{
64 dispatch_queue_t queue
;
67 sqlite3_stmt
*beginTxn
;
69 sqlite3_stmt
*insertIssuer
;
70 sqlite3_stmt
*selectIssuer
;
74 static dispatch_once_t kSecCAIssuerCacheOnce
;
75 static SecCAIssuerCacheRef kSecCAIssuerCache
;
77 /* @@@ Duplicated from SecTrustStore.c */
78 static int sec_create_path(const char *path
)
80 char pathbuf
[PATH_MAX
];
81 size_t pos
, len
= strlen(path
);
82 if (len
== 0 || len
> PATH_MAX
)
83 return SQLITE_CANTOPEN
;
84 memcpy(pathbuf
, path
, len
);
85 for (pos
= len
-1; pos
> 0; --pos
)
87 /* Search backwards for trailing '/'. */
88 if (pathbuf
[pos
] == '/')
91 /* Attempt to create parent directories of the database. */
92 if (!mkdir(pathbuf
, 0777))
100 return SQLITE_CANTOPEN
;
102 return SQLITE_READONLY
;
105 if (err
== ENOSPC
|| err
== EDQUOT
)
110 /* EFAULT || ELOOP | ENAMETOOLONG || something else */
111 return SQLITE_INTERNAL
;
118 static int sec_sqlite3_open(const char *db_name
, sqlite3
**s3h
,
122 s3e
= sqlite3_open(db_name
, s3h
);
123 if (s3e
== SQLITE_CANTOPEN
&& create_path
) {
124 /* Make sure the path to db_name exists and is writable, then
126 s3e
= sec_create_path(db_name
);
128 s3e
= sqlite3_open(db_name
, s3h
);
134 static int sec_sqlite3_reset(sqlite3_stmt
*stmt
, int s3e
) {
136 if (s3e
== SQLITE_ROW
|| s3e
== SQLITE_DONE
)
138 s3e2
= sqlite3_reset(stmt
);
141 s3e2
= sqlite3_clear_bindings(stmt
);
147 static int SecCAIssuerCacheEnsureTxn(SecCAIssuerCacheRef
this) {
150 if (this->in_transaction
)
153 s3e
= sqlite3_step(this->beginTxn
);
154 if (s3e
== SQLITE_DONE
) {
155 this->in_transaction
= true;
158 secdebug("caissuercache", "sqlite3_step returned [%d]: %s", s3e
,
159 sqlite3_errmsg(this->s3h
));
161 s3e2
= sqlite3_reset(this->beginTxn
);
168 static int SecCAIssuerCacheCommitTxn(SecCAIssuerCacheRef
this) {
171 if (!this->in_transaction
)
174 s3e
= sqlite3_step(this->endTxn
);
175 if (s3e
== SQLITE_DONE
) {
176 this->in_transaction
= false;
179 secdebug("caissuercache", "sqlite3_step returned [%d]: %s", s3e
,
180 sqlite3_errmsg(this->s3h
));
182 s3e2
= sqlite3_reset(this->endTxn
);
189 static SecCAIssuerCacheRef
SecCAIssuerCacheCreate(const char *db_name
) {
190 SecCAIssuerCacheRef
this;
194 require(this = (SecCAIssuerCacheRef
)calloc(sizeof(struct __SecCAIssuerCache
), 1), errOut
);
195 require_action_quiet((this->queue
= dispatch_queue_create("caissuercache", 0)), errOut
, s3e
= errSecAllocate
);
196 require_noerr(s3e
= sec_sqlite3_open(db_name
, &this->s3h
, create
), errOut
);
197 this->in_transaction
= false;
199 s3e
= sqlite3_prepare_v2(this->s3h
, beginTxnSQL
, sizeof(beginTxnSQL
),
200 &this->beginTxn
, NULL
);
201 require_noerr(s3e
, errOut
);
202 s3e
= sqlite3_prepare_v2(this->s3h
, endTxnSQL
, sizeof(endTxnSQL
),
203 &this->endTxn
, NULL
);
204 require_noerr(s3e
, errOut
);
206 s3e
= sqlite3_prepare_v2(this->s3h
, expireSQL
, sizeof(expireSQL
),
207 &this->expire
, NULL
);
208 if (create
&& s3e
== SQLITE_ERROR
) {
209 s3e
= SecCAIssuerCacheEnsureTxn(this);
210 require_noerr(s3e
, errOut
);
212 /* sqlite3_prepare returns SQLITE_ERROR if the table we are
213 compiling this statement for doesn't exist. */
215 s3e
= sqlite3_exec(this->s3h
,
216 "CREATE TABLE issuers("
217 "uri BLOB PRIMARY KEY,"
218 "expires DOUBLE NOT NULL,"
219 "certificate BLOB NOT NULL"
221 "CREATE INDEX iexpires ON issuers(expires);"
222 , NULL
, NULL
, &errmsg
);
224 caissuerErrorLog("caissuer db CREATE TABLES: %s", errmsg
);
225 sqlite3_free(errmsg
);
227 require_noerr(s3e
, errOut
);
228 s3e
= sqlite3_prepare_v2(this->s3h
, expireSQL
, sizeof(expireSQL
),
229 &this->expire
, NULL
);
231 require_noerr(s3e
, errOut
);
232 s3e
= sqlite3_prepare_v2(this->s3h
, insertIssuerSQL
, sizeof(insertIssuerSQL
),
233 &this->insertIssuer
, NULL
);
234 require_noerr(s3e
, errOut
);
235 s3e
= sqlite3_prepare_v2(this->s3h
, selectIssuerSQL
, sizeof(selectIssuerSQL
),
236 &this->selectIssuer
, NULL
);
237 require_noerr(s3e
, errOut
);
244 dispatch_release(this->queue
);
246 sqlite3_close(this->s3h
);
253 static void SecCAIssuerCacheInit(void) {
254 WithPathInKeychainDirectory(CFSTR(kSecCAIssuerFileName
), ^(const char *utf8String
) {
255 kSecCAIssuerCache
= SecCAIssuerCacheCreate(utf8String
);
258 if (kSecCAIssuerCache
)
259 atexit(SecCAIssuerCacheGC
);
262 /* Instance implemenation. */
264 static void _SecCAIssuerCacheAddCertificate(SecCAIssuerCacheRef
this,
265 SecCertificateRef certificate
,
266 CFURLRef uri
, CFAbsoluteTime expires
) {
269 secdebug("caissuercache", "adding certificate from %@", uri
);
270 require_noerr(s3e
= SecCAIssuerCacheEnsureTxn(this), errOut
);
274 require_action(uriData
= CFURLCreateData(kCFAllocatorDefault
, uri
,
275 kCFStringEncodingUTF8
, false), errOut
, s3e
= SQLITE_NOMEM
);
276 s3e
= sqlite3_bind_blob_wrapper(this->insertIssuer
, 1,
277 CFDataGetBytePtr(uriData
), CFDataGetLength(uriData
), SQLITE_TRANSIENT
);
281 if (!s3e
) s3e
= sqlite3_bind_double(this->insertIssuer
, 2, expires
);
283 /* issuer.certificate */
284 if (!s3e
) s3e
= sqlite3_bind_blob_wrapper(this->insertIssuer
, 3,
285 SecCertificateGetBytePtr(certificate
),
286 SecCertificateGetLength(certificate
), SQLITE_TRANSIENT
);
288 /* Execute the insert statement. */
289 if (!s3e
) s3e
= sqlite3_step(this->insertIssuer
);
290 require_noerr(s3e
= sec_sqlite3_reset(this->insertIssuer
, s3e
), errOut
);
294 caissuerErrorLog("caissuer cache add failed: %s", sqlite3_errmsg(this->s3h
));
295 /* TODO: Blow away the cache and create a new db. */
299 static SecCertificateRef
_SecCAIssuerCacheCopyMatching(SecCAIssuerCacheRef
this,
301 SecCertificateRef certificate
= NULL
;
304 CFDataRef uriData
= NULL
;
305 require(uriData
= CFURLCreateData(kCFAllocatorDefault
, uri
,
306 kCFStringEncodingUTF8
, false), errOut
);
307 s3e
= sqlite3_bind_blob_wrapper(this->selectIssuer
, 1, CFDataGetBytePtr(uriData
),
308 CFDataGetLength(uriData
), SQLITE_TRANSIENT
);
311 if (!s3e
) s3e
= sqlite3_step(this->selectIssuer
);
312 if (s3e
== SQLITE_ROW
) {
313 /* Found an entry! */
314 secdebug("caissuercache", "found cached response for %@", uri
);
316 const void *respData
= sqlite3_column_blob(this->selectIssuer
, 0);
317 int respLen
= sqlite3_column_bytes(this->selectIssuer
, 0);
318 certificate
= SecCertificateCreateWithBytes(NULL
, respData
, respLen
);
321 require_noerr(s3e
= sec_sqlite3_reset(this->selectIssuer
, s3e
), errOut
);
325 if (s3e
!= SQLITE_DONE
) {
326 caissuerErrorLog("caissuer cache lookup failed: %s", sqlite3_errmsg(this->s3h
));
327 /* TODO: Blow away the cache and create a new db. */
331 CFRelease(certificate
);
336 secdebug("caissuercache", "returning %s for %@", (certificate
? "cached response" : "NULL"), uri
);
341 static void _SecCAIssuerCacheGC(void *context
) {
342 SecCAIssuerCacheRef
this = context
;
345 require_noerr(s3e
= SecCAIssuerCacheEnsureTxn(this), errOut
);
346 secdebug("caissuercache", "expiring stale responses");
347 s3e
= sqlite3_bind_double(this->expire
, 1, CFAbsoluteTimeGetCurrent());
348 if (!s3e
) s3e
= sqlite3_step(this->expire
);
349 require_noerr(s3e
= sec_sqlite3_reset(this->expire
, s3e
), errOut
);
350 require_noerr(s3e
= SecCAIssuerCacheCommitTxn(this), errOut
);
354 caissuerErrorLog("caissuer cache expire failed: %s", sqlite3_errmsg(this->s3h
));
355 /* TODO: Blow away the cache and create a new db. */
359 static void _SecCAIssuerCacheFlush(void *context
) {
360 SecCAIssuerCacheRef
this = context
;
363 secdebug("caissuercache", "flushing pending changes");
364 s3e
= SecCAIssuerCacheCommitTxn(this);
367 caissuerErrorLog("caissuer cache flush failed: %s", sqlite3_errmsg(this->s3h
));
368 /* TODO: Blow away the cache and create a new db. */
374 void SecCAIssuerCacheAddCertificate(SecCertificateRef certificate
,
375 CFURLRef uri
, CFAbsoluteTime expires
) {
376 dispatch_once(&kSecCAIssuerCacheOnce
, ^{
377 SecCAIssuerCacheInit();
379 if (!kSecCAIssuerCache
)
382 dispatch_sync(kSecCAIssuerCache
->queue
, ^{
383 _SecCAIssuerCacheAddCertificate(kSecCAIssuerCache
, certificate
, uri
, expires
);
387 SecCertificateRef
SecCAIssuerCacheCopyMatching(CFURLRef uri
) {
388 dispatch_once(&kSecCAIssuerCacheOnce
, ^{
389 SecCAIssuerCacheInit();
391 __block SecCertificateRef cert
= NULL
;
392 if (kSecCAIssuerCache
)
393 dispatch_sync(kSecCAIssuerCache
->queue
, ^{
394 cert
= _SecCAIssuerCacheCopyMatching(kSecCAIssuerCache
, uri
);
399 /* This should be called on a normal non emergency exit. This function
400 effectively does a SecCAIssuerCacheFlush.
401 Currently this is called from our atexit handeler.
402 This function expires any records that are stale and commits.
404 Idea for future cache management policies:
405 Expire old cache entires from database if:
406 - The time to do so has arrived based on the nextExpire date in the
408 - If the size of the database exceeds the limit set in the maxSize field
409 in the policy table, vacuum the db. If the database is still too
410 big, expire records on a LRU basis.
412 void SecCAIssuerCacheGC(void) {
413 if (kSecCAIssuerCache
)
414 dispatch_sync(kSecCAIssuerCache
->queue
, ^{
415 _SecCAIssuerCacheGC(kSecCAIssuerCache
);
419 /* Call this periodically or perhaps when we are exiting due to low memory. */
420 void SecCAIssuerCacheFlush(void) {
421 if (kSecCAIssuerCache
)
422 dispatch_sync(kSecCAIssuerCache
->queue
, ^{
423 _SecCAIssuerCacheFlush(kSecCAIssuerCache
);