--- /dev/null
+/*
+ * Copyright (c) 2016 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@
+ */
+
+/*
+ * SecTrustOSXEntryPoints - Interface for unified SecTrust into OS X Security
+ * Framework.
+ */
+
+#include "SecTrustOSXEntryPoints.h"
+
+#include <Security/Security.h>
+#include <Security/cssmtype.h>
+#include <Security/SecKeychain.h>
+#include <Security/SecItemPriv.h>
+#include <Security/SecTrustSettingsPriv.h>
+#include <Security/SecCertificate.h>
+#include <Security/SecImportExport.h>
+#include <security_keychain/SecImportExportPem.h>
+#include <security_utilities/debugging.h>
+
+#include <security_ocspd/ocspdClient.h>
+#include <security_ocspd/ocspdUtils.h>
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <CoreFoundation/CFRunLoop.h>
+#include <dispatch/dispatch.h>
+#include <AssertMacros.h>
+#include <pthread.h>
+
+/*
+ * MARK: CFRunloop
+ */
+
+static OSStatus SecLegacySourceChanged(__unused SecKeychainEvent keychainEvent, __unused SecKeychainCallbackInfo *info, __unused void *context) {
+ // Purge keychain parent cache
+ SecItemParentCachePurge();
+ // Purge unrestricted roots cache
+ SecTrustSettingsPurgeUserAdminCertsCache();
+ return 0;
+}
+
+static void *SecTrustOSXCFRunloop(__unused void *unused) {
+ CFRunLoopTimerRef timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, (CFTimeInterval) UINT_MAX, 0, 0, 0, ^(__unused CFRunLoopTimerRef _timer) {
+ /* do nothing */
+ });
+
+ /* add a timer to force the runloop to stay running */
+ CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
+ /* add keychain callback before we initiate a runloop to avoid it exiting due to no sources */
+
+ SecKeychainEventMask trustdMask = (kSecAddEventMask | kSecDeleteEventMask | kSecUpdateEventMask |
+ kSecDefaultChangedEventMask | kSecKeychainListChangedMask |
+ kSecTrustSettingsChangedEventMask);
+ SecKeychainAddCallback(SecLegacySourceChanged, trustdMask, NULL);
+
+ try {
+ CFRunLoopRun();
+ }
+ catch (...) {
+ /* An exception was rethrown from the runloop. Since we can't reliably
+ * obtain info about changes to keychains or trust settings anymore,
+ * just exit and respawn the process when needed. */
+
+ secerror("Exception occurred in CFRunLoopRun; exiting");
+ exit(0);
+ }
+ CFRelease(timer);
+ return NULL;
+}
+
+void SecTrustLegacySourcesEventRunloopCreate(void) {
+ /* A runloop is currently necessary to receive notifications about changes in the
+ * legacy keychains and trust settings. */
+ static dispatch_once_t once;
+
+ dispatch_once(&once, ^{
+ pthread_attr_t attrs;
+ pthread_t thread;
+
+ pthread_attr_init(&attrs);
+ pthread_attr_setdetachstate(&attrs, PTHREAD_CREATE_DETACHED);
+
+ /* we do this with traditional pthread to avoid impacting our 512 WQ thread limit since this is a parked thread */
+ pthread_create(&thread, &attrs, SecTrustOSXCFRunloop, NULL);
+ });
+}
+
+/*
+ * MARK: ocspd CRL Interface
+ */
+/* lengths of time strings without trailing NULL */
+#define CSSM_TIME_STRLEN 14 /* no trailing 'Z' */
+#define GENERALIZED_TIME_STRLEN 15
+
+OSStatus SecTrustLegacyCRLStatus(SecCertificateRef cert, CFArrayRef chain, CFURLRef currCRLDP);
+OSStatus SecTrustLegacyCRLFetch(CFURLRef currCRLDP, CFAbsoluteTime verifyTime);
+
+static OSStatus cssmReturnToOSStatus(CSSM_RETURN crtn) {
+ OSStatus status = errSecInternalComponent;
+
+ switch (crtn) {
+ case CSSM_OK:
+ status = errSecSuccess;
+ break;
+ case CSSMERR_TP_CERT_REVOKED:
+ status = errSecCertificateRevoked;
+ break;
+ case CSSMERR_APPLETP_NETWORK_FAILURE:
+ status = errSecNetworkFailure;
+ break;
+ case CSSMERR_APPLETP_CRL_NOT_FOUND:
+ status = errSecCRLNotFound;
+ break;
+ default:
+ status = errSecInternalComponent;
+ }
+ return status;
+}
+
+#define PEM_STRING_X509 "CERTIFICATE"
+static CFDataRef serializedPathToPemSequences(CFArrayRef certs) {
+ CFMutableDataRef result = NULL;
+ CFIndex certIX, certCount;
+ require_quiet(certs, out);
+ certCount = CFArrayGetCount(certs);
+ require_quiet(certCount > 0, out);
+ require_quiet(result = CFDataCreateMutable(NULL, 0), out);
+ for (certIX = 0; certIX < certCount; certIX++) {
+ CFDataRef certData = (CFDataRef)CFArrayGetValueAtIndex(certs, certIX);
+ require_noerr_quiet(impExpPemEncodeExportRep(certData, PEM_STRING_X509,
+ NULL, result), out);
+ }
+out:
+ return result;
+}
+
+OSStatus SecTrustLegacyCRLStatus(SecCertificateRef cert, CFArrayRef chain, CFURLRef currCRLDP) {
+ OSStatus result = errSecParam;
+ CSSM_RETURN crtn = CSSMERR_TP_INTERNAL_ERROR;
+ CFDataRef serialData = NULL, pemIssuers = NULL, crlDP = NULL;
+ CFMutableArrayRef issuersArray = NULL;
+
+ if (!cert || !chain) {
+ return result;
+ }
+
+ /* serialNumber is a CSSM_DATA with the value from the TBS Certificate. */
+ CSSM_DATA serialNumber = { 0, NULL };
+ serialData = SecCertificateCopySerialNumber(cert, NULL);
+ if (serialData) {
+ serialNumber.Data = (uint8_t *)CFDataGetBytePtr(serialData);
+ serialNumber.Length = CFDataGetLength(serialData);
+ }
+
+ /* issuers is CSSM_DATA containing pem sequence of all issuers in the chain */
+ CSSM_DATA issuers = { 0, NULL };
+ issuersArray = CFArrayCreateMutableCopy(NULL, 0, chain);
+ if (issuersArray) {
+ CFArrayRemoveValueAtIndex(issuersArray, 0);
+ pemIssuers = serializedPathToPemSequences(issuersArray);
+ }
+ if (pemIssuers) {
+ issuers.Data = (uint8_t *)CFDataGetBytePtr(pemIssuers);
+ issuers.Length = CFDataGetLength(pemIssuers);
+ }
+
+ /* crlUrl is CSSM_DATA with the CRLDP url*/
+ CSSM_DATA crlUrl = { 0, NULL };
+ crlDP = CFURLCreateData(NULL, currCRLDP, kCFStringEncodingASCII, true);
+ if (crlDP) {
+ crlUrl.Data = (uint8_t *)CFDataGetBytePtr(crlDP);
+ crlUrl.Length = CFDataGetLength(crlDP);
+ }
+
+ if (serialNumber.Data && issuers.Data && crlUrl.Data) {
+ crtn = ocspdCRLStatus(serialNumber, issuers, NULL, &crlUrl);
+ }
+
+ result = cssmReturnToOSStatus(crtn);
+
+ if (serialData) { CFRelease(serialData); }
+ if (issuersArray) { CFRelease(issuersArray); }
+ if (pemIssuers) { CFRelease(pemIssuers); }
+ if (crlDP) { CFRelease(crlDP); }
+ return result;
+}
+
+static CSSM_RETURN ocspdCRLFetchToCache(const CSSM_DATA &crlURL,
+ CSSM_TIMESTRING verifyTime) {
+ Allocator &alloc(Allocator::standard(Allocator::normal));
+ CSSM_DATA crlData = { 0, NULL };
+ CSSM_RETURN crtn;
+
+ crtn = ocspdCRLFetch(alloc, crlURL, NULL, true, true, verifyTime, crlData);
+ if (crlData.Data) { alloc.free(crlData.Data); }
+ return crtn;
+}
+
+static OSStatus fetchCRL(CFURLRef currCRLDP, CFAbsoluteTime verifyTime) {
+ OSStatus result = errSecParam;
+ CSSM_RETURN crtn = CSSMERR_TP_INTERNAL_ERROR;
+ CFDataRef crlDP = NULL;
+ char *cssmTime = NULL, *genTime = NULL;
+
+ if (!currCRLDP) {
+ return result;
+ }
+
+ /* crlUrl is CSSM_DATA with the CRLDP url*/
+ CSSM_DATA crlUrl = { 0, NULL };
+ crlDP = CFURLCreateData(NULL, currCRLDP, kCFStringEncodingASCII, true);
+ if (crlDP) {
+ crlUrl.Data = (uint8_t *)CFDataGetBytePtr(crlDP);
+ crlUrl.Length = CFDataGetLength(crlDP);
+ }
+
+ /* determine verification time */
+ cssmTime = (char *)malloc(CSSM_TIME_STRLEN + 1);
+ genTime = (char *)malloc(GENERAL_TIME_STRLEN + 1);
+ if (cssmTime && genTime) {
+ if (verifyTime != 0.0) {
+ cfAbsTimeToGgenTime(verifyTime, genTime);
+ } else {
+ cfAbsTimeToGgenTime(CFAbsoluteTimeGetCurrent(), genTime);
+ }
+ memmove(cssmTime, genTime, GENERAL_TIME_STRLEN - 1); // don't copy the Z
+ cssmTime[CSSM_TIME_STRLEN] = '\0';
+ }
+
+ if (crlUrl.Data && cssmTime) {
+ crtn = ocspdCRLFetchToCache(crlUrl, (CSSM_TIMESTRING)cssmTime);
+ }
+
+ result = cssmReturnToOSStatus(crtn);
+
+ if (crlDP) { CFRelease(crlDP); }
+ if (cssmTime) { free(cssmTime); }
+ if (genTime) { free(genTime); }
+ return result;
+}
+
+/*
+ * MARK: async_ocspd methods
+ */
+static void async_ocspd_complete(async_ocspd_t *ocspd) {
+ if (ocspd->completed) {
+ ocspd->completed(ocspd);
+ }
+}
+
+/* Return true, iff we didn't schedule any work, return false if we did. */
+bool SecTrustLegacyCRLFetch(async_ocspd_t *ocspd,
+ CFURLRef currCRLDP, CFAbsoluteTime verifyTime,
+ SecCertificateRef cert, CFArrayRef chain) {
+ dispatch_async(ocspd->queue, ^ {
+ OSStatus status = fetchCRL(currCRLDP, verifyTime);
+ switch (status) {
+ case errSecSuccess:
+ ocspd->response= SecTrustLegacyCRLStatus(cert, chain, currCRLDP);
+ break;
+ default:
+ ocspd->response = status;
+ break;
+ }
+ async_ocspd_complete(ocspd);
+ if (chain) { CFRelease(chain); }
+ });
+
+ return false; /* false -> something was scheduled. */
+}