X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/Security/sec/securityd/SecOCSPCache.c?ds=inline diff --git a/Security/sec/securityd/SecOCSPCache.c b/Security/sec/securityd/SecOCSPCache.c new file mode 100644 index 00000000..8d415fa5 --- /dev/null +++ b/Security/sec/securityd/SecOCSPCache.c @@ -0,0 +1,403 @@ +/* + * Copyright (c) 2009-2010,2012-2014 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +/* + * SecOCSPCache.c - securityd + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "utilities/SecDb.h" +#include "utilities/SecFileLocations.h" +#include "utilities/iOSforOSX.h" + +#define expireSQL CFSTR("DELETE FROM responses WHERE expiresdb = SecOCSPCacheDbCreate(db_name), errOut); + + return this; + +errOut: + if (this) { + CFReleaseSafe(this->db); + free(this); + } + + return NULL; +} + +static CFStringRef SecOCSPCacheCopyPath(void) { + CFStringRef ocspRelPath = kSecOCSPCacheFileName; + CFURLRef ocspURL = SecCopyURLForFileInKeychainDirectory(ocspRelPath); + CFStringRef ocspPath = NULL; + if (ocspURL) { + ocspPath = CFURLCopyFileSystemPath(ocspURL, kCFURLPOSIXPathStyle); + CFRelease(ocspURL); + } + return ocspPath; +} + +static void SecOCSPCacheWith(void(^cacheJob)(SecOCSPCacheRef cache)) { + dispatch_once(&kSecOCSPCacheOnce, ^{ + CFStringRef dbPath = SecOCSPCacheCopyPath(); + if (dbPath) { + kSecOCSPCache = SecOCSPCacheCreate(dbPath); + CFRelease(dbPath); + } + }); + // Do pre job run work here (cancel idle timers etc.) + cacheJob(kSecOCSPCache); + // Do post job run work here (gc timer, etc.) +} + +/* Instance implementation. */ + +static void _SecOCSPCacheAddResponse(SecOCSPCacheRef this, + SecOCSPResponseRef ocspResponse, CFURLRef localResponderURI) { + secdebug("ocspcache", "adding response from %@", localResponderURI); + /* responses.ocspResponse */ + CFDataRef responseData = SecOCSPResponseGetData(ocspResponse); + __block CFErrorRef localError = NULL; + __block bool ok = true; + ok &= SecDbPerformWrite(this->db, &localError, ^(SecDbConnectionRef dbconn) { + ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) { + __block sqlite3_int64 responseId; + ok = SecDbWithSQL(dbconn, insertResponseSQL, &localError, ^bool(sqlite3_stmt *insertResponse) { + if (ok) + ok = SecDbBindBlob(insertResponse, 1, + CFDataGetBytePtr(responseData), + CFDataGetLength(responseData), + SQLITE_TRANSIENT, &localError); + + /* responses.responderURI */ + if (ok) { + CFDataRef uriData = NULL; + if (localResponderURI) { + uriData = CFURLCreateData(kCFAllocatorDefault, localResponderURI, + kCFStringEncodingUTF8, false); + } + if (uriData) { + ok = SecDbBindBlob(insertResponse, 2, + CFDataGetBytePtr(uriData), + CFDataGetLength(uriData), + SQLITE_TRANSIENT, &localError); + CFRelease(uriData); + } else { + // Since we use SecDbClearBindings this shouldn't be needed. + //ok = SecDbBindNull(insertResponse, 2, &localError); + } + } + /* responses.expires */ + if (ok) + ok = SecDbBindDouble(insertResponse, 3, + SecOCSPResponseGetExpirationTime(ocspResponse), + &localError); + /* responses.lastUsed */ + if (ok) + ok = SecDbBindDouble(insertResponse, 4, + SecOCSPResponseVerifyTime(ocspResponse), + &localError); + + /* Execute the insert statement. */ + if (ok) + ok = SecDbStep(dbconn, insertResponse, &localError, NULL); + + responseId = sqlite3_last_insert_rowid(SecDbHandle(dbconn)); + return ok; + }); + + /* Now add a link record for every singleResponse in the ocspResponse. */ + if (ok) ok = SecDbWithSQL(dbconn, insertLinkSQL, &localError, ^bool(sqlite3_stmt *insertLink) { + SecAsn1OCSPSingleResponse **responses; + for (responses = ocspResponse->responseData.responses; + *responses; ++responses) { + SecAsn1OCSPSingleResponse *resp = *responses; + SecAsn1OCSPCertID *certId = &resp->certID; + if (ok) ok = SecDbBindBlob(insertLink, 1, + certId->algId.algorithm.Data, + certId->algId.algorithm.Length, + SQLITE_TRANSIENT, &localError); + if (ok) ok = SecDbBindBlob(insertLink, 2, + certId->issuerNameHash.Data, + certId->issuerNameHash.Length, + SQLITE_TRANSIENT, &localError); + if (ok) ok = SecDbBindBlob(insertLink, 3, + certId->issuerPubKeyHash.Data, + certId->issuerPubKeyHash.Length, + SQLITE_TRANSIENT, &localError); + if (ok) ok = SecDbBindBlob(insertLink, 4, + certId->serialNumber.Data, + certId->serialNumber.Length, + SQLITE_TRANSIENT, &localError); + if (ok) ok = SecDbBindInt64(insertLink, 5, responseId, &localError); + + /* Execute the insert statement. */ + if (ok) ok = SecDbStep(dbconn, insertLink, &localError, NULL); + if (ok) ok = SecDbReset(insertLink, &localError); + } + return ok; + }); + if (!ok) + *commit = false; + }); + }); + if (!ok) { + secerror("_SecOCSPCacheAddResponse failed: %@", localError); + } + CFReleaseSafe(localError); +} + +static SecOCSPResponseRef _SecOCSPCacheCopyMatching(SecOCSPCacheRef this, + SecOCSPRequestRef request, CFURLRef responderURI) { + const DERItem *publicKey; + CFDataRef issuer = NULL; + CFDataRef serial = NULL; + __block SecOCSPResponseRef response = NULL; + __block CFErrorRef localError = NULL; + __block bool ok = true; + + require(publicKey = SecCertificateGetPublicKeyData(request->issuer), errOut); + require(issuer = SecCertificateCopyIssuerSequence(request->certificate), errOut); + require(serial = SecCertificateCopySerialNumber(request->certificate), errOut); + + ok &= SecDbPerformRead(this->db, &localError, ^(SecDbConnectionRef dbconn) { + ok &= SecDbWithSQL(dbconn, selectHashAlgorithmSQL, &localError, ^bool(sqlite3_stmt *selectHash) { + ok = SecDbBindBlob(selectHash, 1, CFDataGetBytePtr(serial), CFDataGetLength(serial), SQLITE_TRANSIENT, &localError); + ok &= SecDbStep(dbconn, selectHash, &localError, ^(bool *stopHash) { + SecAsn1Oid algorithm; + algorithm.Data = (uint8_t *)sqlite3_column_blob(selectHash, 0); + algorithm.Length = sqlite3_column_bytes(selectHash, 0); + + /* Calcluate the issuerKey and issuerName digests using the returned + hashAlgorithm. */ + CFDataRef issuerNameHash = SecDigestCreate(kCFAllocatorDefault, + &algorithm, NULL, CFDataGetBytePtr(issuer), CFDataGetLength(issuer)); + CFDataRef issuerPubKeyHash = SecDigestCreate(kCFAllocatorDefault, + &algorithm, NULL, publicKey->data, publicKey->length); + + if (issuerNameHash && issuerPubKeyHash && ok) ok &= SecDbWithSQL(dbconn, selectResponseSQL, &localError, ^bool(sqlite3_stmt *selectResponse) { + /* Now we have the serial, algorithm, issuerNameHash and + issuerPubKeyHash so let's lookup the db entry. */ + if (ok) ok = SecDbBindBlob(selectResponse, 1, CFDataGetBytePtr(issuerNameHash), + CFDataGetLength(issuerNameHash), SQLITE_TRANSIENT, &localError); + if (ok) ok = SecDbBindBlob(selectResponse, 2, CFDataGetBytePtr(issuerPubKeyHash), + CFDataGetLength(issuerPubKeyHash), SQLITE_TRANSIENT, &localError); + if (ok) ok = SecDbBindBlob(selectResponse, 3, CFDataGetBytePtr(serial), + CFDataGetLength(serial), SQLITE_TRANSIENT, &localError); + if (ok) ok = SecDbBindBlob(selectResponse, 4, algorithm.Data, + algorithm.Length, SQLITE_TRANSIENT, &localError); + if (ok) ok &= SecDbStep(dbconn, selectResponse, &localError, ^(bool *stopResponse) { + /* Found an entry! */ + secdebug("ocspcache", "found cached response"); + CFDataRef resp = CFDataCreate(kCFAllocatorDefault, + sqlite3_column_blob(selectResponse, 0), + sqlite3_column_bytes(selectResponse, 0)); + if (resp) { + response = SecOCSPResponseCreate(resp, NULL_TIME); + CFRelease(resp); + } + if (response) { + //sqlite3_int64 responseId = sqlite3_column_int64(this->selectResponse, 1); + /* @@@ Update the lastUsed field in the db. */ + } + }); + return ok; + }); + + CFReleaseSafe(issuerNameHash); + CFReleaseSafe(issuerPubKeyHash); + }); + return ok; + }); + }); + +errOut: + CFReleaseSafe(serial); + CFReleaseSafe(issuer); + + if (!ok) { + secerror("ocsp cache lookup failed: %@", localError); + if (response) { + SecOCSPResponseFinalize(response); + response = NULL; + } + } + CFReleaseSafe(localError); + + secdebug("ocspcache", "returning %s", (response ? "cached response" : "NULL")); + + return response; +} + +static void _SecOCSPCacheGC(SecOCSPCacheRef this) { + secdebug("ocspcache", "expiring stale responses"); + + __block CFErrorRef localError = NULL; + __block bool ok = true; + ok &= SecDbPerformWrite(this->db, &localError, ^(SecDbConnectionRef dbconn) { + ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) { + ok &= SecDbWithSQL(dbconn, expireSQL, &localError, ^bool(sqlite3_stmt *expire) { + return SecDbBindDouble(expire, 1, CFAbsoluteTimeGetCurrent(), &localError) && + SecDbStep(dbconn, expire, &localError, NULL); + }); + *commit = ok; + }); + }); + + if (!ok) { + secerror("ocsp cache expire failed: %@", localError); + } + CFReleaseSafe(localError); +} + +static void _SecOCSPCacheFlush(SecOCSPCacheRef this) { + secdebug("ocspcache", "flushing pending changes"); + // NOOP since we use WAL now and commit right away. +} + +/* Public API */ + +void SecOCSPCacheAddResponse(SecOCSPResponseRef response, + CFURLRef localResponderURI) { + SecOCSPCacheWith(^(SecOCSPCacheRef cache) { + _SecOCSPCacheAddResponse(cache, response, localResponderURI); + _SecOCSPCacheGC(cache); /* delete expired entries */ + }); +} + +SecOCSPResponseRef SecOCSPCacheCopyMatching(SecOCSPRequestRef request, + CFURLRef localResponderURI /* may be NULL */) { + __block SecOCSPResponseRef response = NULL; + SecOCSPCacheWith(^(SecOCSPCacheRef cache) { + response = _SecOCSPCacheCopyMatching(cache, request, localResponderURI); + }); + return response; +} + +/* This should be called on a normal non emergency exit. This function + effectively does a SecOCSPCacheFlush. + Currently this is called from our atexit handeler. + This function expires any records that are stale and commits. + + Idea for future cache management policies: + Expire old cache entires from database if: + - The time to do so has arrived based on the nextExpire date in the + policy table. + - If the size of the database exceeds the limit set in the maxSize field + in the policy table, vacuum the db. If the database is still too + big, expire records on a LRU basis. + */ +void SecOCSPCacheGC(void) { + if (kSecOCSPCache) + _SecOCSPCacheGC(kSecOCSPCache); +} + +/* Call this periodically or perhaps when we are exiting due to low memory. */ +void SecOCSPCacheFlush(void) { + if (kSecOCSPCache) + _SecOCSPCacheFlush(kSecOCSPCache); +}