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 <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#define caissuerErrorLog(args...) asl_log(NULL, NULL, ASL_LEVEL_ERR, ## args) 52 53static const char expireSQL[] = "DELETE FROM issuers WHERE expires<?"; 54static const char beginTxnSQL[] = "BEGIN EXCLUSIVE TRANSACTION"; 55static const char endTxnSQL[] = "COMMIT TRANSACTION"; 56static const char insertIssuerSQL[] = "INSERT OR REPLACE INTO issuers " 57 "(uri,expires,certificate) VALUES (?,?,?)"; 58static const char selectIssuerSQL[] = "SELECT certificate FROM " 59 "issuers WHERE uri=?"; 60 61#define kSecCAIssuerFileName "caissuercache.sqlite3" 62 63typedef struct __SecCAIssuerCache *SecCAIssuerCacheRef; 64struct __SecCAIssuerCache { 65 dispatch_queue_t queue; 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 75static dispatch_once_t kSecCAIssuerCacheOnce; 76static SecCAIssuerCacheRef kSecCAIssuerCache; 77 78/* @@@ Duplicated from SecTrustStore.c */ 79static 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 119static 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 135static 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 148static 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 169static 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 190static SecCAIssuerCacheRef SecCAIssuerCacheCreate(const char *db_name) { 191 SecCAIssuerCacheRef this; 192 int s3e; 193 bool create = true; 194 195 require(this = (SecCAIssuerCacheRef)calloc(sizeof(struct __SecCAIssuerCache), 1), errOut); 196 require_action_quiet((this->queue = dispatch_queue_create("caissuercache", 0)), errOut, s3e = errSecAllocate); 197 require_noerr(s3e = sec_sqlite3_open(db_name, &this->s3h, create), errOut); 198 this->in_transaction = false; 199 200 s3e = sqlite3_prepare_v2(this->s3h, beginTxnSQL, sizeof(beginTxnSQL), 201 &this->beginTxn, NULL); 202 require_noerr(s3e, errOut); 203 s3e = sqlite3_prepare_v2(this->s3h, endTxnSQL, sizeof(endTxnSQL), 204 &this->endTxn, NULL); 205 require_noerr(s3e, errOut); 206 207 s3e = sqlite3_prepare_v2(this->s3h, expireSQL, sizeof(expireSQL), 208 &this->expire, NULL); 209 if (create && s3e == SQLITE_ERROR) { 210 s3e = SecCAIssuerCacheEnsureTxn(this); 211 require_noerr(s3e, errOut); 212 213 /* sqlite3_prepare returns SQLITE_ERROR if the table we are 214 compiling this statement for doesn't exist. */ 215 char *errmsg = NULL; 216 s3e = sqlite3_exec(this->s3h, 217 "CREATE TABLE issuers(" 218 "uri BLOB PRIMARY KEY," 219 "expires DOUBLE NOT NULL," 220 "certificate BLOB NOT NULL" 221 ");" 222 "CREATE INDEX iexpires ON issuers(expires);" 223 , NULL, NULL, &errmsg); 224 if (errmsg) { 225 caissuerErrorLog("caissuer db CREATE TABLES: %s", errmsg); 226 sqlite3_free(errmsg); 227 } 228 require_noerr(s3e, errOut); 229 s3e = sqlite3_prepare_v2(this->s3h, expireSQL, sizeof(expireSQL), 230 &this->expire, NULL); 231 } 232 require_noerr(s3e, errOut); 233 s3e = sqlite3_prepare_v2(this->s3h, insertIssuerSQL, sizeof(insertIssuerSQL), 234 &this->insertIssuer, NULL); 235 require_noerr(s3e, errOut); 236 s3e = sqlite3_prepare_v2(this->s3h, selectIssuerSQL, sizeof(selectIssuerSQL), 237 &this->selectIssuer, NULL); 238 require_noerr(s3e, errOut); 239 240 return this; 241 242errOut: 243 if (this) { 244 if (this->queue) 245 dispatch_release(this->queue); 246 if (this->s3h) 247 sqlite3_close(this->s3h); 248 free(this); 249 } 250 251 return NULL; 252} 253 254static void SecCAIssuerCacheInit(void) { 255 WithPathInKeychainDirectory(CFSTR(kSecCAIssuerFileName), ^(const char *utf8String) { 256 kSecCAIssuerCache = SecCAIssuerCacheCreate(utf8String); 257 }); 258 259 if (kSecCAIssuerCache) 260 atexit(SecCAIssuerCacheGC); 261} 262 263/* Instance implemenation. */ 264 265static void _SecCAIssuerCacheAddCertificate(SecCAIssuerCacheRef this, 266 SecCertificateRef certificate, 267 CFURLRef uri, CFAbsoluteTime expires) { 268 int s3e; 269 270 secdebug("caissuercache", "adding certificate from %@", uri); 271 require_noerr(s3e = SecCAIssuerCacheEnsureTxn(this), errOut); 272 273 /* issuer.uri */ 274 CFDataRef uriData; 275 require_action(uriData = CFURLCreateData(kCFAllocatorDefault, uri, 276 kCFStringEncodingUTF8, false), errOut, s3e = SQLITE_NOMEM); 277 s3e = sqlite3_bind_blob_wrapper(this->insertIssuer, 1, 278 CFDataGetBytePtr(uriData), CFDataGetLength(uriData), SQLITE_TRANSIENT); 279 CFRelease(uriData); 280 281 /* issuer.expires */ 282 if (!s3e) s3e = sqlite3_bind_double(this->insertIssuer, 2, expires); 283 284 /* issuer.certificate */ 285 if (!s3e) s3e = sqlite3_bind_blob_wrapper(this->insertIssuer, 3, 286 SecCertificateGetBytePtr(certificate), 287 SecCertificateGetLength(certificate), SQLITE_TRANSIENT); 288 289 /* Execute the insert statement. */ 290 if (!s3e) s3e = sqlite3_step(this->insertIssuer); 291 require_noerr(s3e = sec_sqlite3_reset(this->insertIssuer, s3e), errOut); 292 293errOut: 294 if (s3e) { 295 caissuerErrorLog("caissuer cache add failed: %s", sqlite3_errmsg(this->s3h)); 296 /* TODO: Blow away the cache and create a new db. */ 297 } 298} 299 300static SecCertificateRef _SecCAIssuerCacheCopyMatching(SecCAIssuerCacheRef this, 301 CFURLRef uri) { 302 SecCertificateRef certificate = NULL; 303 int s3e = SQLITE_OK; 304 305 CFDataRef uriData = NULL; 306 require(uriData = CFURLCreateData(kCFAllocatorDefault, uri, 307 kCFStringEncodingUTF8, false), errOut); 308 s3e = sqlite3_bind_blob_wrapper(this->selectIssuer, 1, CFDataGetBytePtr(uriData), 309 CFDataGetLength(uriData), SQLITE_TRANSIENT); 310 CFRelease(uriData); 311 312 if (!s3e) s3e = sqlite3_step(this->selectIssuer); 313 if (s3e == SQLITE_ROW) { 314 /* Found an entry! */ 315 secdebug("caissuercache", "found cached response for %@", uri); 316 317 const void *respData = sqlite3_column_blob(this->selectIssuer, 0); 318 int respLen = sqlite3_column_bytes(this->selectIssuer, 0); 319 certificate = SecCertificateCreateWithBytes(NULL, respData, respLen); 320 } 321 322 require_noerr(s3e = sec_sqlite3_reset(this->selectIssuer, s3e), errOut); 323 324errOut: 325 if (s3e) { 326 if (s3e != SQLITE_DONE) { 327 caissuerErrorLog("caissuer cache lookup failed: %s", sqlite3_errmsg(this->s3h)); 328 /* TODO: Blow away the cache and create a new db. */ 329 } 330 331 if (certificate) { 332 CFRelease(certificate); 333 certificate = NULL; 334 } 335 } 336 337 secdebug("caissuercache", "returning %s for %@", (certificate ? "cached response" : "NULL"), uri); 338 339 return certificate; 340} 341 342static void _SecCAIssuerCacheGC(void *context) { 343 SecCAIssuerCacheRef this = context; 344 int s3e; 345 346 require_noerr(s3e = SecCAIssuerCacheEnsureTxn(this), errOut); 347 secdebug("caissuercache", "expiring stale responses"); 348 s3e = sqlite3_bind_double(this->expire, 1, CFAbsoluteTimeGetCurrent()); 349 if (!s3e) s3e = sqlite3_step(this->expire); 350 require_noerr(s3e = sec_sqlite3_reset(this->expire, s3e), errOut); 351 require_noerr(s3e = SecCAIssuerCacheCommitTxn(this), errOut); 352 353errOut: 354 if (s3e) { 355 caissuerErrorLog("caissuer cache expire failed: %s", sqlite3_errmsg(this->s3h)); 356 /* TODO: Blow away the cache and create a new db. */ 357 } 358} 359 360static void _SecCAIssuerCacheFlush(void *context) { 361 SecCAIssuerCacheRef this = context; 362 int s3e; 363 364 secdebug("caissuercache", "flushing pending changes"); 365 s3e = SecCAIssuerCacheCommitTxn(this); 366 367 if (s3e) { 368 caissuerErrorLog("caissuer cache flush failed: %s", sqlite3_errmsg(this->s3h)); 369 /* TODO: Blow away the cache and create a new db. */ 370 } 371} 372 373/* Public API */ 374 375void SecCAIssuerCacheAddCertificate(SecCertificateRef certificate, 376 CFURLRef uri, CFAbsoluteTime expires) { 377 dispatch_once(&kSecCAIssuerCacheOnce, ^{ 378 SecCAIssuerCacheInit(); 379 }); 380 if (!kSecCAIssuerCache) 381 return; 382 383 dispatch_sync(kSecCAIssuerCache->queue, ^{ 384 _SecCAIssuerCacheAddCertificate(kSecCAIssuerCache, certificate, uri, expires); 385 }); 386} 387 388SecCertificateRef SecCAIssuerCacheCopyMatching(CFURLRef uri) { 389 dispatch_once(&kSecCAIssuerCacheOnce, ^{ 390 SecCAIssuerCacheInit(); 391 }); 392 __block SecCertificateRef cert = NULL; 393 if (kSecCAIssuerCache) 394 dispatch_sync(kSecCAIssuerCache->queue, ^{ 395 cert = _SecCAIssuerCacheCopyMatching(kSecCAIssuerCache, uri); 396 }); 397 return cert; 398} 399 400/* This should be called on a normal non emergency exit. This function 401 effectively does a SecCAIssuerCacheFlush. 402 Currently this is called from our atexit handeler. 403 This function expires any records that are stale and commits. 404 405 Idea for future cache management policies: 406 Expire old cache entires from database if: 407 - The time to do so has arrived based on the nextExpire date in the 408 policy table. 409 - If the size of the database exceeds the limit set in the maxSize field 410 in the policy table, vacuum the db. If the database is still too 411 big, expire records on a LRU basis. 412 */ 413void SecCAIssuerCacheGC(void) { 414 if (kSecCAIssuerCache) 415 dispatch_sync(kSecCAIssuerCache->queue, ^{ 416 _SecCAIssuerCacheGC(kSecCAIssuerCache); 417 }); 418} 419 420/* Call this periodically or perhaps when we are exiting due to low memory. */ 421void SecCAIssuerCacheFlush(void) { 422 if (kSecCAIssuerCache) 423 dispatch_sync(kSecCAIssuerCache->queue, ^{ 424 _SecCAIssuerCacheFlush(kSecCAIssuerCache); 425 }); 426} 427