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 static const char expireSQL
[] = "DELETE FROM issuers WHERE expires<?";
51 static const char beginTxnSQL
[] = "BEGIN EXCLUSIVE TRANSACTION";
52 static const char endTxnSQL
[] = "COMMIT TRANSACTION";
53 static const char insertIssuerSQL
[] = "INSERT OR REPLACE INTO issuers "
54 "(uri,expires,certificate) VALUES (?,?,?)";
55 static const char selectIssuerSQL
[] = "SELECT certificate FROM "
56 "issuers WHERE uri=?";
58 #define kSecCAIssuerFileName "caissuercache.sqlite3"
60 typedef struct __SecCAIssuerCache
*SecCAIssuerCacheRef
;
61 struct __SecCAIssuerCache
{
62 dispatch_queue_t queue
;
65 sqlite3_stmt
*beginTxn
;
67 sqlite3_stmt
*insertIssuer
;
68 sqlite3_stmt
*selectIssuer
;
72 static dispatch_once_t kSecCAIssuerCacheOnce
;
73 static SecCAIssuerCacheRef kSecCAIssuerCache
;
75 /* @@@ Duplicated from SecTrustStore.c */
76 static int sec_create_path(const char *path
)
78 char pathbuf
[PATH_MAX
];
79 size_t pos
, len
= strlen(path
);
80 if (len
== 0 || len
> PATH_MAX
)
81 return SQLITE_CANTOPEN
;
82 memcpy(pathbuf
, path
, len
);
83 for (pos
= len
-1; pos
> 0; --pos
)
85 /* Search backwards for trailing '/'. */
86 if (pathbuf
[pos
] == '/')
89 /* Attempt to create parent directories of the database. */
90 if (!mkdir(pathbuf
, 0777))
98 return SQLITE_CANTOPEN
;
100 return SQLITE_READONLY
;
103 if (err
== ENOSPC
|| err
== EDQUOT
)
108 /* EFAULT || ELOOP | ENAMETOOLONG || something else */
109 return SQLITE_INTERNAL
;
116 static int sec_sqlite3_open(const char *db_name
, sqlite3
**s3h
,
120 s3e
= sqlite3_open(db_name
, s3h
);
121 if (s3e
== SQLITE_CANTOPEN
&& create_path
) {
122 /* Make sure the path to db_name exists and is writable, then
124 s3e
= sec_create_path(db_name
);
126 s3e
= sqlite3_open(db_name
, s3h
);
132 static int sec_sqlite3_reset(sqlite3_stmt
*stmt
, int s3e
) {
134 if (s3e
== SQLITE_ROW
|| s3e
== SQLITE_DONE
)
136 s3e2
= sqlite3_reset(stmt
);
139 s3e2
= sqlite3_clear_bindings(stmt
);
145 static int SecCAIssuerCacheEnsureTxn(SecCAIssuerCacheRef
this) {
148 if (this->in_transaction
)
151 s3e
= sqlite3_step(this->beginTxn
);
152 if (s3e
== SQLITE_DONE
) {
153 this->in_transaction
= true;
156 secdebug("caissuercache", "sqlite3_step returned [%d]: %s", s3e
,
157 sqlite3_errmsg(this->s3h
));
159 s3e2
= sqlite3_reset(this->beginTxn
);
166 static int SecCAIssuerCacheCommitTxn(SecCAIssuerCacheRef
this) {
169 if (!this->in_transaction
)
172 s3e
= sqlite3_step(this->endTxn
);
173 if (s3e
== SQLITE_DONE
) {
174 this->in_transaction
= false;
177 secdebug("caissuercache", "sqlite3_step returned [%d]: %s", s3e
,
178 sqlite3_errmsg(this->s3h
));
180 s3e2
= sqlite3_reset(this->endTxn
);
187 static SecCAIssuerCacheRef
SecCAIssuerCacheCreate(const char *db_name
) {
188 SecCAIssuerCacheRef
this;
192 require(this = (SecCAIssuerCacheRef
)calloc(sizeof(struct __SecCAIssuerCache
), 1), errOut
);
193 require_action_quiet((this->queue
= dispatch_queue_create("caissuercache", 0)), errOut
, s3e
= errSecAllocate
);
194 require_noerr(s3e
= sec_sqlite3_open(db_name
, &this->s3h
, create
), errOut
);
195 this->in_transaction
= false;
197 s3e
= sqlite3_prepare_v2(this->s3h
, beginTxnSQL
, sizeof(beginTxnSQL
),
198 &this->beginTxn
, NULL
);
199 require_noerr(s3e
, errOut
);
200 s3e
= sqlite3_prepare_v2(this->s3h
, endTxnSQL
, sizeof(endTxnSQL
),
201 &this->endTxn
, NULL
);
202 require_noerr(s3e
, errOut
);
204 s3e
= sqlite3_prepare_v2(this->s3h
, expireSQL
, sizeof(expireSQL
),
205 &this->expire
, NULL
);
206 if (create
&& s3e
== SQLITE_ERROR
) {
207 s3e
= SecCAIssuerCacheEnsureTxn(this);
208 require_noerr(s3e
, errOut
);
210 /* sqlite3_prepare returns SQLITE_ERROR if the table we are
211 compiling this statement for doesn't exist. */
213 s3e
= sqlite3_exec(this->s3h
,
214 "CREATE TABLE issuers("
215 "uri BLOB PRIMARY KEY,"
216 "expires DOUBLE NOT NULL,"
217 "certificate BLOB NOT NULL"
219 "CREATE INDEX iexpires ON issuers(expires);"
220 , NULL
, NULL
, &errmsg
);
222 secerror("caissuer db CREATE TABLES: %s", errmsg
);
223 sqlite3_free(errmsg
);
225 require_noerr(s3e
, errOut
);
226 s3e
= sqlite3_prepare_v2(this->s3h
, expireSQL
, sizeof(expireSQL
),
227 &this->expire
, NULL
);
229 require_noerr(s3e
, errOut
);
230 s3e
= sqlite3_prepare_v2(this->s3h
, insertIssuerSQL
, sizeof(insertIssuerSQL
),
231 &this->insertIssuer
, NULL
);
232 require_noerr(s3e
, errOut
);
233 s3e
= sqlite3_prepare_v2(this->s3h
, selectIssuerSQL
, sizeof(selectIssuerSQL
),
234 &this->selectIssuer
, NULL
);
235 require_noerr(s3e
, errOut
);
242 dispatch_release(this->queue
);
244 sqlite3_close(this->s3h
);
251 static void SecCAIssuerCacheInit(void) {
253 WithPathInKeychainDirectory(CFSTR(kSecCAIssuerFileName
), ^(const char *utf8String
) {
254 kSecCAIssuerCache
= SecCAIssuerCacheCreate(utf8String
);
257 /* macOS caches should be in user cache dir */
258 WithPathInUserCacheDirectory(CFSTR(kSecCAIssuerFileName
), ^(const char *utf8String
) {
259 kSecCAIssuerCache
= SecCAIssuerCacheCreate(utf8String
);
263 if (kSecCAIssuerCache
)
264 atexit(SecCAIssuerCacheGC
);
267 /* Instance implemenation. */
269 static void _SecCAIssuerCacheAddCertificate(SecCAIssuerCacheRef
this,
270 SecCertificateRef certificate
,
271 CFURLRef uri
, CFAbsoluteTime expires
) {
274 secdebug("caissuercache", "adding certificate from %@", uri
);
275 require_noerr(s3e
= SecCAIssuerCacheEnsureTxn(this), errOut
);
279 require_action(uriData
= CFURLCreateData(kCFAllocatorDefault
, uri
,
280 kCFStringEncodingUTF8
, false), errOut
, s3e
= SQLITE_NOMEM
);
281 s3e
= sqlite3_bind_blob_wrapper(this->insertIssuer
, 1,
282 CFDataGetBytePtr(uriData
), CFDataGetLength(uriData
), SQLITE_TRANSIENT
);
286 if (!s3e
) s3e
= sqlite3_bind_double(this->insertIssuer
, 2, expires
);
288 /* issuer.certificate */
289 if (!s3e
) s3e
= sqlite3_bind_blob_wrapper(this->insertIssuer
, 3,
290 SecCertificateGetBytePtr(certificate
),
291 SecCertificateGetLength(certificate
), SQLITE_TRANSIENT
);
293 /* Execute the insert statement. */
294 if (!s3e
) s3e
= sqlite3_step(this->insertIssuer
);
295 require_noerr(s3e
= sec_sqlite3_reset(this->insertIssuer
, s3e
), errOut
);
299 secerror("caissuer cache add failed: %s", sqlite3_errmsg(this->s3h
));
300 /* TODO: Blow away the cache and create a new db. */
304 static SecCertificateRef
_SecCAIssuerCacheCopyMatching(SecCAIssuerCacheRef
this,
306 SecCertificateRef certificate
= NULL
;
309 CFDataRef uriData
= NULL
;
310 require(uriData
= CFURLCreateData(kCFAllocatorDefault
, uri
,
311 kCFStringEncodingUTF8
, false), errOut
);
312 s3e
= sqlite3_bind_blob_wrapper(this->selectIssuer
, 1, CFDataGetBytePtr(uriData
),
313 CFDataGetLength(uriData
), SQLITE_TRANSIENT
);
316 if (!s3e
) s3e
= sqlite3_step(this->selectIssuer
);
317 if (s3e
== SQLITE_ROW
) {
318 /* Found an entry! */
319 secdebug("caissuercache", "found cached response for %@", uri
);
321 const void *respData
= sqlite3_column_blob(this->selectIssuer
, 0);
322 int respLen
= sqlite3_column_bytes(this->selectIssuer
, 0);
323 certificate
= SecCertificateCreateWithBytes(NULL
, respData
, respLen
);
326 require_noerr(s3e
= sec_sqlite3_reset(this->selectIssuer
, s3e
), errOut
);
330 if (s3e
!= SQLITE_DONE
) {
331 secerror("caissuer cache lookup failed: %s", sqlite3_errmsg(this->s3h
));
332 /* TODO: Blow away the cache and create a new db. */
336 CFRelease(certificate
);
341 secdebug("caissuercache", "returning %s for %@", (certificate
? "cached response" : "NULL"), uri
);
346 static void _SecCAIssuerCacheGC(void *context
) {
347 SecCAIssuerCacheRef
this = context
;
350 require_noerr(s3e
= SecCAIssuerCacheEnsureTxn(this), errOut
);
351 secdebug("caissuercache", "expiring stale responses");
352 s3e
= sqlite3_bind_double(this->expire
, 1, CFAbsoluteTimeGetCurrent());
353 if (!s3e
) s3e
= sqlite3_step(this->expire
);
354 require_noerr(s3e
= sec_sqlite3_reset(this->expire
, s3e
), errOut
);
355 require_noerr(s3e
= SecCAIssuerCacheCommitTxn(this), errOut
);
359 secerror("caissuer cache expire failed: %s", sqlite3_errmsg(this->s3h
));
360 /* TODO: Blow away the cache and create a new db. */
364 static void _SecCAIssuerCacheFlush(void *context
) {
365 SecCAIssuerCacheRef
this = context
;
368 secdebug("caissuercache", "flushing pending changes");
369 s3e
= SecCAIssuerCacheCommitTxn(this);
372 secerror("caissuer cache flush failed: %s", sqlite3_errmsg(this->s3h
));
373 /* TODO: Blow away the cache and create a new db. */
379 void SecCAIssuerCacheAddCertificate(SecCertificateRef certificate
,
380 CFURLRef uri
, CFAbsoluteTime expires
) {
381 dispatch_once(&kSecCAIssuerCacheOnce
, ^{
382 SecCAIssuerCacheInit();
384 if (!kSecCAIssuerCache
)
387 dispatch_sync(kSecCAIssuerCache
->queue
, ^{
388 _SecCAIssuerCacheAddCertificate(kSecCAIssuerCache
, certificate
, uri
, expires
);
392 SecCertificateRef
SecCAIssuerCacheCopyMatching(CFURLRef uri
) {
393 dispatch_once(&kSecCAIssuerCacheOnce
, ^{
394 SecCAIssuerCacheInit();
396 __block SecCertificateRef cert
= NULL
;
397 if (kSecCAIssuerCache
)
398 dispatch_sync(kSecCAIssuerCache
->queue
, ^{
399 cert
= _SecCAIssuerCacheCopyMatching(kSecCAIssuerCache
, uri
);
404 /* This should be called on a normal non emergency exit. This function
405 effectively does a SecCAIssuerCacheFlush.
406 Currently this is called from our atexit handeler.
407 This function expires any records that are stale and commits.
409 Idea for future cache management policies:
410 Expire old cache entires from database if:
411 - The time to do so has arrived based on the nextExpire date in the
413 - If the size of the database exceeds the limit set in the maxSize field
414 in the policy table, vacuum the db. If the database is still too
415 big, expire records on a LRU basis.
417 void SecCAIssuerCacheGC(void) {
418 if (kSecCAIssuerCache
)
419 dispatch_sync(kSecCAIssuerCache
->queue
, ^{
420 _SecCAIssuerCacheGC(kSecCAIssuerCache
);
424 /* Call this periodically or perhaps when we are exiting due to low memory. */
425 void SecCAIssuerCacheFlush(void) {
426 if (kSecCAIssuerCache
)
427 dispatch_sync(kSecCAIssuerCache
->queue
, ^{
428 _SecCAIssuerCacheFlush(kSecCAIssuerCache
);