]> git.saurik.com Git - apple/security.git/blob - OSX/sec/securityd/SecCAIssuerCache.c
Security-58286.200.222.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 <securityd/SecTrustLoggingServer.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 #include <utilities/SecCFWrappers.h>
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 = SQLITE_OK;
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 secerror("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 (s3e != SQLITE_OK) {
243 TrustdHealthAnalyticsLogErrorCodeForDatabase(TACAIssuerCache, TAOperationCreate, TAFatalError, s3e);
244 }
245 if (this) {
246 if (this->queue)
247 dispatch_release(this->queue);
248 if (this->s3h)
249 sqlite3_close(this->s3h);
250 free(this);
251 }
252
253 return NULL;
254 }
255
256 static void SecCAIssuerCacheInit(void) {
257 #if TARGET_OS_IPHONE
258 WithPathInKeychainDirectory(CFSTR(kSecCAIssuerFileName), ^(const char *utf8String) {
259 kSecCAIssuerCache = SecCAIssuerCacheCreate(utf8String);
260 });
261 #else
262 /* macOS caches should be in user cache dir */
263 WithPathInUserCacheDirectory(CFSTR(kSecCAIssuerFileName), ^(const char *utf8String) {
264 kSecCAIssuerCache = SecCAIssuerCacheCreate(utf8String);
265 });
266 #endif
267
268 if (kSecCAIssuerCache)
269 atexit(SecCAIssuerCacheGC);
270 }
271
272 static CF_RETURNS_RETAINED CFDataRef convertArrayOfCertsToData(CFArrayRef certificates) {
273 if (!certificates || CFArrayGetCount(certificates) == 0) {
274 return NULL;
275 }
276
277 CFMutableDataRef output = CFDataCreateMutable(NULL, 0);
278 CFArrayForEach(certificates, ^(const void *value) {
279 CFDataRef certData = SecCertificateCopyData((SecCertificateRef)value);
280 if (certData) {
281 CFDataAppend(output, certData);
282 }
283 CFReleaseNull(certData);
284 });
285
286 return output;
287 }
288
289 static CF_RETURNS_RETAINED CFArrayRef convertDataToArrayOfCerts(uint8_t *data, size_t dataLen) {
290 if (!data || dataLen == 0) {
291 return NULL;
292 }
293
294 CFMutableArrayRef output = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
295 uint8_t *nextCertPtr = data;
296 size_t remainingDataLen = dataLen;
297 while (nextCertPtr < data + dataLen) {
298 SecCertificateRef cert = SecCertificateCreateWithBytes(NULL, nextCertPtr, remainingDataLen);
299 if (cert) {
300 CFArrayAppendValue(output, cert);
301 nextCertPtr += SecCertificateGetLength(cert);
302 remainingDataLen -= SecCertificateGetLength(cert);
303 CFReleaseNull(cert);
304 } else {
305 /* We don't know where the next cert starts, so we should just stop */
306 break;
307 }
308 }
309
310 if (CFArrayGetCount(output) < 1) {
311 CFReleaseNull(output);
312 }
313 return output;
314 }
315
316 /* Instance implemenation. */
317
318 static void _SecCAIssuerCacheAddCertificates(SecCAIssuerCacheRef this,
319 CFArrayRef certificates,
320 CFURLRef uri, CFAbsoluteTime expires) {
321 int s3e;
322 CFDataRef certsData = NULL;
323 CFDataRef uriData = NULL;
324
325 secdebug("caissuercache", "adding certificate from %@", uri);
326 require_noerr(s3e = SecCAIssuerCacheEnsureTxn(this), errOut);
327
328 /* issuer.uri */
329 require_action(uriData = CFURLCreateData(kCFAllocatorDefault, uri,
330 kCFStringEncodingUTF8, false), errOut, s3e = SQLITE_NOMEM);
331 s3e = sqlite3_bind_blob_wrapper(this->insertIssuer, 1,
332 CFDataGetBytePtr(uriData), CFDataGetLength(uriData), SQLITE_TRANSIENT);
333 CFRelease(uriData);
334
335 /* issuer.expires */
336 if (!s3e) s3e = sqlite3_bind_double(this->insertIssuer, 2, expires);
337
338 /* issuer.certificate */
339 require_action(certsData = convertArrayOfCertsToData(certificates), errOut,
340 s3e = SQLITE_NOMEM);
341 if (!s3e) {
342 s3e = sqlite3_bind_blob_wrapper(this->insertIssuer, 3,
343 CFDataGetBytePtr(certsData),
344 CFDataGetLength(certsData), SQLITE_TRANSIENT);
345 }
346 CFReleaseNull(certsData);
347
348 /* Execute the insert statement. */
349 if (!s3e) s3e = sqlite3_step(this->insertIssuer);
350 require_noerr(s3e = sec_sqlite3_reset(this->insertIssuer, s3e), errOut);
351
352 errOut:
353 if (s3e != SQLITE_OK) {
354 secerror("caissuer cache add failed: %s", sqlite3_errmsg(this->s3h));
355 TrustdHealthAnalyticsLogErrorCodeForDatabase(TACAIssuerCache, TAOperationWrite, TAFatalError, s3e);
356 /* TODO: Blow away the cache and create a new db. */
357 }
358 }
359
360 static CFArrayRef _SecCAIssuerCacheCopyMatching(SecCAIssuerCacheRef this,
361 CFURLRef uri) {
362 CFArrayRef certificates = NULL;
363 int s3e = SQLITE_OK;
364
365 CFDataRef uriData = NULL;
366 require(uriData = CFURLCreateData(kCFAllocatorDefault, uri,
367 kCFStringEncodingUTF8, false), errOut);
368 s3e = sqlite3_bind_blob_wrapper(this->selectIssuer, 1, CFDataGetBytePtr(uriData),
369 CFDataGetLength(uriData), SQLITE_TRANSIENT);
370 CFRelease(uriData);
371
372 if (!s3e) s3e = sqlite3_step(this->selectIssuer);
373 if (s3e == SQLITE_ROW) {
374 /* Found an entry! */
375 secdebug("caissuercache", "found cached response for %@", uri);
376
377 const void *respData = sqlite3_column_blob(this->selectIssuer, 0);
378 int respLen = sqlite3_column_bytes(this->selectIssuer, 0);
379 certificates = convertDataToArrayOfCerts((uint8_t *)respData, respLen);
380 }
381
382 require_noerr(s3e = sec_sqlite3_reset(this->selectIssuer, s3e), errOut);
383
384 errOut:
385 if (s3e != SQLITE_OK) {
386 if (s3e != SQLITE_DONE) {
387 secerror("caissuer cache lookup failed: %s", sqlite3_errmsg(this->s3h));
388 TrustdHealthAnalyticsLogErrorCodeForDatabase(TACAIssuerCache, TAOperationRead, TAFatalError, s3e);
389 /* TODO: Blow away the cache and create a new db. */
390 }
391
392 if (certificates) {
393 CFRelease(certificates);
394 certificates = NULL;
395 }
396 }
397
398 secdebug("caissuercache", "returning %s for %@", (certificates ? "cached response" : "NULL"), uri);
399 return certificates;
400 }
401
402 static void _SecCAIssuerCacheGC(void *context) {
403 SecCAIssuerCacheRef this = context;
404 int s3e;
405
406 require_noerr(s3e = SecCAIssuerCacheEnsureTxn(this), errOut);
407 secdebug("caissuercache", "expiring stale responses");
408 s3e = sqlite3_bind_double(this->expire, 1, CFAbsoluteTimeGetCurrent());
409 if (!s3e) s3e = sqlite3_step(this->expire);
410 require_noerr(s3e = sec_sqlite3_reset(this->expire, s3e), errOut);
411 require_noerr(s3e = SecCAIssuerCacheCommitTxn(this), errOut);
412
413 errOut:
414 if (s3e != SQLITE_OK) {
415 secerror("caissuer cache expire failed: %s", sqlite3_errmsg(this->s3h));
416 TrustdHealthAnalyticsLogErrorCodeForDatabase(TACAIssuerCache, TAOperationWrite, TAFatalError, s3e);
417 /* TODO: Blow away the cache and create a new db. */
418 }
419 }
420
421 static void _SecCAIssuerCacheFlush(void *context) {
422 SecCAIssuerCacheRef this = context;
423 int s3e;
424
425 secdebug("caissuercache", "flushing pending changes");
426 s3e = SecCAIssuerCacheCommitTxn(this);
427
428 if (s3e != SQLITE_OK) {
429 secerror("caissuer cache flush failed: %s", sqlite3_errmsg(this->s3h));
430 TrustdHealthAnalyticsLogErrorCodeForDatabase(TACAIssuerCache, TAOperationWrite, TAFatalError, s3e);
431 /* TODO: Blow away the cache and create a new db. */
432 }
433 }
434
435 /* Public API */
436
437 void SecCAIssuerCacheAddCertificates(CFArrayRef certificates,
438 CFURLRef uri, CFAbsoluteTime expires) {
439 dispatch_once(&kSecCAIssuerCacheOnce, ^{
440 SecCAIssuerCacheInit();
441 });
442 if (!kSecCAIssuerCache)
443 return;
444
445 dispatch_sync(kSecCAIssuerCache->queue, ^{
446 _SecCAIssuerCacheAddCertificates(kSecCAIssuerCache, certificates, uri, expires);
447 _SecCAIssuerCacheFlush(kSecCAIssuerCache);
448 });
449 }
450
451 CFArrayRef SecCAIssuerCacheCopyMatching(CFURLRef uri) {
452 dispatch_once(&kSecCAIssuerCacheOnce, ^{
453 SecCAIssuerCacheInit();
454 });
455 __block CFArrayRef certs = NULL;
456 if (kSecCAIssuerCache)
457 dispatch_sync(kSecCAIssuerCache->queue, ^{
458 certs = _SecCAIssuerCacheCopyMatching(kSecCAIssuerCache, uri);
459 });
460 return certs;
461 }
462
463 /* This should be called on a normal non emergency exit. This function
464 effectively does a SecCAIssuerCacheFlush.
465 Currently this is called from our atexit handeler.
466 This function expires any records that are stale and commits.
467
468 Idea for future cache management policies:
469 Expire old cache entires from database if:
470 - The time to do so has arrived based on the nextExpire date in the
471 policy table.
472 - If the size of the database exceeds the limit set in the maxSize field
473 in the policy table, vacuum the db. If the database is still too
474 big, expire records on a LRU basis.
475 */
476 void SecCAIssuerCacheGC(void) {
477 if (kSecCAIssuerCache)
478 dispatch_sync(kSecCAIssuerCache->queue, ^{
479 _SecCAIssuerCacheGC(kSecCAIssuerCache);
480 });
481 }