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 <securityd/SecTrustLoggingServer.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 static const char expireSQL
[] = "DELETE FROM issuers WHERE expires<?";
52 static const char beginTxnSQL
[] = "BEGIN EXCLUSIVE TRANSACTION";
53 static const char endTxnSQL
[] = "COMMIT TRANSACTION";
54 static const char insertIssuerSQL
[] = "INSERT OR REPLACE INTO issuers "
55 "(uri,expires,certificate) VALUES (?,?,?)";
56 static const char selectIssuerSQL
[] = "SELECT certificate FROM "
57 "issuers WHERE uri=?";
59 #define kSecCAIssuerFileName "caissuercache.sqlite3"
61 typedef struct __SecCAIssuerCache
*SecCAIssuerCacheRef
;
62 struct __SecCAIssuerCache
{
63 dispatch_queue_t queue
;
66 sqlite3_stmt
*beginTxn
;
68 sqlite3_stmt
*insertIssuer
;
69 sqlite3_stmt
*selectIssuer
;
73 static dispatch_once_t kSecCAIssuerCacheOnce
;
74 static SecCAIssuerCacheRef kSecCAIssuerCache
;
76 /* @@@ Duplicated from SecTrustStore.c */
77 static int sec_create_path(const char *path
)
79 char pathbuf
[PATH_MAX
];
80 size_t pos
, len
= strlen(path
);
81 if (len
== 0 || len
> PATH_MAX
)
82 return SQLITE_CANTOPEN
;
83 memcpy(pathbuf
, path
, len
);
84 for (pos
= len
-1; pos
> 0; --pos
)
86 /* Search backwards for trailing '/'. */
87 if (pathbuf
[pos
] == '/')
90 /* Attempt to create parent directories of the database. */
91 if (!mkdir(pathbuf
, 0777))
99 return SQLITE_CANTOPEN
;
101 return SQLITE_READONLY
;
104 if (err
== ENOSPC
|| err
== EDQUOT
)
109 /* EFAULT || ELOOP | ENAMETOOLONG || something else */
110 return SQLITE_INTERNAL
;
117 static int sec_sqlite3_open(const char *db_name
, sqlite3
**s3h
,
121 s3e
= sqlite3_open(db_name
, s3h
);
122 if (s3e
== SQLITE_CANTOPEN
&& create_path
) {
123 /* Make sure the path to db_name exists and is writable, then
125 s3e
= sec_create_path(db_name
);
127 s3e
= sqlite3_open(db_name
, s3h
);
133 static int sec_sqlite3_reset(sqlite3_stmt
*stmt
, int s3e
) {
135 if (s3e
== SQLITE_ROW
|| s3e
== SQLITE_DONE
)
137 s3e2
= sqlite3_reset(stmt
);
140 s3e2
= sqlite3_clear_bindings(stmt
);
146 static int SecCAIssuerCacheEnsureTxn(SecCAIssuerCacheRef
this) {
149 if (this->in_transaction
)
152 s3e
= sqlite3_step(this->beginTxn
);
153 if (s3e
== SQLITE_DONE
) {
154 this->in_transaction
= true;
157 secdebug("caissuercache", "sqlite3_step returned [%d]: %s", s3e
,
158 sqlite3_errmsg(this->s3h
));
160 s3e2
= sqlite3_reset(this->beginTxn
);
167 static int SecCAIssuerCacheCommitTxn(SecCAIssuerCacheRef
this) {
170 if (!this->in_transaction
)
173 s3e
= sqlite3_step(this->endTxn
);
174 if (s3e
== SQLITE_DONE
) {
175 this->in_transaction
= false;
178 secdebug("caissuercache", "sqlite3_step returned [%d]: %s", s3e
,
179 sqlite3_errmsg(this->s3h
));
181 s3e2
= sqlite3_reset(this->endTxn
);
188 static SecCAIssuerCacheRef
SecCAIssuerCacheCreate(const char *db_name
) {
189 SecCAIssuerCacheRef
this;
193 require(this = (SecCAIssuerCacheRef
)calloc(sizeof(struct __SecCAIssuerCache
), 1), errOut
);
194 require_action_quiet((this->queue
= dispatch_queue_create("caissuercache", 0)), errOut
, s3e
= errSecAllocate
);
195 require_noerr(s3e
= sec_sqlite3_open(db_name
, &this->s3h
, create
), errOut
);
196 this->in_transaction
= false;
198 s3e
= sqlite3_prepare_v2(this->s3h
, beginTxnSQL
, sizeof(beginTxnSQL
),
199 &this->beginTxn
, NULL
);
200 require_noerr(s3e
, errOut
);
201 s3e
= sqlite3_prepare_v2(this->s3h
, endTxnSQL
, sizeof(endTxnSQL
),
202 &this->endTxn
, NULL
);
203 require_noerr(s3e
, errOut
);
205 s3e
= sqlite3_prepare_v2(this->s3h
, expireSQL
, sizeof(expireSQL
),
206 &this->expire
, NULL
);
207 if (create
&& s3e
== SQLITE_ERROR
) {
208 s3e
= SecCAIssuerCacheEnsureTxn(this);
209 require_noerr(s3e
, errOut
);
211 /* sqlite3_prepare returns SQLITE_ERROR if the table we are
212 compiling this statement for doesn't exist. */
214 s3e
= sqlite3_exec(this->s3h
,
215 "CREATE TABLE issuers("
216 "uri BLOB PRIMARY KEY,"
217 "expires DOUBLE NOT NULL,"
218 "certificate BLOB NOT NULL"
220 "CREATE INDEX iexpires ON issuers(expires);"
221 , NULL
, NULL
, &errmsg
);
223 secerror("caissuer db CREATE TABLES: %s", errmsg
);
224 sqlite3_free(errmsg
);
226 require_noerr(s3e
, errOut
);
227 s3e
= sqlite3_prepare_v2(this->s3h
, expireSQL
, sizeof(expireSQL
),
228 &this->expire
, NULL
);
230 require_noerr(s3e
, errOut
);
231 s3e
= sqlite3_prepare_v2(this->s3h
, insertIssuerSQL
, sizeof(insertIssuerSQL
),
232 &this->insertIssuer
, NULL
);
233 require_noerr(s3e
, errOut
);
234 s3e
= sqlite3_prepare_v2(this->s3h
, selectIssuerSQL
, sizeof(selectIssuerSQL
),
235 &this->selectIssuer
, NULL
);
236 require_noerr(s3e
, errOut
);
241 if (s3e
!= SQLITE_OK
) {
242 TrustdHealthAnalyticsLogErrorCodeForDatabase(TACAIssuerCache
, TAOperationCreate
, TAFatalError
, s3e
);
246 dispatch_release(this->queue
);
248 sqlite3_close(this->s3h
);
255 static void SecCAIssuerCacheInit(void) {
257 WithPathInKeychainDirectory(CFSTR(kSecCAIssuerFileName
), ^(const char *utf8String
) {
258 kSecCAIssuerCache
= SecCAIssuerCacheCreate(utf8String
);
261 /* macOS caches should be in user cache dir */
262 WithPathInUserCacheDirectory(CFSTR(kSecCAIssuerFileName
), ^(const char *utf8String
) {
263 kSecCAIssuerCache
= SecCAIssuerCacheCreate(utf8String
);
267 if (kSecCAIssuerCache
)
268 atexit(SecCAIssuerCacheGC
);
271 /* Instance implemenation. */
273 static void _SecCAIssuerCacheAddCertificate(SecCAIssuerCacheRef
this,
274 SecCertificateRef certificate
,
275 CFURLRef uri
, CFAbsoluteTime expires
) {
278 secdebug("caissuercache", "adding certificate from %@", uri
);
279 require_noerr(s3e
= SecCAIssuerCacheEnsureTxn(this), errOut
);
283 require_action(uriData
= CFURLCreateData(kCFAllocatorDefault
, uri
,
284 kCFStringEncodingUTF8
, false), errOut
, s3e
= SQLITE_NOMEM
);
285 s3e
= sqlite3_bind_blob_wrapper(this->insertIssuer
, 1,
286 CFDataGetBytePtr(uriData
), CFDataGetLength(uriData
), SQLITE_TRANSIENT
);
290 if (!s3e
) s3e
= sqlite3_bind_double(this->insertIssuer
, 2, expires
);
292 /* issuer.certificate */
293 if (!s3e
) s3e
= sqlite3_bind_blob_wrapper(this->insertIssuer
, 3,
294 SecCertificateGetBytePtr(certificate
),
295 SecCertificateGetLength(certificate
), SQLITE_TRANSIENT
);
297 /* Execute the insert statement. */
298 if (!s3e
) s3e
= sqlite3_step(this->insertIssuer
);
299 require_noerr(s3e
= sec_sqlite3_reset(this->insertIssuer
, s3e
), errOut
);
302 if (s3e
!= SQLITE_OK
) {
303 secerror("caissuer cache add failed: %s", sqlite3_errmsg(this->s3h
));
304 TrustdHealthAnalyticsLogErrorCodeForDatabase(TACAIssuerCache
, TAOperationWrite
, TAFatalError
, s3e
);
305 /* TODO: Blow away the cache and create a new db. */
309 static SecCertificateRef
_SecCAIssuerCacheCopyMatching(SecCAIssuerCacheRef
this,
311 SecCertificateRef certificate
= NULL
;
314 CFDataRef uriData
= NULL
;
315 require(uriData
= CFURLCreateData(kCFAllocatorDefault
, uri
,
316 kCFStringEncodingUTF8
, false), errOut
);
317 s3e
= sqlite3_bind_blob_wrapper(this->selectIssuer
, 1, CFDataGetBytePtr(uriData
),
318 CFDataGetLength(uriData
), SQLITE_TRANSIENT
);
321 if (!s3e
) s3e
= sqlite3_step(this->selectIssuer
);
322 if (s3e
== SQLITE_ROW
) {
323 /* Found an entry! */
324 secdebug("caissuercache", "found cached response for %@", uri
);
326 const void *respData
= sqlite3_column_blob(this->selectIssuer
, 0);
327 int respLen
= sqlite3_column_bytes(this->selectIssuer
, 0);
328 certificate
= SecCertificateCreateWithBytes(NULL
, respData
, respLen
);
331 require_noerr(s3e
= sec_sqlite3_reset(this->selectIssuer
, s3e
), errOut
);
334 if (s3e
!= SQLITE_OK
) {
335 if (s3e
!= SQLITE_DONE
) {
336 secerror("caissuer cache lookup failed: %s", sqlite3_errmsg(this->s3h
));
337 TrustdHealthAnalyticsLogErrorCodeForDatabase(TACAIssuerCache
, TAOperationRead
, TAFatalError
, s3e
);
338 /* TODO: Blow away the cache and create a new db. */
342 CFRelease(certificate
);
347 secdebug("caissuercache", "returning %s for %@", (certificate
? "cached response" : "NULL"), uri
);
352 static void _SecCAIssuerCacheGC(void *context
) {
353 SecCAIssuerCacheRef
this = context
;
356 require_noerr(s3e
= SecCAIssuerCacheEnsureTxn(this), errOut
);
357 secdebug("caissuercache", "expiring stale responses");
358 s3e
= sqlite3_bind_double(this->expire
, 1, CFAbsoluteTimeGetCurrent());
359 if (!s3e
) s3e
= sqlite3_step(this->expire
);
360 require_noerr(s3e
= sec_sqlite3_reset(this->expire
, s3e
), errOut
);
361 require_noerr(s3e
= SecCAIssuerCacheCommitTxn(this), errOut
);
364 if (s3e
!= SQLITE_OK
) {
365 secerror("caissuer cache expire failed: %s", sqlite3_errmsg(this->s3h
));
366 TrustdHealthAnalyticsLogErrorCodeForDatabase(TACAIssuerCache
, TAOperationWrite
, TAFatalError
, s3e
);
367 /* TODO: Blow away the cache and create a new db. */
371 static void _SecCAIssuerCacheFlush(void *context
) {
372 SecCAIssuerCacheRef
this = context
;
375 secdebug("caissuercache", "flushing pending changes");
376 s3e
= SecCAIssuerCacheCommitTxn(this);
378 if (s3e
!= SQLITE_OK
) {
379 secerror("caissuer cache flush failed: %s", sqlite3_errmsg(this->s3h
));
380 TrustdHealthAnalyticsLogErrorCodeForDatabase(TACAIssuerCache
, TAOperationWrite
, TAFatalError
, s3e
);
381 /* TODO: Blow away the cache and create a new db. */
387 void SecCAIssuerCacheAddCertificate(SecCertificateRef certificate
,
388 CFURLRef uri
, CFAbsoluteTime expires
) {
389 dispatch_once(&kSecCAIssuerCacheOnce
, ^{
390 SecCAIssuerCacheInit();
392 if (!kSecCAIssuerCache
)
395 dispatch_sync(kSecCAIssuerCache
->queue
, ^{
396 _SecCAIssuerCacheAddCertificate(kSecCAIssuerCache
, certificate
, uri
, expires
);
400 SecCertificateRef
SecCAIssuerCacheCopyMatching(CFURLRef uri
) {
401 dispatch_once(&kSecCAIssuerCacheOnce
, ^{
402 SecCAIssuerCacheInit();
404 __block SecCertificateRef cert
= NULL
;
405 if (kSecCAIssuerCache
)
406 dispatch_sync(kSecCAIssuerCache
->queue
, ^{
407 cert
= _SecCAIssuerCacheCopyMatching(kSecCAIssuerCache
, uri
);
412 /* This should be called on a normal non emergency exit. This function
413 effectively does a SecCAIssuerCacheFlush.
414 Currently this is called from our atexit handeler.
415 This function expires any records that are stale and commits.
417 Idea for future cache management policies:
418 Expire old cache entires from database if:
419 - The time to do so has arrived based on the nextExpire date in the
421 - If the size of the database exceeds the limit set in the maxSize field
422 in the policy table, vacuum the db. If the database is still too
423 big, expire records on a LRU basis.
425 void SecCAIssuerCacheGC(void) {
426 if (kSecCAIssuerCache
)
427 dispatch_sync(kSecCAIssuerCache
->queue
, ^{
428 _SecCAIssuerCacheGC(kSecCAIssuerCache
);
432 /* Call this periodically or perhaps when we are exiting due to low memory. */
433 void SecCAIssuerCacheFlush(void) {
434 if (kSecCAIssuerCache
)
435 dispatch_sync(kSecCAIssuerCache
->queue
, ^{
436 _SecCAIssuerCacheFlush(kSecCAIssuerCache
);