]> git.saurik.com Git - apple/security.git/blob - sec/securityd/SecCAIssuerCache.c
Security-55163.44.tar.gz
[apple/security.git] / sec / securityd / SecCAIssuerCache.c
1 /*
2 * Copyright (c) 2011 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 * Created by Michael Brouwer on 6/17/11.
24 */
25
26 /*
27 * SecCAIssuerCache.c - securityd
28 */
29
30 #include <securityd/SecCAIssuerCache.h>
31 #include <security_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 <pthread.h>
44 #include <asl.h>
45 #include "sqlutils.h"
46
47 #define caissuerErrorLog(args...) asl_log(NULL, NULL, ASL_LEVEL_ERR, ## args)
48
49 static const char expireSQL[] = "DELETE FROM issuers WHERE expires<?";
50 static const char beginTxnSQL[] = "BEGIN EXCLUSIVE TRANSACTION";
51 static const char endTxnSQL[] = "COMMIT TRANSACTION";
52 static const char insertIssuerSQL[] = "INSERT INTO issuers "
53 "(uri,expires,certificate) VALUES (?,?,?)";
54 static const char selectIssuerSQL[] = "SELECT certificate FROM "
55 "issuers WHERE uri=?";
56
57 #if NO_SERVER
58 CF_EXPORT
59 CFURLRef CFCopyHomeDirectoryURLForUser(CFStringRef uName); /* Pass NULL for the current user's home directory */
60 #endif
61
62 #define kSecCAIssuerCachePath "/Library/Keychains/caissuercache.sqlite3";
63
64 typedef struct __SecCAIssuerCache *SecCAIssuerCacheRef;
65 struct __SecCAIssuerCache {
66 sqlite3 *s3h;
67 sqlite3_stmt *expire;
68 sqlite3_stmt *beginTxn;
69 sqlite3_stmt *endTxn;
70 sqlite3_stmt *insertIssuer;
71 sqlite3_stmt *selectIssuer;
72 bool in_transaction;
73 };
74
75 static pthread_once_t kSecCAIssuerCacheOnce = PTHREAD_ONCE_INIT;
76 static SecCAIssuerCacheRef kSecCAIssuerCache = NULL;
77
78 /* @@@ Duplicated from SecTrustStore.c */
79 static int sec_create_path(const char *path)
80 {
81 char pathbuf[PATH_MAX];
82 size_t pos, len = strlen(path);
83 if (len == 0 || len > PATH_MAX)
84 return SQLITE_CANTOPEN;
85 memcpy(pathbuf, path, len);
86 for (pos = len-1; pos > 0; --pos)
87 {
88 /* Search backwards for trailing '/'. */
89 if (pathbuf[pos] == '/')
90 {
91 pathbuf[pos] = '\0';
92 /* Attempt to create parent directories of the database. */
93 if (!mkdir(pathbuf, 0777))
94 break;
95 else
96 {
97 int err = errno;
98 if (err == EEXIST)
99 return 0;
100 if (err == ENOTDIR)
101 return SQLITE_CANTOPEN;
102 if (err == EROFS)
103 return SQLITE_READONLY;
104 if (err == EACCES)
105 return SQLITE_PERM;
106 if (err == ENOSPC || err == EDQUOT)
107 return SQLITE_FULL;
108 if (err == EIO)
109 return SQLITE_IOERR;
110
111 /* EFAULT || ELOOP | ENAMETOOLONG || something else */
112 return SQLITE_INTERNAL;
113 }
114 }
115 }
116 return SQLITE_OK;
117 }
118
119 static int sec_sqlite3_open(const char *db_name, sqlite3 **s3h,
120 bool create_path)
121 {
122 int s3e;
123 s3e = sqlite3_open(db_name, s3h);
124 if (s3e == SQLITE_CANTOPEN && create_path) {
125 /* Make sure the path to db_name exists and is writable, then
126 try again. */
127 s3e = sec_create_path(db_name);
128 if (!s3e)
129 s3e = sqlite3_open(db_name, s3h);
130 }
131
132 return s3e;
133 }
134
135 static int sec_sqlite3_reset(sqlite3_stmt *stmt, int s3e) {
136 int s3e2;
137 if (s3e == SQLITE_ROW || s3e == SQLITE_DONE)
138 s3e = SQLITE_OK;
139 s3e2 = sqlite3_reset(stmt);
140 if (s3e2 && !s3e)
141 s3e = s3e2;
142 s3e2 = sqlite3_clear_bindings(stmt);
143 if (s3e2 && !s3e)
144 s3e = s3e2;
145 return s3e;
146 }
147
148 static int SecCAIssuerCacheEnsureTxn(SecCAIssuerCacheRef this) {
149 int s3e, s3e2;
150
151 if (this->in_transaction)
152 return SQLITE_OK;
153
154 s3e = sqlite3_step(this->beginTxn);
155 if (s3e == SQLITE_DONE) {
156 this->in_transaction = true;
157 s3e = SQLITE_OK;
158 } else {
159 secdebug("caissuercache", "sqlite3_step returned [%d]: %s", s3e,
160 sqlite3_errmsg(this->s3h));
161 }
162 s3e2 = sqlite3_reset(this->beginTxn);
163 if (s3e2 && !s3e)
164 s3e = s3e2;
165
166 return s3e;
167 }
168
169 static int SecCAIssuerCacheCommitTxn(SecCAIssuerCacheRef this) {
170 int s3e, s3e2;
171
172 if (!this->in_transaction)
173 return SQLITE_OK;
174
175 s3e = sqlite3_step(this->endTxn);
176 if (s3e == SQLITE_DONE) {
177 this->in_transaction = false;
178 s3e = SQLITE_OK;
179 } else {
180 secdebug("caissuercache", "sqlite3_step returned [%d]: %s", s3e,
181 sqlite3_errmsg(this->s3h));
182 }
183 s3e2 = sqlite3_reset(this->endTxn);
184 if (s3e2 && !s3e)
185 s3e = s3e2;
186
187 return s3e;
188 }
189
190 static SecCAIssuerCacheRef SecCAIssuerCacheCreate(const char *db_name) {
191 SecCAIssuerCacheRef this;
192 int s3e;
193 bool create = true;
194
195 require(this = (SecCAIssuerCacheRef)malloc(sizeof(struct __SecCAIssuerCache)), errOut);
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 sqlite3_close(this->s3h);
244 free(this);
245 }
246
247 return NULL;
248 }
249
250 static void SecCAIssuerCacheInit(void) {
251 static const char *path = kSecCAIssuerCachePath;
252 #if NO_SERVER
253 /* Added this block of code back to keep the tests happy for now. */
254 const char *home = getenv("HOME");
255 char buffer[PATH_MAX];
256 size_t homeLen;
257 size_t pathLen = strlen(path);
258 if (home) {
259 homeLen = strlen(home);
260 if (homeLen + pathLen >= sizeof(buffer)) {
261 return;
262 }
263
264 strlcpy(buffer, home, sizeof(buffer));
265 } else {
266 CFURLRef homeURL = CFCopyHomeDirectoryURLForUser(NULL);
267 if (!homeURL)
268 return;
269
270 CFURLGetFileSystemRepresentation(homeURL, true, (uint8_t *)buffer,
271 sizeof(buffer));
272 CFRelease(homeURL);
273 homeLen = strlen(buffer);
274 buffer[homeLen] = '\0';
275 if (homeLen + pathLen >= sizeof(buffer)) {
276 return;
277 }
278 }
279
280 strlcat(buffer, path, sizeof(buffer));
281
282 path = buffer;
283
284 #endif
285
286 kSecCAIssuerCache = SecCAIssuerCacheCreate(path);
287 if (kSecCAIssuerCache)
288 atexit(SecCAIssuerCacheGC);
289 }
290
291 /* Instance implemenation. */
292
293 static void _SecCAIssuerCacheAddCertificate(SecCAIssuerCacheRef this,
294 SecCertificateRef certificate,
295 CFURLRef uri, CFAbsoluteTime expires) {
296 int s3e;
297
298 secdebug("caissuercache", "adding certificate from %@", uri);
299 require_noerr(s3e = SecCAIssuerCacheEnsureTxn(this), errOut);
300
301 /* issuer.uri */
302 CFDataRef uriData;
303 require_action(uriData = CFURLCreateData(kCFAllocatorDefault, uri,
304 kCFStringEncodingUTF8, false), errOut, s3e = SQLITE_NOMEM);
305 s3e = sqlite3_bind_blob_wrapper(this->insertIssuer, 1,
306 CFDataGetBytePtr(uriData), CFDataGetLength(uriData), SQLITE_TRANSIENT);
307 CFRelease(uriData);
308
309 /* issuer.expires */
310 if (!s3e) s3e = sqlite3_bind_double(this->insertIssuer, 2, expires);
311
312 /* issuer.certificate */
313 if (!s3e) s3e = sqlite3_bind_blob_wrapper(this->insertIssuer, 3,
314 SecCertificateGetBytePtr(certificate),
315 SecCertificateGetLength(certificate), SQLITE_TRANSIENT);
316
317 /* Execute the insert statement. */
318 if (!s3e) s3e = sqlite3_step(this->insertIssuer);
319 require_noerr(s3e = sec_sqlite3_reset(this->insertIssuer, s3e), errOut);
320
321 errOut:
322 if (s3e) {
323 caissuerErrorLog("caissuer cache add failed: %s", sqlite3_errmsg(this->s3h));
324 /* TODO: Blow away the cache and create a new db. */
325 }
326 }
327
328 static SecCertificateRef _SecCAIssuerCacheCopyMatching(SecCAIssuerCacheRef this,
329 CFURLRef uri) {
330 SecCertificateRef certificate = NULL;
331 int s3e = SQLITE_OK;
332
333 CFDataRef uriData = NULL;
334 require(uriData = CFURLCreateData(kCFAllocatorDefault, uri,
335 kCFStringEncodingUTF8, false), errOut);
336 s3e = sqlite3_bind_blob_wrapper(this->selectIssuer, 1, CFDataGetBytePtr(uriData),
337 CFDataGetLength(uriData), SQLITE_TRANSIENT);
338 CFRelease(uriData);
339
340 if (!s3e) s3e = sqlite3_step(this->selectIssuer);
341 if (s3e == SQLITE_ROW) {
342 /* Found an entry! */
343 secdebug("caissuercache", "found cached response for %@", uri);
344
345 const void *respData = sqlite3_column_blob(this->selectIssuer, 0);
346 int respLen = sqlite3_column_bytes(this->selectIssuer, 0);
347 certificate = SecCertificateCreateWithBytes(NULL, respData, respLen);
348 }
349
350 require_noerr(s3e = sec_sqlite3_reset(this->selectIssuer, s3e), errOut);
351
352 errOut:
353 if (s3e) {
354 if (s3e != SQLITE_DONE) {
355 caissuerErrorLog("caissuer cache lookup failed: %s", sqlite3_errmsg(this->s3h));
356 /* TODO: Blow away the cache and create a new db. */
357 }
358
359 if (certificate) {
360 CFRelease(certificate);
361 certificate = NULL;
362 }
363 }
364
365 secdebug("caissuercache", "returning %s for %@", (certificate ? "cached response" : "NULL"), uri);
366
367 return certificate;
368 }
369
370 static void _SecCAIssuerCacheGC(void *context) {
371 SecCAIssuerCacheRef this = context;
372 int s3e;
373
374 require_noerr(s3e = SecCAIssuerCacheEnsureTxn(this), errOut);
375 secdebug("caissuercache", "expiring stale responses");
376 s3e = sqlite3_bind_double(this->expire, 1, CFAbsoluteTimeGetCurrent());
377 if (!s3e) s3e = sqlite3_step(this->expire);
378 require_noerr(s3e = sec_sqlite3_reset(this->expire, s3e), errOut);
379 require_noerr(s3e = SecCAIssuerCacheCommitTxn(this), errOut);
380
381 errOut:
382 if (s3e) {
383 caissuerErrorLog("caissuer cache expire failed: %s", sqlite3_errmsg(this->s3h));
384 /* TODO: Blow away the cache and create a new db. */
385 }
386 }
387
388 static void _SecCAIssuerCacheFlush(void *context) {
389 SecCAIssuerCacheRef this = context;
390 int s3e;
391
392 secdebug("caissuercache", "flushing pending changes");
393 s3e = SecCAIssuerCacheCommitTxn(this);
394
395 if (s3e) {
396 caissuerErrorLog("caissuer cache flush failed: %s", sqlite3_errmsg(this->s3h));
397 /* TODO: Blow away the cache and create a new db. */
398 }
399 }
400
401 /* Public API */
402
403 void SecCAIssuerCacheAddCertificate(SecCertificateRef certificate,
404 CFURLRef uri, CFAbsoluteTime expires) {
405 pthread_once(&kSecCAIssuerCacheOnce, SecCAIssuerCacheInit);
406 if (!kSecCAIssuerCache)
407 return;
408
409 _SecCAIssuerCacheAddCertificate(kSecCAIssuerCache, certificate, uri, expires);
410 }
411
412 SecCertificateRef SecCAIssuerCacheCopyMatching(CFURLRef uri) {
413 pthread_once(&kSecCAIssuerCacheOnce, SecCAIssuerCacheInit);
414 if (!kSecCAIssuerCache)
415 return NULL;
416
417 return _SecCAIssuerCacheCopyMatching(kSecCAIssuerCache, uri);
418 }
419
420 /* This should be called on a normal non emergency exit. This function
421 effectively does a SecCAIssuerCacheFlush.
422 Currently this is called from our atexit handeler.
423 This function expires any records that are stale and commits.
424
425 Idea for future cache management policies:
426 Expire old cache entires from database if:
427 - The time to do so has arrived based on the nextExpire date in the
428 policy table.
429 - If the size of the database exceeds the limit set in the maxSize field
430 in the policy table, vacuum the db. If the database is still too
431 big, expire records on a LRU basis.
432 */
433 void SecCAIssuerCacheGC(void) {
434 if (kSecCAIssuerCache)
435 _SecCAIssuerCacheGC(kSecCAIssuerCache);
436 }
437
438 /* Call this periodically or perhaps when we are exiting due to low memory. */
439 void SecCAIssuerCacheFlush(void) {
440 if (kSecCAIssuerCache)
441 _SecCAIssuerCacheFlush(kSecCAIssuerCache);
442 }