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 "trust/trustd/SecCAIssuerCache.h"
30 #include "trust/trustd/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>
50 #include <utilities/SecCFWrappers.h>
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 secerror("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
);
242 if (s3e
!= SQLITE_OK
) {
243 TrustdHealthAnalyticsLogErrorCodeForDatabase(TACAIssuerCache
, TAOperationCreate
, TAFatalError
, s3e
);
247 dispatch_release(this->queue
);
249 sqlite3_close(this->s3h
);
256 static void SecCAIssuerCacheInit(void) {
258 WithPathInKeychainDirectory(CFSTR(kSecCAIssuerFileName
), ^(const char *utf8String
) {
259 kSecCAIssuerCache
= SecCAIssuerCacheCreate(utf8String
);
262 /* macOS caches should be in user cache dir */
263 WithPathInUserCacheDirectory(CFSTR(kSecCAIssuerFileName
), ^(const char *utf8String
) {
264 kSecCAIssuerCache
= SecCAIssuerCacheCreate(utf8String
);
268 if (kSecCAIssuerCache
)
269 atexit(SecCAIssuerCacheGC
);
272 static CF_RETURNS_RETAINED CFDataRef
convertArrayOfCertsToData(CFArrayRef certificates
) {
273 if (!certificates
|| CFArrayGetCount(certificates
) == 0) {
277 CFMutableDataRef output
= CFDataCreateMutable(NULL
, 0);
278 CFArrayForEach(certificates
, ^(const void *value
) {
279 CFDataRef certData
= SecCertificateCopyData((SecCertificateRef
)value
);
281 CFDataAppend(output
, certData
);
283 CFReleaseNull(certData
);
289 static CF_RETURNS_RETAINED CFArrayRef
convertDataToArrayOfCerts(uint8_t *data
, size_t dataLen
) {
290 if (!data
|| dataLen
== 0) {
294 CFMutableArrayRef output
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
);
295 uint8_t *nextCertPtr
= data
;
296 size_t remainingDataLen
= dataLen
;
297 while (nextCertPtr
< data
+ dataLen
) {
298 SecCertificateRef cert
= SecCertificateCreateWithBytes(NULL
, nextCertPtr
, remainingDataLen
);
300 CFArrayAppendValue(output
, cert
);
301 nextCertPtr
+= SecCertificateGetLength(cert
);
302 remainingDataLen
-= SecCertificateGetLength(cert
);
305 /* We don't know where the next cert starts, so we should just stop */
310 if (CFArrayGetCount(output
) < 1) {
311 CFReleaseNull(output
);
316 /* Instance implemenation. */
318 static void _SecCAIssuerCacheAddCertificates(SecCAIssuerCacheRef
this,
319 CFArrayRef certificates
,
320 CFURLRef uri
, CFAbsoluteTime expires
) {
322 CFDataRef certsData
= NULL
;
323 CFDataRef uriData
= NULL
;
325 secdebug("caissuercache", "adding certificate from %@", uri
);
326 require_noerr(s3e
= SecCAIssuerCacheEnsureTxn(this), errOut
);
329 require_action(uriData
= CFURLCreateData(kCFAllocatorDefault
, uri
,
330 kCFStringEncodingUTF8
, false), errOut
, s3e
= SQLITE_NOMEM
);
331 s3e
= sqlite3_bind_blob_wrapper(this->insertIssuer
, 1,
332 CFDataGetBytePtr(uriData
), CFDataGetLength(uriData
), SQLITE_TRANSIENT
);
336 if (!s3e
) s3e
= sqlite3_bind_double(this->insertIssuer
, 2, expires
);
338 /* issuer.certificate */
339 require_action(certsData
= convertArrayOfCertsToData(certificates
), errOut
,
342 s3e
= sqlite3_bind_blob_wrapper(this->insertIssuer
, 3,
343 CFDataGetBytePtr(certsData
),
344 CFDataGetLength(certsData
), SQLITE_TRANSIENT
);
346 CFReleaseNull(certsData
);
348 /* Execute the insert statement. */
349 if (!s3e
) s3e
= sqlite3_step(this->insertIssuer
);
350 require_noerr(s3e
= sec_sqlite3_reset(this->insertIssuer
, s3e
), errOut
);
353 if (s3e
!= SQLITE_OK
) {
354 secerror("caissuer cache add failed: %s", sqlite3_errmsg(this->s3h
));
355 TrustdHealthAnalyticsLogErrorCodeForDatabase(TACAIssuerCache
, TAOperationWrite
, TAFatalError
, s3e
);
356 /* TODO: Blow away the cache and create a new db. */
360 static CFArrayRef
_SecCAIssuerCacheCopyMatching(SecCAIssuerCacheRef
this,
362 CFArrayRef certificates
= NULL
;
365 CFDataRef uriData
= NULL
;
366 require(uriData
= CFURLCreateData(kCFAllocatorDefault
, uri
,
367 kCFStringEncodingUTF8
, false), errOut
);
368 s3e
= sqlite3_bind_blob_wrapper(this->selectIssuer
, 1, CFDataGetBytePtr(uriData
),
369 CFDataGetLength(uriData
), SQLITE_TRANSIENT
);
372 if (!s3e
) s3e
= sqlite3_step(this->selectIssuer
);
373 if (s3e
== SQLITE_ROW
) {
374 /* Found an entry! */
375 secdebug("caissuercache", "found cached response for %@", uri
);
377 const void *respData
= sqlite3_column_blob(this->selectIssuer
, 0);
378 int respLen
= sqlite3_column_bytes(this->selectIssuer
, 0);
379 certificates
= convertDataToArrayOfCerts((uint8_t *)respData
, respLen
);
382 require_noerr(s3e
= sec_sqlite3_reset(this->selectIssuer
, s3e
), errOut
);
385 if (s3e
!= SQLITE_OK
) {
386 if (s3e
!= SQLITE_DONE
) {
387 secerror("caissuer cache lookup failed: %s", sqlite3_errmsg(this->s3h
));
388 TrustdHealthAnalyticsLogErrorCodeForDatabase(TACAIssuerCache
, TAOperationRead
, TAFatalError
, s3e
);
389 /* TODO: Blow away the cache and create a new db. */
393 CFRelease(certificates
);
398 secdebug("caissuercache", "returning %s for %@", (certificates
? "cached response" : "NULL"), uri
);
402 static void _SecCAIssuerCacheGC(void *context
) {
403 SecCAIssuerCacheRef
this = context
;
406 require_noerr(s3e
= SecCAIssuerCacheEnsureTxn(this), errOut
);
407 secdebug("caissuercache", "expiring stale responses");
408 s3e
= sqlite3_bind_double(this->expire
, 1, CFAbsoluteTimeGetCurrent());
409 if (!s3e
) s3e
= sqlite3_step(this->expire
);
410 require_noerr(s3e
= sec_sqlite3_reset(this->expire
, s3e
), errOut
);
411 require_noerr(s3e
= SecCAIssuerCacheCommitTxn(this), errOut
);
414 if (s3e
!= SQLITE_OK
) {
415 secerror("caissuer cache expire failed: %s", sqlite3_errmsg(this->s3h
));
416 TrustdHealthAnalyticsLogErrorCodeForDatabase(TACAIssuerCache
, TAOperationWrite
, TAFatalError
, s3e
);
417 /* TODO: Blow away the cache and create a new db. */
421 static void _SecCAIssuerCacheFlush(void *context
) {
422 SecCAIssuerCacheRef
this = context
;
425 secdebug("caissuercache", "flushing pending changes");
426 s3e
= SecCAIssuerCacheCommitTxn(this);
428 if (s3e
!= SQLITE_OK
) {
429 secerror("caissuer cache flush failed: %s", sqlite3_errmsg(this->s3h
));
430 TrustdHealthAnalyticsLogErrorCodeForDatabase(TACAIssuerCache
, TAOperationWrite
, TAFatalError
, s3e
);
431 /* TODO: Blow away the cache and create a new db. */
437 void SecCAIssuerCacheAddCertificates(CFArrayRef certificates
,
438 CFURLRef uri
, CFAbsoluteTime expires
) {
439 dispatch_once(&kSecCAIssuerCacheOnce
, ^{
440 SecCAIssuerCacheInit();
442 if (!kSecCAIssuerCache
)
445 dispatch_sync(kSecCAIssuerCache
->queue
, ^{
446 _SecCAIssuerCacheAddCertificates(kSecCAIssuerCache
, certificates
, uri
, expires
);
447 _SecCAIssuerCacheFlush(kSecCAIssuerCache
);
451 CFArrayRef
SecCAIssuerCacheCopyMatching(CFURLRef uri
) {
452 dispatch_once(&kSecCAIssuerCacheOnce
, ^{
453 SecCAIssuerCacheInit();
455 __block CFArrayRef certs
= NULL
;
456 if (kSecCAIssuerCache
)
457 dispatch_sync(kSecCAIssuerCache
->queue
, ^{
458 certs
= _SecCAIssuerCacheCopyMatching(kSecCAIssuerCache
, uri
);
463 /* This should be called on a normal non emergency exit. This function
464 effectively does a SecCAIssuerCacheFlush.
465 Currently this is called from our atexit handeler.
466 This function expires any records that are stale and commits.
468 Idea for future cache management policies:
469 Expire old cache entires from database if:
470 - The time to do so has arrived based on the nextExpire date in the
472 - If the size of the database exceeds the limit set in the maxSize field
473 in the policy table, vacuum the db. If the database is still too
474 big, expire records on a LRU basis.
476 void SecCAIssuerCacheGC(void) {
477 if (kSecCAIssuerCache
)
478 dispatch_sync(kSecCAIssuerCache
->queue
, ^{
479 _SecCAIssuerCacheGC(kSecCAIssuerCache
);