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