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) {
252 WithPathInKeychainDirectory(CFSTR(kSecCAIssuerFileName
), ^(const char *utf8String
) {
253 kSecCAIssuerCache
= SecCAIssuerCacheCreate(utf8String
);
256 if (kSecCAIssuerCache
)
257 atexit(SecCAIssuerCacheGC
);
260 /* Instance implemenation. */
262 static void _SecCAIssuerCacheAddCertificate(SecCAIssuerCacheRef
this,
263 SecCertificateRef certificate
,
264 CFURLRef uri
, CFAbsoluteTime expires
) {
267 secdebug("caissuercache", "adding certificate from %@", uri
);
268 require_noerr(s3e
= SecCAIssuerCacheEnsureTxn(this), errOut
);
272 require_action(uriData
= CFURLCreateData(kCFAllocatorDefault
, uri
,
273 kCFStringEncodingUTF8
, false), errOut
, s3e
= SQLITE_NOMEM
);
274 s3e
= sqlite3_bind_blob_wrapper(this->insertIssuer
, 1,
275 CFDataGetBytePtr(uriData
), CFDataGetLength(uriData
), SQLITE_TRANSIENT
);
279 if (!s3e
) s3e
= sqlite3_bind_double(this->insertIssuer
, 2, expires
);
281 /* issuer.certificate */
282 if (!s3e
) s3e
= sqlite3_bind_blob_wrapper(this->insertIssuer
, 3,
283 SecCertificateGetBytePtr(certificate
),
284 SecCertificateGetLength(certificate
), SQLITE_TRANSIENT
);
286 /* Execute the insert statement. */
287 if (!s3e
) s3e
= sqlite3_step(this->insertIssuer
);
288 require_noerr(s3e
= sec_sqlite3_reset(this->insertIssuer
, s3e
), errOut
);
292 secerror("caissuer cache add failed: %s", sqlite3_errmsg(this->s3h
));
293 /* TODO: Blow away the cache and create a new db. */
297 static SecCertificateRef
_SecCAIssuerCacheCopyMatching(SecCAIssuerCacheRef
this,
299 SecCertificateRef certificate
= NULL
;
302 CFDataRef uriData
= NULL
;
303 require(uriData
= CFURLCreateData(kCFAllocatorDefault
, uri
,
304 kCFStringEncodingUTF8
, false), errOut
);
305 s3e
= sqlite3_bind_blob_wrapper(this->selectIssuer
, 1, CFDataGetBytePtr(uriData
),
306 CFDataGetLength(uriData
), SQLITE_TRANSIENT
);
309 if (!s3e
) s3e
= sqlite3_step(this->selectIssuer
);
310 if (s3e
== SQLITE_ROW
) {
311 /* Found an entry! */
312 secdebug("caissuercache", "found cached response for %@", uri
);
314 const void *respData
= sqlite3_column_blob(this->selectIssuer
, 0);
315 int respLen
= sqlite3_column_bytes(this->selectIssuer
, 0);
316 certificate
= SecCertificateCreateWithBytes(NULL
, respData
, respLen
);
319 require_noerr(s3e
= sec_sqlite3_reset(this->selectIssuer
, s3e
), errOut
);
323 if (s3e
!= SQLITE_DONE
) {
324 secerror("caissuer cache lookup failed: %s", sqlite3_errmsg(this->s3h
));
325 /* TODO: Blow away the cache and create a new db. */
329 CFRelease(certificate
);
334 secdebug("caissuercache", "returning %s for %@", (certificate
? "cached response" : "NULL"), uri
);
339 static void _SecCAIssuerCacheGC(void *context
) {
340 SecCAIssuerCacheRef
this = context
;
343 require_noerr(s3e
= SecCAIssuerCacheEnsureTxn(this), errOut
);
344 secdebug("caissuercache", "expiring stale responses");
345 s3e
= sqlite3_bind_double(this->expire
, 1, CFAbsoluteTimeGetCurrent());
346 if (!s3e
) s3e
= sqlite3_step(this->expire
);
347 require_noerr(s3e
= sec_sqlite3_reset(this->expire
, s3e
), errOut
);
348 require_noerr(s3e
= SecCAIssuerCacheCommitTxn(this), errOut
);
352 secerror("caissuer cache expire failed: %s", sqlite3_errmsg(this->s3h
));
353 /* TODO: Blow away the cache and create a new db. */
357 static void _SecCAIssuerCacheFlush(void *context
) {
358 SecCAIssuerCacheRef
this = context
;
361 secdebug("caissuercache", "flushing pending changes");
362 s3e
= SecCAIssuerCacheCommitTxn(this);
365 secerror("caissuer cache flush failed: %s", sqlite3_errmsg(this->s3h
));
366 /* TODO: Blow away the cache and create a new db. */
372 void SecCAIssuerCacheAddCertificate(SecCertificateRef certificate
,
373 CFURLRef uri
, CFAbsoluteTime expires
) {
374 dispatch_once(&kSecCAIssuerCacheOnce
, ^{
375 SecCAIssuerCacheInit();
377 if (!kSecCAIssuerCache
)
380 dispatch_sync(kSecCAIssuerCache
->queue
, ^{
381 _SecCAIssuerCacheAddCertificate(kSecCAIssuerCache
, certificate
, uri
, expires
);
385 SecCertificateRef
SecCAIssuerCacheCopyMatching(CFURLRef uri
) {
386 dispatch_once(&kSecCAIssuerCacheOnce
, ^{
387 SecCAIssuerCacheInit();
389 __block SecCertificateRef cert
= NULL
;
390 if (kSecCAIssuerCache
)
391 dispatch_sync(kSecCAIssuerCache
->queue
, ^{
392 cert
= _SecCAIssuerCacheCopyMatching(kSecCAIssuerCache
, uri
);
397 /* This should be called on a normal non emergency exit. This function
398 effectively does a SecCAIssuerCacheFlush.
399 Currently this is called from our atexit handeler.
400 This function expires any records that are stale and commits.
402 Idea for future cache management policies:
403 Expire old cache entires from database if:
404 - The time to do so has arrived based on the nextExpire date in the
406 - If the size of the database exceeds the limit set in the maxSize field
407 in the policy table, vacuum the db. If the database is still too
408 big, expire records on a LRU basis.
410 void SecCAIssuerCacheGC(void) {
411 if (kSecCAIssuerCache
)
412 dispatch_sync(kSecCAIssuerCache
->queue
, ^{
413 _SecCAIssuerCacheGC(kSecCAIssuerCache
);
417 /* Call this periodically or perhaps when we are exiting due to low memory. */
418 void SecCAIssuerCacheFlush(void) {
419 if (kSecCAIssuerCache
)
420 dispatch_sync(kSecCAIssuerCache
->queue
, ^{
421 _SecCAIssuerCacheFlush(kSecCAIssuerCache
);