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