]> git.saurik.com Git - apple/security.git/blob - sec/securityd/SecCAIssuerCache.c
Security-55471.14.18.tar.gz
[apple/security.git] / sec / securityd / SecCAIssuerCache.c
1 /*
2 * Copyright (c) 2011 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
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
11 * file.
12 *
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.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 *
23 * Created by Michael Brouwer on 6/17/11.
24 */
25
26 /*
27 * SecCAIssuerCache.c - securityd
28 */
29
30 #include <securityd/SecCAIssuerCache.h>
31 #include <utilities/debugging.h>
32 #include <Security/SecCertificateInternal.h>
33 #include <Security/SecFramework.h>
34 #include <Security/SecInternal.h>
35 #include <sqlite3.h>
36 #include <AssertMacros.h>
37 #include <stdlib.h>
38 #include <limits.h>
39 #include <string.h>
40 #include <fcntl.h>
41 #include <sys/stat.h>
42 #include <errno.h>
43 #include <dispatch/dispatch.h>
44 #include <asl.h>
45 #include "utilities/sqlutils.h"
46 #include "utilities/iOSforOSX.h"
47
48 #include <CoreFoundation/CFUtilities.h>
49 #include <utilities/SecFileLocations.h>
50
51 #define caissuerErrorLog(args...) asl_log(NULL, NULL, ASL_LEVEL_ERR, ## args)
52
53 static const char expireSQL[] = "DELETE FROM issuers WHERE expires<?";
54 static const char beginTxnSQL[] = "BEGIN EXCLUSIVE TRANSACTION";
55 static const char endTxnSQL[] = "COMMIT TRANSACTION";
56 static const char insertIssuerSQL[] = "INSERT OR REPLACE INTO issuers "
57 "(uri,expires,certificate) VALUES (?,?,?)";
58 static const char selectIssuerSQL[] = "SELECT certificate FROM "
59 "issuers WHERE uri=?";
60
61 #define kSecCAIssuerFileName "caissuercache.sqlite3"
62
63 typedef struct __SecCAIssuerCache *SecCAIssuerCacheRef;
64 struct __SecCAIssuerCache {
65 dispatch_queue_t queue;
66 sqlite3 *s3h;
67 sqlite3_stmt *expire;
68 sqlite3_stmt *beginTxn;
69 sqlite3_stmt *endTxn;
70 sqlite3_stmt *insertIssuer;
71 sqlite3_stmt *selectIssuer;
72 bool in_transaction;
73 };
74
75 static dispatch_once_t kSecCAIssuerCacheOnce;
76 static SecCAIssuerCacheRef kSecCAIssuerCache;
77
78 /* @@@ Duplicated from SecTrustStore.c */
79 static int sec_create_path(const char *path)
80 {
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)
87 {
88 /* Search backwards for trailing '/'. */
89 if (pathbuf[pos] == '/')
90 {
91 pathbuf[pos] = '\0';
92 /* Attempt to create parent directories of the database. */
93 if (!mkdir(pathbuf, 0777))
94 break;
95 else
96 {
97 int err = errno;
98 if (err == EEXIST)
99 return 0;
100 if (err == ENOTDIR)
101 return SQLITE_CANTOPEN;
102 if (err == EROFS)
103 return SQLITE_READONLY;
104 if (err == EACCES)
105 return SQLITE_PERM;
106 if (err == ENOSPC || err == EDQUOT)
107 return SQLITE_FULL;
108 if (err == EIO)
109 return SQLITE_IOERR;
110
111 /* EFAULT || ELOOP | ENAMETOOLONG || something else */
112 return SQLITE_INTERNAL;
113 }
114 }
115 }
116 return SQLITE_OK;
117 }
118
119 static int sec_sqlite3_open(const char *db_name, sqlite3 **s3h,
120 bool create_path)
121 {
122 int s3e;
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
126 try again. */
127 s3e = sec_create_path(db_name);
128 if (!s3e)
129 s3e = sqlite3_open(db_name, s3h);
130 }
131
132 return s3e;
133 }
134
135 static int sec_sqlite3_reset(sqlite3_stmt *stmt, int s3e) {
136 int s3e2;
137 if (s3e == SQLITE_ROW || s3e == SQLITE_DONE)
138 s3e = SQLITE_OK;
139 s3e2 = sqlite3_reset(stmt);
140 if (s3e2 && !s3e)
141 s3e = s3e2;
142 s3e2 = sqlite3_clear_bindings(stmt);
143 if (s3e2 && !s3e)
144 s3e = s3e2;
145 return s3e;
146 }
147
148 static int SecCAIssuerCacheEnsureTxn(SecCAIssuerCacheRef this) {
149 int s3e, s3e2;
150
151 if (this->in_transaction)
152 return SQLITE_OK;
153
154 s3e = sqlite3_step(this->beginTxn);
155 if (s3e == SQLITE_DONE) {
156 this->in_transaction = true;
157 s3e = SQLITE_OK;
158 } else {
159 secdebug("caissuercache", "sqlite3_step returned [%d]: %s", s3e,
160 sqlite3_errmsg(this->s3h));
161 }
162 s3e2 = sqlite3_reset(this->beginTxn);
163 if (s3e2 && !s3e)
164 s3e = s3e2;
165
166 return s3e;
167 }
168
169 static int SecCAIssuerCacheCommitTxn(SecCAIssuerCacheRef this) {
170 int s3e, s3e2;
171
172 if (!this->in_transaction)
173 return SQLITE_OK;
174
175 s3e = sqlite3_step(this->endTxn);
176 if (s3e == SQLITE_DONE) {
177 this->in_transaction = false;
178 s3e = SQLITE_OK;
179 } else {
180 secdebug("caissuercache", "sqlite3_step returned [%d]: %s", s3e,
181 sqlite3_errmsg(this->s3h));
182 }
183 s3e2 = sqlite3_reset(this->endTxn);
184 if (s3e2 && !s3e)
185 s3e = s3e2;
186
187 return s3e;
188 }
189
190 static SecCAIssuerCacheRef SecCAIssuerCacheCreate(const char *db_name) {
191 SecCAIssuerCacheRef this;
192 int s3e;
193 bool create = true;
194
195 require(this = (SecCAIssuerCacheRef)calloc(sizeof(struct __SecCAIssuerCache), 1), errOut);
196 require_action_quiet((this->queue = dispatch_queue_create("caissuercache", 0)), errOut, s3e = errSecAllocate);
197 require_noerr(s3e = sec_sqlite3_open(db_name, &this->s3h, create), errOut);
198 this->in_transaction = false;
199
200 s3e = sqlite3_prepare_v2(this->s3h, beginTxnSQL, sizeof(beginTxnSQL),
201 &this->beginTxn, NULL);
202 require_noerr(s3e, errOut);
203 s3e = sqlite3_prepare_v2(this->s3h, endTxnSQL, sizeof(endTxnSQL),
204 &this->endTxn, NULL);
205 require_noerr(s3e, errOut);
206
207 s3e = sqlite3_prepare_v2(this->s3h, expireSQL, sizeof(expireSQL),
208 &this->expire, NULL);
209 if (create && s3e == SQLITE_ERROR) {
210 s3e = SecCAIssuerCacheEnsureTxn(this);
211 require_noerr(s3e, errOut);
212
213 /* sqlite3_prepare returns SQLITE_ERROR if the table we are
214 compiling this statement for doesn't exist. */
215 char *errmsg = NULL;
216 s3e = sqlite3_exec(this->s3h,
217 "CREATE TABLE issuers("
218 "uri BLOB PRIMARY KEY,"
219 "expires DOUBLE NOT NULL,"
220 "certificate BLOB NOT NULL"
221 ");"
222 "CREATE INDEX iexpires ON issuers(expires);"
223 , NULL, NULL, &errmsg);
224 if (errmsg) {
225 caissuerErrorLog("caissuer db CREATE TABLES: %s", errmsg);
226 sqlite3_free(errmsg);
227 }
228 require_noerr(s3e, errOut);
229 s3e = sqlite3_prepare_v2(this->s3h, expireSQL, sizeof(expireSQL),
230 &this->expire, NULL);
231 }
232 require_noerr(s3e, errOut);
233 s3e = sqlite3_prepare_v2(this->s3h, insertIssuerSQL, sizeof(insertIssuerSQL),
234 &this->insertIssuer, NULL);
235 require_noerr(s3e, errOut);
236 s3e = sqlite3_prepare_v2(this->s3h, selectIssuerSQL, sizeof(selectIssuerSQL),
237 &this->selectIssuer, NULL);
238 require_noerr(s3e, errOut);
239
240 return this;
241
242 errOut:
243 if (this) {
244 if (this->queue)
245 dispatch_release(this->queue);
246 if (this->s3h)
247 sqlite3_close(this->s3h);
248 free(this);
249 }
250
251 return NULL;
252 }
253
254 static void SecCAIssuerCacheInit(void) {
255 WithPathInKeychainDirectory(CFSTR(kSecCAIssuerFileName), ^(const char *utf8String) {
256 kSecCAIssuerCache = SecCAIssuerCacheCreate(utf8String);
257 });
258
259 if (kSecCAIssuerCache)
260 atexit(SecCAIssuerCacheGC);
261 }
262
263 /* Instance implemenation. */
264
265 static void _SecCAIssuerCacheAddCertificate(SecCAIssuerCacheRef this,
266 SecCertificateRef certificate,
267 CFURLRef uri, CFAbsoluteTime expires) {
268 int s3e;
269
270 secdebug("caissuercache", "adding certificate from %@", uri);
271 require_noerr(s3e = SecCAIssuerCacheEnsureTxn(this), errOut);
272
273 /* issuer.uri */
274 CFDataRef uriData;
275 require_action(uriData = CFURLCreateData(kCFAllocatorDefault, uri,
276 kCFStringEncodingUTF8, false), errOut, s3e = SQLITE_NOMEM);
277 s3e = sqlite3_bind_blob_wrapper(this->insertIssuer, 1,
278 CFDataGetBytePtr(uriData), CFDataGetLength(uriData), SQLITE_TRANSIENT);
279 CFRelease(uriData);
280
281 /* issuer.expires */
282 if (!s3e) s3e = sqlite3_bind_double(this->insertIssuer, 2, expires);
283
284 /* issuer.certificate */
285 if (!s3e) s3e = sqlite3_bind_blob_wrapper(this->insertIssuer, 3,
286 SecCertificateGetBytePtr(certificate),
287 SecCertificateGetLength(certificate), SQLITE_TRANSIENT);
288
289 /* Execute the insert statement. */
290 if (!s3e) s3e = sqlite3_step(this->insertIssuer);
291 require_noerr(s3e = sec_sqlite3_reset(this->insertIssuer, s3e), errOut);
292
293 errOut:
294 if (s3e) {
295 caissuerErrorLog("caissuer cache add failed: %s", sqlite3_errmsg(this->s3h));
296 /* TODO: Blow away the cache and create a new db. */
297 }
298 }
299
300 static SecCertificateRef _SecCAIssuerCacheCopyMatching(SecCAIssuerCacheRef this,
301 CFURLRef uri) {
302 SecCertificateRef certificate = NULL;
303 int s3e = SQLITE_OK;
304
305 CFDataRef uriData = NULL;
306 require(uriData = CFURLCreateData(kCFAllocatorDefault, uri,
307 kCFStringEncodingUTF8, false), errOut);
308 s3e = sqlite3_bind_blob_wrapper(this->selectIssuer, 1, CFDataGetBytePtr(uriData),
309 CFDataGetLength(uriData), SQLITE_TRANSIENT);
310 CFRelease(uriData);
311
312 if (!s3e) s3e = sqlite3_step(this->selectIssuer);
313 if (s3e == SQLITE_ROW) {
314 /* Found an entry! */
315 secdebug("caissuercache", "found cached response for %@", uri);
316
317 const void *respData = sqlite3_column_blob(this->selectIssuer, 0);
318 int respLen = sqlite3_column_bytes(this->selectIssuer, 0);
319 certificate = SecCertificateCreateWithBytes(NULL, respData, respLen);
320 }
321
322 require_noerr(s3e = sec_sqlite3_reset(this->selectIssuer, s3e), errOut);
323
324 errOut:
325 if (s3e) {
326 if (s3e != SQLITE_DONE) {
327 caissuerErrorLog("caissuer cache lookup failed: %s", sqlite3_errmsg(this->s3h));
328 /* TODO: Blow away the cache and create a new db. */
329 }
330
331 if (certificate) {
332 CFRelease(certificate);
333 certificate = NULL;
334 }
335 }
336
337 secdebug("caissuercache", "returning %s for %@", (certificate ? "cached response" : "NULL"), uri);
338
339 return certificate;
340 }
341
342 static void _SecCAIssuerCacheGC(void *context) {
343 SecCAIssuerCacheRef this = context;
344 int s3e;
345
346 require_noerr(s3e = SecCAIssuerCacheEnsureTxn(this), errOut);
347 secdebug("caissuercache", "expiring stale responses");
348 s3e = sqlite3_bind_double(this->expire, 1, CFAbsoluteTimeGetCurrent());
349 if (!s3e) s3e = sqlite3_step(this->expire);
350 require_noerr(s3e = sec_sqlite3_reset(this->expire, s3e), errOut);
351 require_noerr(s3e = SecCAIssuerCacheCommitTxn(this), errOut);
352
353 errOut:
354 if (s3e) {
355 caissuerErrorLog("caissuer cache expire failed: %s", sqlite3_errmsg(this->s3h));
356 /* TODO: Blow away the cache and create a new db. */
357 }
358 }
359
360 static void _SecCAIssuerCacheFlush(void *context) {
361 SecCAIssuerCacheRef this = context;
362 int s3e;
363
364 secdebug("caissuercache", "flushing pending changes");
365 s3e = SecCAIssuerCacheCommitTxn(this);
366
367 if (s3e) {
368 caissuerErrorLog("caissuer cache flush failed: %s", sqlite3_errmsg(this->s3h));
369 /* TODO: Blow away the cache and create a new db. */
370 }
371 }
372
373 /* Public API */
374
375 void SecCAIssuerCacheAddCertificate(SecCertificateRef certificate,
376 CFURLRef uri, CFAbsoluteTime expires) {
377 dispatch_once(&kSecCAIssuerCacheOnce, ^{
378 SecCAIssuerCacheInit();
379 });
380 if (!kSecCAIssuerCache)
381 return;
382
383 dispatch_sync(kSecCAIssuerCache->queue, ^{
384 _SecCAIssuerCacheAddCertificate(kSecCAIssuerCache, certificate, uri, expires);
385 });
386 }
387
388 SecCertificateRef SecCAIssuerCacheCopyMatching(CFURLRef uri) {
389 dispatch_once(&kSecCAIssuerCacheOnce, ^{
390 SecCAIssuerCacheInit();
391 });
392 __block SecCertificateRef cert = NULL;
393 if (kSecCAIssuerCache)
394 dispatch_sync(kSecCAIssuerCache->queue, ^{
395 cert = _SecCAIssuerCacheCopyMatching(kSecCAIssuerCache, uri);
396 });
397 return cert;
398 }
399
400 /* This should be called on a normal non emergency exit. This function
401 effectively does a SecCAIssuerCacheFlush.
402 Currently this is called from our atexit handeler.
403 This function expires any records that are stale and commits.
404
405 Idea for future cache management policies:
406 Expire old cache entires from database if:
407 - The time to do so has arrived based on the nextExpire date in the
408 policy table.
409 - If the size of the database exceeds the limit set in the maxSize field
410 in the policy table, vacuum the db. If the database is still too
411 big, expire records on a LRU basis.
412 */
413 void SecCAIssuerCacheGC(void) {
414 if (kSecCAIssuerCache)
415 dispatch_sync(kSecCAIssuerCache->queue, ^{
416 _SecCAIssuerCacheGC(kSecCAIssuerCache);
417 });
418 }
419
420 /* Call this periodically or perhaps when we are exiting due to low memory. */
421 void SecCAIssuerCacheFlush(void) {
422 if (kSecCAIssuerCache)
423 dispatch_sync(kSecCAIssuerCache->queue, ^{
424 _SecCAIssuerCacheFlush(kSecCAIssuerCache);
425 });
426 }