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