]> git.saurik.com Git - apple/security.git/blob - OSX/sec/securityd/SecOCSPCache.c
Security-59306.11.20.tar.gz
[apple/security.git] / OSX / sec / securityd / SecOCSPCache.c
1 /*
2 * Copyright (c) 2009-2010,2012-2017 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 * SecOCSPCache.c - securityd
26 */
27
28 #include <CoreFoundation/CFUtilities.h>
29 #include <CoreFoundation/CFString.h>
30 #include <securityd/SecOCSPCache.h>
31 #include <securityd/SecTrustLoggingServer.h>
32 #include <utilities/debugging.h>
33 #include <Security/SecCertificateInternal.h>
34 #include <Security/SecFramework.h>
35 #include <Security/SecInternal.h>
36 #include <AssertMacros.h>
37 #include <stdlib.h>
38 #include <limits.h>
39 #include <sys/stat.h>
40 #include <asl.h>
41 #include "utilities/SecCFWrappers.h"
42 #include "utilities/SecDb.h"
43 #include "utilities/SecFileLocations.h"
44 #include "utilities/iOSforOSX.h"
45
46 /* Note that lastUsed is actually time of insert because we don't
47 refresh lastUsed on each SELECT. */
48
49 #define flushSQL CFSTR("DELETE FROM responses")
50 #define expireSQL CFSTR("DELETE FROM responses WHERE expires<?")
51 #define insertResponseSQL CFSTR("INSERT INTO responses " \
52 "(ocspResponse,responderURI,expires,lastUsed) VALUES (?,?,?,?)")
53 #define insertLinkSQL CFSTR("INSERT INTO ocsp (hashAlgorithm," \
54 "issuerNameHash,issuerPubKeyHash,serialNum,responseId) VALUES (?,?,?,?,?)")
55 #define deleteResponseSQL CFSTR("DELETE FROM responses WHERE responseId=?")
56 #define selectHashAlgorithmSQL CFSTR("SELECT DISTINCT hashAlgorithm " \
57 "FROM ocsp WHERE serialNum=?")
58 #define selectResponseSQL CFSTR("SELECT ocspResponse,responseId FROM " \
59 "responses WHERE lastUsed>? AND responseId=(SELECT responseId FROM ocsp WHERE " \
60 "issuerNameHash=? AND issuerPubKeyHash=? AND serialNum=? AND hashAlgorithm=?)" \
61 " ORDER BY expires DESC")
62
63 #define kSecOCSPCacheFileName CFSTR("ocspcache.sqlite3")
64
65
66 // MARK; -
67 // MARK: SecOCSPCacheDb
68
69 static SecDbRef SecOCSPCacheDbCreate(CFStringRef path) {
70 return SecDbCreate(path, 0600, true, true, true, true, 1,
71 ^bool (SecDbRef db, SecDbConnectionRef dbconn, bool didCreate, bool *callMeAgainForNextConnection, CFErrorRef *error) {
72 __block bool ok = true;
73
74 CFErrorRef localError = NULL;
75 if (!SecDbWithSQL(dbconn, selectHashAlgorithmSQL /* expireSQL */, &localError, NULL) && CFErrorGetCode(localError) == SQLITE_ERROR) {
76 /* SecDbWithSQL returns SQLITE_ERROR if the table we are preparing the above statement for doesn't exist. */
77 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, error, ^(bool *commit) {
78 ok &= SecDbExec(dbconn,
79 CFSTR("CREATE TABLE ocsp("
80 "issuerNameHash BLOB NOT NULL,"
81 "issuerPubKeyHash BLOB NOT NULL,"
82 "serialNum BLOB NOT NULL,"
83 "hashAlgorithm BLOB NOT NULL,"
84 "responseId INTEGER NOT NULL"
85 ");"
86 "CREATE INDEX iResponseId ON ocsp(responseId);"
87 "CREATE INDEX iserialNum ON ocsp(serialNum);"
88 "CREATE INDEX iSNumDAlg ON ocsp(serialNum,hashAlgorithm);"
89 "CREATE TABLE responses("
90 "responseId INTEGER PRIMARY KEY,"
91 "ocspResponse BLOB NOT NULL,"
92 "responderURI BLOB,"
93 "expires DOUBLE NOT NULL,"
94 "lastUsed DOUBLE NOT NULL"
95 ");"
96 "CREATE INDEX iexpires ON responses(expires);"
97 "CREATE TRIGGER tocspdel BEFORE DELETE ON responses FOR EACH ROW "
98 "BEGIN "
99 "DELETE FROM ocsp WHERE responseId=OLD.responseId;"
100 " END;"), error);
101 *commit = ok;
102 });
103 }
104 CFReleaseSafe(localError);
105 if (!ok) {
106 secerror("%s failed: %@", didCreate ? "Create" : "Open", error ? *error : NULL);
107 CFIndex errCode = errSecInternalComponent;
108 if (error && *error) {
109 errCode = CFErrorGetCode(*error);
110 }
111 TrustdHealthAnalyticsLogErrorCodeForDatabase(TAOCSPCache,
112 didCreate ? TAOperationCreate : TAOperationOpen,
113 TAFatalError, errCode);
114 }
115 return ok;
116 });
117 }
118
119 // MARK; -
120 // MARK: SecOCSPCache
121
122 typedef struct __SecOCSPCache *SecOCSPCacheRef;
123 struct __SecOCSPCache {
124 SecDbRef db;
125 };
126
127 static dispatch_once_t kSecOCSPCacheOnce;
128 static SecOCSPCacheRef kSecOCSPCache = NULL;
129
130 static SecOCSPCacheRef SecOCSPCacheCreate(CFStringRef db_name) {
131 SecOCSPCacheRef this;
132
133 require(this = (SecOCSPCacheRef)malloc(sizeof(struct __SecOCSPCache)), errOut);
134 require(this->db = SecOCSPCacheDbCreate(db_name), errOut);
135
136 return this;
137
138 errOut:
139 if (this) {
140 CFReleaseSafe(this->db);
141 free(this);
142 }
143
144 return NULL;
145 }
146
147 static CFStringRef SecOCSPCacheCopyPath(void) {
148 CFStringRef ocspRelPath = kSecOCSPCacheFileName;
149 #if TARGET_OS_IPHONE
150 CFURLRef ocspURL = SecCopyURLForFileInKeychainDirectory(ocspRelPath);
151 if (!ocspURL) {
152 ocspURL = SecCopyURLForFileInUserCacheDirectory(ocspRelPath);
153 }
154 #else
155 /* macOS caches should be in user cache dir */
156 CFURLRef ocspURL = SecCopyURLForFileInUserCacheDirectory(ocspRelPath);
157 #endif
158 CFStringRef ocspPath = NULL;
159 if (ocspURL) {
160 ocspPath = CFURLCopyFileSystemPath(ocspURL, kCFURLPOSIXPathStyle);
161 CFRelease(ocspURL);
162 }
163 return ocspPath;
164 }
165
166 static void SecOCSPCacheWith(void(^cacheJob)(SecOCSPCacheRef cache)) {
167 dispatch_once(&kSecOCSPCacheOnce, ^{
168 CFStringRef dbPath = SecOCSPCacheCopyPath();
169 if (dbPath) {
170 kSecOCSPCache = SecOCSPCacheCreate(dbPath);
171 CFRelease(dbPath);
172 }
173 });
174 // Do pre job run work here (cancel idle timers etc.)
175 cacheJob(kSecOCSPCache);
176 // Do post job run work here (gc timer, etc.)
177 }
178
179 static bool _SecOCSPCacheExpireWithTransaction(SecDbConnectionRef dbconn, CFAbsoluteTime now, CFErrorRef *error) {
180 //if (now > nextExpireTime)
181 {
182 return SecDbWithSQL(dbconn, expireSQL, error, ^bool(sqlite3_stmt *expire) {
183 return SecDbBindDouble(expire, 1, now, error) &&
184 SecDbStep(dbconn, expire, error, NULL);
185 });
186 // TODO: Write now + expireDelay to nextExpireTime;
187 // currently we try to expire entries on each cache write
188 }
189 }
190
191 /* Instance implementation. */
192
193 static void _SecOCSPCacheReplaceResponse(SecOCSPCacheRef this,
194 SecOCSPResponseRef oldResponse, SecOCSPResponseRef ocspResponse,
195 CFURLRef localResponderURI, CFAbsoluteTime verifyTime) {
196 secdebug("ocspcache", "adding response from %@", localResponderURI);
197 /* responses.ocspResponse */
198
199 // TODO: Update a latestProducedAt value using date in new entry, to ensure forward movement of time.
200 // Set "now" to the new producedAt we are receiving here if localTime is before this date.
201 // In addition whenever we run though here, check to see if "now" is more than past
202 // the nextCacheExpireDate and expire the cache if it is.
203 CFDataRef responseData = SecOCSPResponseGetData(ocspResponse);
204 __block CFErrorRef localError = NULL;
205 __block bool ok = true;
206 ok &= SecDbPerformWrite(this->db, &localError, ^(SecDbConnectionRef dbconn) {
207 ok &= SecDbTransaction(dbconn, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
208 __block sqlite3_int64 responseId;
209 if (oldResponse && (responseId = SecOCSPResponseGetID(oldResponse)) >= 0) {
210 ok &= SecDbWithSQL(dbconn, deleteResponseSQL, &localError, ^bool(sqlite3_stmt *deleteResponse) {
211 ok &= SecDbBindInt64(deleteResponse, 1, responseId, &localError);
212 /* Execute the delete statement. */
213 ok &= SecDbStep(dbconn, deleteResponse, &localError, NULL);
214 return ok;
215 });
216 }
217
218 ok &= SecDbWithSQL(dbconn, insertResponseSQL, &localError, ^bool(sqlite3_stmt *insertResponse) {
219 ok &= SecDbBindBlob(insertResponse, 1,
220 CFDataGetBytePtr(responseData),
221 CFDataGetLength(responseData),
222 SQLITE_TRANSIENT, &localError);
223
224 /* responses.responderURI */
225 if (ok) {
226 CFDataRef uriData = NULL;
227 if (localResponderURI) {
228 uriData = CFURLCreateData(kCFAllocatorDefault, localResponderURI,
229 kCFStringEncodingUTF8, false);
230 }
231 if (uriData) {
232 ok = SecDbBindBlob(insertResponse, 2,
233 CFDataGetBytePtr(uriData),
234 CFDataGetLength(uriData),
235 SQLITE_TRANSIENT, &localError);
236 CFRelease(uriData);
237 }
238 }
239 /* responses.expires */
240 ok &= SecDbBindDouble(insertResponse, 3,
241 SecOCSPResponseGetExpirationTime(ocspResponse),
242 &localError);
243 /* responses.lastUsed */
244 ok &= SecDbBindDouble(insertResponse, 4,
245 verifyTime,
246 &localError);
247
248 /* Execute the insert statement. */
249 ok &= SecDbStep(dbconn, insertResponse, &localError, NULL);
250
251 responseId = sqlite3_last_insert_rowid(SecDbHandle(dbconn));
252 return ok;
253 });
254
255 /* Now add a link record for every singleResponse in the ocspResponse. */
256 ok &= SecDbWithSQL(dbconn, insertLinkSQL, &localError, ^bool(sqlite3_stmt *insertLink) {
257 SecAsn1OCSPSingleResponse **responses;
258 for (responses = ocspResponse->responseData.responses;
259 *responses; ++responses) {
260 SecAsn1OCSPSingleResponse *resp = *responses;
261 SecAsn1OCSPCertID *certId = &resp->certID;
262 ok &= SecDbBindBlob(insertLink, 1,
263 certId->algId.algorithm.Data,
264 certId->algId.algorithm.Length,
265 SQLITE_TRANSIENT, &localError);
266 ok &= SecDbBindBlob(insertLink, 2,
267 certId->issuerNameHash.Data,
268 certId->issuerNameHash.Length,
269 SQLITE_TRANSIENT, &localError);
270 ok &= SecDbBindBlob(insertLink, 3,
271 certId->issuerPubKeyHash.Data,
272 certId->issuerPubKeyHash.Length,
273 SQLITE_TRANSIENT, &localError);
274 ok &= SecDbBindBlob(insertLink, 4,
275 certId->serialNumber.Data,
276 certId->serialNumber.Length,
277 SQLITE_TRANSIENT, &localError);
278 ok &= SecDbBindInt64(insertLink, 5, responseId, &localError);
279
280 /* Execute the insert statement. */
281 ok &= SecDbStep(dbconn, insertLink, &localError, NULL);
282 ok &= SecDbReset(insertLink, &localError);
283 }
284 return ok;
285 });
286
287 // Remove expired entries here.
288 // TODO: Consider only doing this once per 24 hours or something.
289 ok &= _SecOCSPCacheExpireWithTransaction(dbconn, verifyTime, &localError);
290 if (!ok)
291 *commit = false;
292 });
293 });
294 if (!ok) {
295 secerror("_SecOCSPCacheAddResponse failed: %@", localError);
296 TrustdHealthAnalyticsLogErrorCodeForDatabase(TAOCSPCache, TAOperationWrite, TAFatalError,
297 localError ? CFErrorGetCode(localError) : errSecInternalComponent);
298 CFReleaseNull(localError);
299 }
300 CFReleaseSafe(localError);
301 }
302
303 static SecOCSPResponseRef _SecOCSPCacheCopyMatching(SecOCSPCacheRef this,
304 SecOCSPRequestRef request, CFURLRef responderURI, CFAbsoluteTime minInsertTime) {
305 const DERItem *publicKey;
306 CFDataRef issuer = NULL;
307 CFDataRef serial = NULL;
308 __block SecOCSPResponseRef response = NULL;
309 __block CFErrorRef localError = NULL;
310 __block bool ok = true;
311
312 require(publicKey = SecCertificateGetPublicKeyData(request->issuer), errOut);
313 require(issuer = SecCertificateCopyIssuerSequence(request->certificate), errOut);
314 require(serial = SecCertificateCopySerialNumberData(request->certificate, NULL), errOut);
315
316 ok &= SecDbPerformRead(this->db, &localError, ^(SecDbConnectionRef dbconn) {
317 ok &= SecDbWithSQL(dbconn, selectHashAlgorithmSQL, &localError, ^bool(sqlite3_stmt *selectHash) {
318 ok = SecDbBindBlob(selectHash, 1, CFDataGetBytePtr(serial), CFDataGetLength(serial), SQLITE_TRANSIENT, &localError);
319 ok &= SecDbStep(dbconn, selectHash, &localError, ^(bool *stopHash) {
320 SecAsn1Oid algorithm;
321 algorithm.Data = (uint8_t *)sqlite3_column_blob(selectHash, 0);
322 algorithm.Length = sqlite3_column_bytes(selectHash, 0);
323
324 /* Calculate the issuerKey and issuerName digests using the returned
325 hashAlgorithm. */
326 CFDataRef issuerNameHash = SecDigestCreate(kCFAllocatorDefault,
327 &algorithm, NULL, CFDataGetBytePtr(issuer), CFDataGetLength(issuer));
328 CFDataRef issuerPubKeyHash = SecDigestCreate(kCFAllocatorDefault,
329 &algorithm, NULL, publicKey->data, publicKey->length);
330
331 if (issuerNameHash && issuerPubKeyHash && ok) ok &= SecDbWithSQL(dbconn, selectResponseSQL, &localError, ^bool(sqlite3_stmt *selectResponse) {
332 /* Now we have the serial, algorithm, issuerNameHash and
333 issuerPubKeyHash so let's lookup the db entry. */
334 ok &= SecDbBindDouble(selectResponse, 1, minInsertTime, &localError);
335 ok &= SecDbBindBlob(selectResponse, 2, CFDataGetBytePtr(issuerNameHash),
336 CFDataGetLength(issuerNameHash), SQLITE_TRANSIENT, &localError);
337 ok &= SecDbBindBlob(selectResponse, 3, CFDataGetBytePtr(issuerPubKeyHash),
338 CFDataGetLength(issuerPubKeyHash), SQLITE_TRANSIENT, &localError);
339 ok &= SecDbBindBlob(selectResponse, 4, CFDataGetBytePtr(serial),
340 CFDataGetLength(serial), SQLITE_TRANSIENT, &localError);
341 ok &= SecDbBindBlob(selectResponse, 5, algorithm.Data,
342 algorithm.Length, SQLITE_TRANSIENT, &localError);
343 ok &= SecDbStep(dbconn, selectResponse, &localError, ^(bool *stopResponse) {
344 /* Found an entry! */
345 secdebug("ocspcache", "found cached response");
346 CFDataRef resp = CFDataCreate(kCFAllocatorDefault,
347 sqlite3_column_blob(selectResponse, 0),
348 sqlite3_column_bytes(selectResponse, 0));
349 sqlite3_int64 responseID = sqlite3_column_int64(selectResponse, 1);
350 if (resp) {
351 SecOCSPResponseRef new_response = SecOCSPResponseCreateWithID(resp, responseID);
352 if (response) {
353 if (SecOCSPResponseProducedAt(response) < SecOCSPResponseProducedAt(new_response)) {
354 SecOCSPResponseFinalize(response);
355 response = new_response;
356 } else {
357 SecOCSPResponseFinalize(new_response);
358 }
359 } else {
360 response = new_response;
361 }
362 CFRelease(resp);
363 }
364 });
365 return ok;
366 });
367
368 CFReleaseSafe(issuerNameHash);
369 CFReleaseSafe(issuerPubKeyHash);
370 });
371 return ok;
372 });
373 });
374
375 errOut:
376 CFReleaseSafe(serial);
377 CFReleaseSafe(issuer);
378
379 if (!ok || localError) {
380 secerror("ocsp cache lookup failed: %@", localError);
381 if (response) {
382 SecOCSPResponseFinalize(response);
383 response = NULL;
384 }
385 TrustdHealthAnalyticsLogErrorCodeForDatabase(TAOCSPCache, TAOperationRead, TAFatalError,
386 localError ? CFErrorGetCode(localError) : errSecInternalComponent);
387 }
388 CFReleaseSafe(localError);
389
390 secdebug("ocspcache", "returning %s", (response ? "cached response" : "NULL"));
391
392 return response;
393 }
394
395 static bool _SecOCSPCacheFlush(SecOCSPCacheRef cache, CFErrorRef *error) {
396 __block CFErrorRef localError = NULL;
397 __block bool ok = true;
398
399 ok &= SecDbPerformWrite(cache->db, &localError, ^(SecDbConnectionRef dbconn) {
400 ok &= SecDbExec(dbconn, flushSQL, &localError);
401 });
402 if (!ok || localError) {
403 TrustdHealthAnalyticsLogErrorCodeForDatabase(TAOCSPCache, TAOperationWrite, TAFatalError,
404 localError ? CFErrorGetCode(localError) : errSecInternalComponent);
405 }
406 (void) CFErrorPropagate(localError, error);
407
408 return ok;
409 }
410
411
412 /* Public API */
413
414 void SecOCSPCacheReplaceResponse(SecOCSPResponseRef old_response, SecOCSPResponseRef response,
415 CFURLRef localResponderURI, CFAbsoluteTime verifyTime) {
416 SecOCSPCacheWith(^(SecOCSPCacheRef cache) {
417 _SecOCSPCacheReplaceResponse(cache, old_response, response, localResponderURI, verifyTime);
418 });
419 }
420
421 SecOCSPResponseRef SecOCSPCacheCopyMatching(SecOCSPRequestRef request,
422 CFURLRef localResponderURI /* may be NULL */) {
423 __block SecOCSPResponseRef response = NULL;
424 SecOCSPCacheWith(^(SecOCSPCacheRef cache) {
425 response = _SecOCSPCacheCopyMatching(cache, request, localResponderURI, 0.0);
426 });
427 return response;
428 }
429
430 SecOCSPResponseRef SecOCSPCacheCopyMatchingWithMinInsertTime(SecOCSPRequestRef request,
431 CFURLRef localResponderURI, CFAbsoluteTime minInsertTime) {
432 __block SecOCSPResponseRef response = NULL;
433 SecOCSPCacheWith(^(SecOCSPCacheRef cache) {
434 response = _SecOCSPCacheCopyMatching(cache, request, localResponderURI, minInsertTime);
435 });
436 return response;
437 }
438
439 bool SecOCSPCacheFlush(CFErrorRef *error) {
440 __block bool result = false;
441 SecOCSPCacheWith(^(SecOCSPCacheRef cache) {
442 result = _SecOCSPCacheFlush(cache, error);
443 });
444 return result;
445 }