2 * Copyright (c) 2011 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@
23 * Created by Michael Brouwer on 6/17/11.
27 * SecCAIssuerCache.c - securityd
30 #include <securityd/SecCAIssuerCache.h>
31 #include <security_utilities/debugging.h>
32 #include <Security/SecCertificateInternal.h>
33 #include <Security/SecFramework.h>
34 #include <Security/SecInternal.h>
36 #include <AssertMacros.h>
47 #define caissuerErrorLog(args...) asl_log(NULL, NULL, ASL_LEVEL_ERR, ## args)
49 static const char expireSQL
[] = "DELETE FROM issuers WHERE expires<?";
50 static const char beginTxnSQL
[] = "BEGIN EXCLUSIVE TRANSACTION";
51 static const char endTxnSQL
[] = "COMMIT TRANSACTION";
52 static const char insertIssuerSQL
[] = "INSERT INTO issuers "
53 "(uri,expires,certificate) VALUES (?,?,?)";
54 static const char selectIssuerSQL
[] = "SELECT certificate FROM "
55 "issuers WHERE uri=?";
59 CFURLRef
CFCopyHomeDirectoryURLForUser(CFStringRef uName
); /* Pass NULL for the current user's home directory */
62 #define kSecCAIssuerCachePath "/Library/Keychains/caissuercache.sqlite3";
64 typedef struct __SecCAIssuerCache
*SecCAIssuerCacheRef
;
65 struct __SecCAIssuerCache
{
68 sqlite3_stmt
*beginTxn
;
70 sqlite3_stmt
*insertIssuer
;
71 sqlite3_stmt
*selectIssuer
;
75 static pthread_once_t kSecCAIssuerCacheOnce
= PTHREAD_ONCE_INIT
;
76 static SecCAIssuerCacheRef kSecCAIssuerCache
= NULL
;
78 /* @@@ Duplicated from SecTrustStore.c */
79 static int sec_create_path(const char *path
)
81 char pathbuf
[PATH_MAX
];
82 size_t pos
, len
= strlen(path
);
83 if (len
== 0 || len
> PATH_MAX
)
84 return SQLITE_CANTOPEN
;
85 memcpy(pathbuf
, path
, len
);
86 for (pos
= len
-1; pos
> 0; --pos
)
88 /* Search backwards for trailing '/'. */
89 if (pathbuf
[pos
] == '/')
92 /* Attempt to create parent directories of the database. */
93 if (!mkdir(pathbuf
, 0777))
101 return SQLITE_CANTOPEN
;
103 return SQLITE_READONLY
;
106 if (err
== ENOSPC
|| err
== EDQUOT
)
111 /* EFAULT || ELOOP | ENAMETOOLONG || something else */
112 return SQLITE_INTERNAL
;
119 static int sec_sqlite3_open(const char *db_name
, sqlite3
**s3h
,
123 s3e
= sqlite3_open(db_name
, s3h
);
124 if (s3e
== SQLITE_CANTOPEN
&& create_path
) {
125 /* Make sure the path to db_name exists and is writable, then
127 s3e
= sec_create_path(db_name
);
129 s3e
= sqlite3_open(db_name
, s3h
);
135 static int sec_sqlite3_reset(sqlite3_stmt
*stmt
, int s3e
) {
137 if (s3e
== SQLITE_ROW
|| s3e
== SQLITE_DONE
)
139 s3e2
= sqlite3_reset(stmt
);
142 s3e2
= sqlite3_clear_bindings(stmt
);
148 static int SecCAIssuerCacheEnsureTxn(SecCAIssuerCacheRef
this) {
151 if (this->in_transaction
)
154 s3e
= sqlite3_step(this->beginTxn
);
155 if (s3e
== SQLITE_DONE
) {
156 this->in_transaction
= true;
159 secdebug("caissuercache", "sqlite3_step returned [%d]: %s", s3e
,
160 sqlite3_errmsg(this->s3h
));
162 s3e2
= sqlite3_reset(this->beginTxn
);
169 static int SecCAIssuerCacheCommitTxn(SecCAIssuerCacheRef
this) {
172 if (!this->in_transaction
)
175 s3e
= sqlite3_step(this->endTxn
);
176 if (s3e
== SQLITE_DONE
) {
177 this->in_transaction
= false;
180 secdebug("caissuercache", "sqlite3_step returned [%d]: %s", s3e
,
181 sqlite3_errmsg(this->s3h
));
183 s3e2
= sqlite3_reset(this->endTxn
);
190 static SecCAIssuerCacheRef
SecCAIssuerCacheCreate(const char *db_name
) {
191 SecCAIssuerCacheRef
this;
195 require(this = (SecCAIssuerCacheRef
)malloc(sizeof(struct __SecCAIssuerCache
)), errOut
);
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
);
243 sqlite3_close(this->s3h
);
250 static void SecCAIssuerCacheInit(void) {
251 static const char *path
= kSecCAIssuerCachePath
;
253 /* Added this block of code back to keep the tests happy for now. */
254 const char *home
= getenv("HOME");
255 char buffer
[PATH_MAX
];
257 size_t pathLen
= strlen(path
);
259 homeLen
= strlen(home
);
260 if (homeLen
+ pathLen
>= sizeof(buffer
)) {
264 strlcpy(buffer
, home
, sizeof(buffer
));
266 CFURLRef homeURL
= CFCopyHomeDirectoryURLForUser(NULL
);
270 CFURLGetFileSystemRepresentation(homeURL
, true, (uint8_t *)buffer
,
273 homeLen
= strlen(buffer
);
274 buffer
[homeLen
] = '\0';
275 if (homeLen
+ pathLen
>= sizeof(buffer
)) {
280 strlcat(buffer
, path
, sizeof(buffer
));
286 kSecCAIssuerCache
= SecCAIssuerCacheCreate(path
);
287 if (kSecCAIssuerCache
)
288 atexit(SecCAIssuerCacheGC
);
291 /* Instance implemenation. */
293 static void _SecCAIssuerCacheAddCertificate(SecCAIssuerCacheRef
this,
294 SecCertificateRef certificate
,
295 CFURLRef uri
, CFAbsoluteTime expires
) {
298 secdebug("caissuercache", "adding certificate from %@", uri
);
299 require_noerr(s3e
= SecCAIssuerCacheEnsureTxn(this), errOut
);
303 require_action(uriData
= CFURLCreateData(kCFAllocatorDefault
, uri
,
304 kCFStringEncodingUTF8
, false), errOut
, s3e
= SQLITE_NOMEM
);
305 s3e
= sqlite3_bind_blob_wrapper(this->insertIssuer
, 1,
306 CFDataGetBytePtr(uriData
), CFDataGetLength(uriData
), SQLITE_TRANSIENT
);
310 if (!s3e
) s3e
= sqlite3_bind_double(this->insertIssuer
, 2, expires
);
312 /* issuer.certificate */
313 if (!s3e
) s3e
= sqlite3_bind_blob_wrapper(this->insertIssuer
, 3,
314 SecCertificateGetBytePtr(certificate
),
315 SecCertificateGetLength(certificate
), SQLITE_TRANSIENT
);
317 /* Execute the insert statement. */
318 if (!s3e
) s3e
= sqlite3_step(this->insertIssuer
);
319 require_noerr(s3e
= sec_sqlite3_reset(this->insertIssuer
, s3e
), errOut
);
323 caissuerErrorLog("caissuer cache add failed: %s", sqlite3_errmsg(this->s3h
));
324 /* TODO: Blow away the cache and create a new db. */
328 static SecCertificateRef
_SecCAIssuerCacheCopyMatching(SecCAIssuerCacheRef
this,
330 SecCertificateRef certificate
= NULL
;
333 CFDataRef uriData
= NULL
;
334 require(uriData
= CFURLCreateData(kCFAllocatorDefault
, uri
,
335 kCFStringEncodingUTF8
, false), errOut
);
336 s3e
= sqlite3_bind_blob_wrapper(this->selectIssuer
, 1, CFDataGetBytePtr(uriData
),
337 CFDataGetLength(uriData
), SQLITE_TRANSIENT
);
340 if (!s3e
) s3e
= sqlite3_step(this->selectIssuer
);
341 if (s3e
== SQLITE_ROW
) {
342 /* Found an entry! */
343 secdebug("caissuercache", "found cached response for %@", uri
);
345 const void *respData
= sqlite3_column_blob(this->selectIssuer
, 0);
346 int respLen
= sqlite3_column_bytes(this->selectIssuer
, 0);
347 certificate
= SecCertificateCreateWithBytes(NULL
, respData
, respLen
);
350 require_noerr(s3e
= sec_sqlite3_reset(this->selectIssuer
, s3e
), errOut
);
354 if (s3e
!= SQLITE_DONE
) {
355 caissuerErrorLog("caissuer cache lookup failed: %s", sqlite3_errmsg(this->s3h
));
356 /* TODO: Blow away the cache and create a new db. */
360 CFRelease(certificate
);
365 secdebug("caissuercache", "returning %s for %@", (certificate
? "cached response" : "NULL"), uri
);
370 static void _SecCAIssuerCacheGC(void *context
) {
371 SecCAIssuerCacheRef
this = context
;
374 require_noerr(s3e
= SecCAIssuerCacheEnsureTxn(this), errOut
);
375 secdebug("caissuercache", "expiring stale responses");
376 s3e
= sqlite3_bind_double(this->expire
, 1, CFAbsoluteTimeGetCurrent());
377 if (!s3e
) s3e
= sqlite3_step(this->expire
);
378 require_noerr(s3e
= sec_sqlite3_reset(this->expire
, s3e
), errOut
);
379 require_noerr(s3e
= SecCAIssuerCacheCommitTxn(this), errOut
);
383 caissuerErrorLog("caissuer cache expire failed: %s", sqlite3_errmsg(this->s3h
));
384 /* TODO: Blow away the cache and create a new db. */
388 static void _SecCAIssuerCacheFlush(void *context
) {
389 SecCAIssuerCacheRef
this = context
;
392 secdebug("caissuercache", "flushing pending changes");
393 s3e
= SecCAIssuerCacheCommitTxn(this);
396 caissuerErrorLog("caissuer cache flush failed: %s", sqlite3_errmsg(this->s3h
));
397 /* TODO: Blow away the cache and create a new db. */
403 void SecCAIssuerCacheAddCertificate(SecCertificateRef certificate
,
404 CFURLRef uri
, CFAbsoluteTime expires
) {
405 pthread_once(&kSecCAIssuerCacheOnce
, SecCAIssuerCacheInit
);
406 if (!kSecCAIssuerCache
)
409 _SecCAIssuerCacheAddCertificate(kSecCAIssuerCache
, certificate
, uri
, expires
);
412 SecCertificateRef
SecCAIssuerCacheCopyMatching(CFURLRef uri
) {
413 pthread_once(&kSecCAIssuerCacheOnce
, SecCAIssuerCacheInit
);
414 if (!kSecCAIssuerCache
)
417 return _SecCAIssuerCacheCopyMatching(kSecCAIssuerCache
, uri
);
420 /* This should be called on a normal non emergency exit. This function
421 effectively does a SecCAIssuerCacheFlush.
422 Currently this is called from our atexit handeler.
423 This function expires any records that are stale and commits.
425 Idea for future cache management policies:
426 Expire old cache entires from database if:
427 - The time to do so has arrived based on the nextExpire date in the
429 - If the size of the database exceeds the limit set in the maxSize field
430 in the policy table, vacuum the db. If the database is still too
431 big, expire records on a LRU basis.
433 void SecCAIssuerCacheGC(void) {
434 if (kSecCAIssuerCache
)
435 _SecCAIssuerCacheGC(kSecCAIssuerCache
);
438 /* Call this periodically or perhaps when we are exiting due to low memory. */
439 void SecCAIssuerCacheFlush(void) {
440 if (kSecCAIssuerCache
)
441 _SecCAIssuerCacheFlush(kSecCAIssuerCache
);