]> git.saurik.com Git - apple/security.git/blob - OSX/sec/securityd/SecCAIssuerCache.c
Security-58286.41.2.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 #if TARGET_OS_IPHONE
253 WithPathInKeychainDirectory(CFSTR(kSecCAIssuerFileName), ^(const char *utf8String) {
254 kSecCAIssuerCache = SecCAIssuerCacheCreate(utf8String);
255 });
256 #else
257 /* macOS caches should be in user cache dir */
258 WithPathInUserCacheDirectory(CFSTR(kSecCAIssuerFileName), ^(const char *utf8String) {
259 kSecCAIssuerCache = SecCAIssuerCacheCreate(utf8String);
260 });
261 #endif
262
263 if (kSecCAIssuerCache)
264 atexit(SecCAIssuerCacheGC);
265 }
266
267 /* Instance implemenation. */
268
269 static void _SecCAIssuerCacheAddCertificate(SecCAIssuerCacheRef this,
270 SecCertificateRef certificate,
271 CFURLRef uri, CFAbsoluteTime expires) {
272 int s3e;
273
274 secdebug("caissuercache", "adding certificate from %@", uri);
275 require_noerr(s3e = SecCAIssuerCacheEnsureTxn(this), errOut);
276
277 /* issuer.uri */
278 CFDataRef uriData;
279 require_action(uriData = CFURLCreateData(kCFAllocatorDefault, uri,
280 kCFStringEncodingUTF8, false), errOut, s3e = SQLITE_NOMEM);
281 s3e = sqlite3_bind_blob_wrapper(this->insertIssuer, 1,
282 CFDataGetBytePtr(uriData), CFDataGetLength(uriData), SQLITE_TRANSIENT);
283 CFRelease(uriData);
284
285 /* issuer.expires */
286 if (!s3e) s3e = sqlite3_bind_double(this->insertIssuer, 2, expires);
287
288 /* issuer.certificate */
289 if (!s3e) s3e = sqlite3_bind_blob_wrapper(this->insertIssuer, 3,
290 SecCertificateGetBytePtr(certificate),
291 SecCertificateGetLength(certificate), SQLITE_TRANSIENT);
292
293 /* Execute the insert statement. */
294 if (!s3e) s3e = sqlite3_step(this->insertIssuer);
295 require_noerr(s3e = sec_sqlite3_reset(this->insertIssuer, s3e), errOut);
296
297 errOut:
298 if (s3e) {
299 secerror("caissuer cache add failed: %s", sqlite3_errmsg(this->s3h));
300 /* TODO: Blow away the cache and create a new db. */
301 }
302 }
303
304 static SecCertificateRef _SecCAIssuerCacheCopyMatching(SecCAIssuerCacheRef this,
305 CFURLRef uri) {
306 SecCertificateRef certificate = NULL;
307 int s3e = SQLITE_OK;
308
309 CFDataRef uriData = NULL;
310 require(uriData = CFURLCreateData(kCFAllocatorDefault, uri,
311 kCFStringEncodingUTF8, false), errOut);
312 s3e = sqlite3_bind_blob_wrapper(this->selectIssuer, 1, CFDataGetBytePtr(uriData),
313 CFDataGetLength(uriData), SQLITE_TRANSIENT);
314 CFRelease(uriData);
315
316 if (!s3e) s3e = sqlite3_step(this->selectIssuer);
317 if (s3e == SQLITE_ROW) {
318 /* Found an entry! */
319 secdebug("caissuercache", "found cached response for %@", uri);
320
321 const void *respData = sqlite3_column_blob(this->selectIssuer, 0);
322 int respLen = sqlite3_column_bytes(this->selectIssuer, 0);
323 certificate = SecCertificateCreateWithBytes(NULL, respData, respLen);
324 }
325
326 require_noerr(s3e = sec_sqlite3_reset(this->selectIssuer, s3e), errOut);
327
328 errOut:
329 if (s3e) {
330 if (s3e != SQLITE_DONE) {
331 secerror("caissuer cache lookup failed: %s", sqlite3_errmsg(this->s3h));
332 /* TODO: Blow away the cache and create a new db. */
333 }
334
335 if (certificate) {
336 CFRelease(certificate);
337 certificate = NULL;
338 }
339 }
340
341 secdebug("caissuercache", "returning %s for %@", (certificate ? "cached response" : "NULL"), uri);
342
343 return certificate;
344 }
345
346 static void _SecCAIssuerCacheGC(void *context) {
347 SecCAIssuerCacheRef this = context;
348 int s3e;
349
350 require_noerr(s3e = SecCAIssuerCacheEnsureTxn(this), errOut);
351 secdebug("caissuercache", "expiring stale responses");
352 s3e = sqlite3_bind_double(this->expire, 1, CFAbsoluteTimeGetCurrent());
353 if (!s3e) s3e = sqlite3_step(this->expire);
354 require_noerr(s3e = sec_sqlite3_reset(this->expire, s3e), errOut);
355 require_noerr(s3e = SecCAIssuerCacheCommitTxn(this), errOut);
356
357 errOut:
358 if (s3e) {
359 secerror("caissuer cache expire failed: %s", sqlite3_errmsg(this->s3h));
360 /* TODO: Blow away the cache and create a new db. */
361 }
362 }
363
364 static void _SecCAIssuerCacheFlush(void *context) {
365 SecCAIssuerCacheRef this = context;
366 int s3e;
367
368 secdebug("caissuercache", "flushing pending changes");
369 s3e = SecCAIssuerCacheCommitTxn(this);
370
371 if (s3e) {
372 secerror("caissuer cache flush failed: %s", sqlite3_errmsg(this->s3h));
373 /* TODO: Blow away the cache and create a new db. */
374 }
375 }
376
377 /* Public API */
378
379 void SecCAIssuerCacheAddCertificate(SecCertificateRef certificate,
380 CFURLRef uri, CFAbsoluteTime expires) {
381 dispatch_once(&kSecCAIssuerCacheOnce, ^{
382 SecCAIssuerCacheInit();
383 });
384 if (!kSecCAIssuerCache)
385 return;
386
387 dispatch_sync(kSecCAIssuerCache->queue, ^{
388 _SecCAIssuerCacheAddCertificate(kSecCAIssuerCache, certificate, uri, expires);
389 });
390 }
391
392 SecCertificateRef SecCAIssuerCacheCopyMatching(CFURLRef uri) {
393 dispatch_once(&kSecCAIssuerCacheOnce, ^{
394 SecCAIssuerCacheInit();
395 });
396 __block SecCertificateRef cert = NULL;
397 if (kSecCAIssuerCache)
398 dispatch_sync(kSecCAIssuerCache->queue, ^{
399 cert = _SecCAIssuerCacheCopyMatching(kSecCAIssuerCache, uri);
400 });
401 return cert;
402 }
403
404 /* This should be called on a normal non emergency exit. This function
405 effectively does a SecCAIssuerCacheFlush.
406 Currently this is called from our atexit handeler.
407 This function expires any records that are stale and commits.
408
409 Idea for future cache management policies:
410 Expire old cache entires from database if:
411 - The time to do so has arrived based on the nextExpire date in the
412 policy table.
413 - If the size of the database exceeds the limit set in the maxSize field
414 in the policy table, vacuum the db. If the database is still too
415 big, expire records on a LRU basis.
416 */
417 void SecCAIssuerCacheGC(void) {
418 if (kSecCAIssuerCache)
419 dispatch_sync(kSecCAIssuerCache->queue, ^{
420 _SecCAIssuerCacheGC(kSecCAIssuerCache);
421 });
422 }
423
424 /* Call this periodically or perhaps when we are exiting due to low memory. */
425 void SecCAIssuerCacheFlush(void) {
426 if (kSecCAIssuerCache)
427 dispatch_sync(kSecCAIssuerCache->queue, ^{
428 _SecCAIssuerCacheFlush(kSecCAIssuerCache);
429 });
430 }