X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/5dd5f9ec28f304ca377c42fd7f711d6cf12b90e1..5c19dc3ae3bd8e40a9c028b0deddd50ff337692c:/OSX/sec/securityd/SecOCSPCache.c?ds=inline diff --git a/OSX/sec/securityd/SecOCSPCache.c b/OSX/sec/securityd/SecOCSPCache.c new file mode 100644 index 00000000..b70dcd1a --- /dev/null +++ b/OSX/sec/securityd/SecOCSPCache.c @@ -0,0 +1,403 @@ +/* + * Copyright (c) 2009-2010,2012-2015 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.) +} + +static bool _SecOCSPCacheExpireWithTransaction(SecDbConnectionRef dbconn, CFAbsoluteTime now, CFErrorRef *error) { + //if (now > nextExpireTime) + { + return SecDbWithSQL(dbconn, expireSQL, error, ^bool(sqlite3_stmt *expire) { + return SecDbBindDouble(expire, 1, now, error) && + SecDbStep(dbconn, expire, error, NULL); + }); + // TODO: Write now + expireDelay to nextExpireTime; + // currently we try to expire entries on each cache write + } +} + +/* Instance implementation. */ + +static void _SecOCSPCacheReplaceResponse(SecOCSPCacheRef this, + SecOCSPResponseRef oldResponse, SecOCSPResponseRef ocspResponse, + CFURLRef localResponderURI, CFAbsoluteTime verifyTime) { + secdebug("ocspcache", "adding response from %@", localResponderURI); + /* responses.ocspResponse */ + + // TODO: Update a latestProducedAt value using date in new entry, to ensure forward movement of time. + // Set "now" to the new producedAt we are receiving here if localTime is before this date. + // In addition whenever we run though here, check to see if "now" is more than past + // the nextCacheExpireDate and expire the cache if it is. + 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; + if (oldResponse && (responseId = SecOCSPResponseGetID(oldResponse)) >= 0) { + ok = SecDbWithSQL(dbconn, deleteResponseSQL, &localError, ^bool(sqlite3_stmt *deleteResponse) { + ok = SecDbBindInt64(deleteResponse, 1, responseId, &localError); + /* Execute the delete statement. */ + if (ok) + ok = SecDbStep(dbconn, deleteResponse, &localError, NULL); + return ok; + }); + } + + if (ok) 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, + verifyTime, + &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; + }); + + // Remove expired entries here. + // TODO: Consider only doing this once per 24 hours or something. + if (ok) ok = _SecOCSPCacheExpireWithTransaction(dbconn, verifyTime, &localError); + 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); +#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) + require(serial = SecCertificateCopySerialNumber(request->certificate, NULL), errOut); +#else + require(serial = SecCertificateCopySerialNumber(request->certificate), errOut); +#endif + + 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); + + /* Calculate 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)); + sqlite3_int64 responseID = sqlite3_column_int64(selectResponse, 1); + if (resp) { + SecOCSPResponseRef new_response = SecOCSPResponseCreateWithID(resp, responseID); + if (response) { + if (SecOCSPResponseProducedAt(response) < SecOCSPResponseProducedAt(new_response)) { + SecOCSPResponseFinalize(response); + response = new_response; + } else { + SecOCSPResponseFinalize(new_response); + } + } else { + response = new_response; + } + CFRelease(resp); + } +#if 0 + if (response) { + //sqlite3_int64 responseId = sqlite3_column_int64(this->selectResponse, 1); + /* @@@ Update the lastUsed field in the db. */ + } +#endif + }); + 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; +} + + +/* Public API */ + +void SecOCSPCacheReplaceResponse(SecOCSPResponseRef old_response, SecOCSPResponseRef response, + CFURLRef localResponderURI, CFAbsoluteTime verifyTime) { + SecOCSPCacheWith(^(SecOCSPCacheRef cache) { + _SecOCSPCacheReplaceResponse(cache, old_response, response, localResponderURI, verifyTime); + }); +} + +SecOCSPResponseRef SecOCSPCacheCopyMatching(SecOCSPRequestRef request, + CFURLRef localResponderURI /* may be NULL */) { + __block SecOCSPResponseRef response = NULL; + SecOCSPCacheWith(^(SecOCSPCacheRef cache) { + response = _SecOCSPCacheCopyMatching(cache, request, localResponderURI); + }); + return response; +}