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