+++ /dev/null
-/*
- * Copyright (c) 2008-2019 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@
- */
-
-/*
- * SecRevocationServer.c - Engine for evaluating certificate revocation.
- */
-
-#include <AssertMacros.h>
-
-#include <mach/mach_time.h>
-
-#include <Security/SecCertificatePriv.h>
-#include <Security/SecCertificateInternal.h>
-#include <Security/SecPolicyPriv.h>
-#include <Security/SecTrustPriv.h>
-#include <Security/SecInternal.h>
-
-#include <utilities/debugging.h>
-#include <utilities/SecCFWrappers.h>
-#include <utilities/SecIOFormat.h>
-
-#include <securityd/SecTrustServer.h>
-#include <securityd/SecOCSPRequest.h>
-#include <securityd/SecOCSPResponse.h>
-#include <securityd/SecOCSPCache.h>
-#include <securityd/SecRevocationDb.h>
-#include <securityd/SecCertificateServer.h>
-#include <securityd/SecPolicyServer.h>
-#include <securityd/SecRevocationNetworking.h>
-
-#include <securityd/SecRevocationServer.h>
-
-// MARK: SecORVCRef
-/********************************************************
- ****************** OCSP RVC Functions ******************
- ********************************************************/
-const CFAbsoluteTime kSecDefaultOCSPResponseTTL = 24.0 * 60.0 * 60.0;
-const CFAbsoluteTime kSecOCSPResponseOnlineTTL = 5.0 * 60.0;
-#define OCSP_RESPONSE_TIMEOUT (3 * NSEC_PER_SEC)
-
-static void SecORVCFinish(SecORVCRef orvc) {
- secdebug("alloc", "finish orvc %p", orvc);
- if (orvc->ocspRequest) {
- SecOCSPRequestFinalize(orvc->ocspRequest);
- orvc->ocspRequest = NULL;
- }
- if (orvc->ocspResponse) {
- SecOCSPResponseFinalize(orvc->ocspResponse);
- orvc->ocspResponse = NULL;
- if (orvc->ocspSingleResponse) {
- SecOCSPSingleResponseDestroy(orvc->ocspSingleResponse);
- orvc->ocspSingleResponse = NULL;
- }
- }
- memset(orvc, 0, sizeof(struct OpaqueSecORVC));
-}
-
-/* Process a verified ocsp response for a given cert. Return true if the
- certificate status was obtained. */
-static bool SecOCSPSingleResponseProcess(SecOCSPSingleResponseRef this,
- SecORVCRef rvc) {
- bool processed;
- switch (this->certStatus) {
- case CS_Good:
- secdebug("ocsp", "CS_Good for cert %" PRIdCFIndex, rvc->certIX);
- /* @@@ Mark cert as valid until a given date (nextUpdate if we have one)
- in the info dictionary. */
- //cert.revokeCheckGood(true);
- rvc->nextUpdate = this->nextUpdate == NULL_TIME ? this->thisUpdate + kSecDefaultOCSPResponseTTL : this->nextUpdate;
- processed = true;
- break;
- case CS_Revoked:
- secdebug("ocsp", "CS_Revoked for cert %" PRIdCFIndex, rvc->certIX);
- /* @@@ Mark cert as revoked (with reason) at revocation date in
- the info dictionary, or perhaps we should use a different key per
- reason? That way a client using exceptions can ignore some but
- not all reasons. */
- SInt32 reason = this->crlReason;
- CFNumberRef cfreason = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &reason);
- SecPathBuilderSetResultInPVCs(rvc->builder, kSecPolicyCheckRevocation, rvc->certIX,
- cfreason, true);
- SecCertificatePathVCRef path = SecPathBuilderGetPath(rvc->builder);
- if (path) {
- SecCertificatePathVCSetRevocationReasonForCertificateAtIndex(path, rvc->certIX, cfreason);
- }
- CFRelease(cfreason);
- processed = true;
- break;
- case CS_Unknown:
- /* not an error, no per-cert status, nothing here */
- secdebug("ocsp", "CS_Unknown for cert %" PRIdCFIndex, rvc->certIX);
- processed = false;
- break;
- default:
- secnotice("ocsp", "BAD certStatus (%d) for cert %" PRIdCFIndex,
- (int)this->certStatus, rvc->certIX);
- processed = false;
- break;
- }
-
- return processed;
-}
-
-void SecORVCUpdatePVC(SecORVCRef rvc) {
- if (rvc->ocspSingleResponse) {
- SecOCSPSingleResponseProcess(rvc->ocspSingleResponse, rvc);
- }
- if (rvc->ocspResponse) {
- rvc->nextUpdate = SecOCSPResponseGetExpirationTime(rvc->ocspResponse);
- }
-}
-
-typedef void (^SecOCSPEvaluationCompleted)(SecTrustResultType tr);
-
-static void
-SecOCSPEvaluateCompleted(const void *userData,
- CFArrayRef chain, CFArrayRef details, CFDictionaryRef info,
- SecTrustResultType result) {
- SecOCSPEvaluationCompleted evaluated = (SecOCSPEvaluationCompleted)userData;
- evaluated(result);
- Block_release(evaluated);
-
-}
-
-static bool SecOCSPResponseEvaluateSigner(SecORVCRef rvc, CFArrayRef signers, CFArrayRef issuers, CFAbsoluteTime verifyTime) {
- __block bool evaluated = false;
- bool trusted = false;
- if (!signers || !issuers) {
- return trusted;
- }
-
- /* Verify the signer chain against the OCSPSigner policy, using the issuer chain as anchors. */
- const void *ocspSigner = SecPolicyCreateOCSPSigner();
- CFArrayRef policies = CFArrayCreate(kCFAllocatorDefault,
- &ocspSigner, 1, &kCFTypeArrayCallBacks);
- CFRelease(ocspSigner);
-
- SecOCSPEvaluationCompleted completed = Block_copy(^(SecTrustResultType result) {
- if (result == kSecTrustResultProceed || result == kSecTrustResultUnspecified) {
- evaluated = true;
- }
- });
-
- CFDataRef clientAuditToken = SecPathBuilderCopyClientAuditToken(rvc->builder);
- SecPathBuilderRef oBuilder = SecPathBuilderCreate(NULL, clientAuditToken,
- signers, issuers, true, false,
- policies, NULL, NULL, NULL,
- verifyTime, NULL, NULL,
- SecOCSPEvaluateCompleted, completed);
- /* disable network access to avoid recursion */
- SecPathBuilderSetCanAccessNetwork(oBuilder, false);
-
- /* Build the chain(s), evaluate them, call the completed block, free the block and builder */
- SecPathBuilderStep(oBuilder);
- CFReleaseNull(clientAuditToken);
- CFReleaseNull(policies);
-
- /* verify the public key of the issuer signed the OCSP signer */
- if (evaluated) {
- SecCertificateRef issuer = NULL, signer = NULL;
- SecKeyRef issuerPubKey = NULL;
-
- issuer = (SecCertificateRef)CFArrayGetValueAtIndex(issuers, 0);
- signer = (SecCertificateRef)CFArrayGetValueAtIndex(signers, 0);
-
- if (issuer) {
- issuerPubKey = SecCertificateCopyKey(issuer);
- }
- if (signer && issuerPubKey && (errSecSuccess == SecCertificateIsSignedBy(signer, issuerPubKey))) {
- trusted = true;
- } else {
- secnotice("ocsp", "ocsp signer cert not signed by issuer");
- }
- CFReleaseNull(issuerPubKey);
- }
-
- return trusted;
-}
-
-static bool SecOCSPResponseVerify(SecOCSPResponseRef ocspResponse, SecORVCRef rvc, CFAbsoluteTime verifyTime) {
- bool trusted;
- SecCertificatePathVCRef issuers = SecCertificatePathVCCopyFromParent(SecPathBuilderGetPath(rvc->builder), rvc->certIX + 1);
- SecCertificateRef issuer = issuers ? CFRetainSafe(SecCertificatePathVCGetCertificateAtIndex(issuers, 0)) : NULL;
- CFArrayRef signers = SecOCSPResponseCopySigners(ocspResponse);
- SecCertificateRef signer = SecOCSPResponseCopySigner(ocspResponse, issuer);
-
- if (signer && signers) {
- if (issuer && CFEqual(signer, issuer)) {
- /* We already know we trust issuer since it's the issuer of the
- * cert we are verifying. */
- secinfo("ocsp", "ocsp responder: %@ response signed by issuer",
- rvc->responder);
- trusted = true;
- } else {
- secinfo("ocsp", "ocsp responder: %@ response signed by cert issued by issuer",
- rvc->responder);
- CFMutableArrayRef signerCerts = NULL;
- CFArrayRef issuerCerts = NULL;
-
- /* Ensure the signer cert is the 0th cert for trust evaluation */
- signerCerts = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
- CFArrayAppendValue(signerCerts, signer);
- CFArrayAppendArray(signerCerts, signers, CFRangeMake(0, CFArrayGetCount(signers)));
-
- if (issuers) {
- issuerCerts = SecCertificatePathVCCopyCertificates(issuers);
- }
-
- if (SecOCSPResponseEvaluateSigner(rvc, signerCerts, issuerCerts, verifyTime)) {
- secdebug("ocsp", "response satisfies ocspSigner policy (%@)",
- rvc->responder);
- trusted = true;
- } else {
- /* @@@ We don't trust the cert so don't use this response. */
- secnotice("ocsp", "ocsp response signed by certificate which "
- "does not satisfy ocspSigner policy");
- trusted = false;
- }
- CFReleaseNull(signerCerts);
- CFReleaseNull(issuerCerts);
- }
- } else {
- /* @@@ No signer found for this ocsp response, discard it. */
- secnotice("ocsp", "ocsp responder: %@ no signer found for response",
- rvc->responder);
- trusted = false;
- }
-
-#if DUMP_OCSPRESPONSES
- char buf[40];
- snprintf(buf, 40, "/tmp/ocspresponse%ld%s.der",
- rvc->certIX, (trusted ? "t" : "u"));
- secdumpdata(ocspResponse->data, buf);
-#endif
- CFReleaseNull(issuers);
- CFReleaseNull(issuer);
- CFReleaseNull(signers);
- CFReleaseNull(signer);
- return trusted;
-}
-
-void SecORVCConsumeOCSPResponse(SecORVCRef rvc, SecOCSPResponseRef ocspResponse /*CF_CONSUMED*/,
- CFTimeInterval maxAge, bool updateCache, bool fromCache) {
- SecOCSPSingleResponseRef sr = NULL;
- require_quiet(ocspResponse, errOut);
- SecOCSPResponseStatus orStatus = SecOCSPGetResponseStatus(ocspResponse);
- require_action_quiet(orStatus == kSecOCSPSuccess, errOut,
- secnotice("ocsp", "responder: %@ returned status: %d", rvc->responder, orStatus));
- require_action_quiet(sr = SecOCSPResponseCopySingleResponse(ocspResponse, rvc->ocspRequest), errOut,
- secnotice("ocsp", "ocsp responder: %@ did not include status of requested cert", rvc->responder));
- // Check if this response is fresher than any (cached) response we might still have in the rvc.
- require_quiet(!rvc->ocspSingleResponse || rvc->ocspSingleResponse->thisUpdate < sr->thisUpdate, errOut);
-
- CFAbsoluteTime verifyTime = CFAbsoluteTimeGetCurrent();
-#if TARGET_OS_IPHONE
- /* Check the OCSP response signature and verify the response if not pulled from the cache.
- * Performance optimization since we don't write invalid responses to the cache. */
- if (!fromCache) {
- require_quiet(SecOCSPResponseVerify(ocspResponse, rvc,
- sr->certStatus == CS_Revoked ? SecOCSPResponseProducedAt(ocspResponse) : verifyTime), errOut);
- }
-#else
- /* Always check the OCSP response signature and verify the response (since the cache is user-modifiable). */
- require_quiet(SecOCSPResponseVerify(ocspResponse, rvc,
- sr->certStatus == CS_Revoked ? SecOCSPResponseProducedAt(ocspResponse) : verifyTime), errOut);
-#endif
-
- // If we get here, we have a properly signed ocsp response
- // but we haven't checked dates yet.
-
- bool sr_valid = SecOCSPSingleResponseCalculateValidity(sr, kSecDefaultOCSPResponseTTL, verifyTime);
- if (sr->certStatus == CS_Good) {
- // Side effect of SecOCSPResponseCalculateValidity sets ocspResponse->expireTime
- require_quiet(sr_valid && SecOCSPResponseCalculateValidity(ocspResponse, maxAge, kSecDefaultOCSPResponseTTL, verifyTime), errOut);
- } else if (sr->certStatus == CS_Revoked) {
- // Expire revoked responses when the subject certificate itself expires.
- ocspResponse->expireTime = SecCertificateNotValidAfter(SecPathBuilderGetCertificateAtIndex(rvc->builder, rvc->certIX));
- }
-
- // Ok we like the new response, let's toss the old one.
- if (updateCache)
- SecOCSPCacheReplaceResponse(rvc->ocspResponse, ocspResponse, rvc->responder, verifyTime);
-
- if (rvc->ocspResponse) SecOCSPResponseFinalize(rvc->ocspResponse);
- rvc->ocspResponse = ocspResponse;
- ocspResponse = NULL;
-
- if (rvc->ocspSingleResponse) SecOCSPSingleResponseDestroy(rvc->ocspSingleResponse);
- rvc->ocspSingleResponse = sr;
- sr = NULL;
-
- rvc->done = sr_valid;
-
-errOut:
- if (sr) SecOCSPSingleResponseDestroy(sr);
- if (ocspResponse) SecOCSPResponseFinalize(ocspResponse);
-}
-
-static SecORVCRef SecORVCCreate(SecRVCRef rvc, SecPathBuilderRef builder, CFIndex certIX) {
- SecORVCRef orvc = NULL;
- orvc = malloc(sizeof(struct OpaqueSecORVC));
- secdebug("alloc", "orvc %p", orvc);
- if (orvc) {
- memset(orvc, 0, sizeof(struct OpaqueSecORVC));
- orvc->builder = builder;
- orvc->rvc = rvc;
- orvc->certIX = certIX;
-
- SecCertificateRef cert = SecPathBuilderGetCertificateAtIndex(builder, certIX);
- if (SecPathBuilderGetCertificateCount(builder) > (certIX + 1)) {
- SecCertificateRef issuer = SecPathBuilderGetCertificateAtIndex(builder, certIX + 1);
- orvc->ocspRequest = SecOCSPRequestCreate(cert, issuer);
- }
- }
- return orvc;
-}
-
-static void SecORVCProcessStapledResponses(SecORVCRef rvc) {
- /* Get stapled OCSP responses */
- CFArrayRef ocspResponsesData = SecPathBuilderCopyOCSPResponses(rvc->builder);
-
- if(ocspResponsesData) {
- secdebug("rvc", "Checking stapled responses for cert %ld", rvc->certIX);
- CFArrayForEach(ocspResponsesData, ^(const void *value) {
- SecOCSPResponseRef ocspResponse = SecOCSPResponseCreate(value);
- SecORVCConsumeOCSPResponse(rvc, ocspResponse, NULL_TIME, false, false);
- });
- CFRelease(ocspResponsesData);
- }
-}
-
-void SecRVCDelete(SecRVCRef rvc) {
- secdebug("alloc", "delete rvc %p", rvc);
- if (rvc->orvc) {
- SecORVCFinish(rvc->orvc);
- free(rvc->orvc);
- rvc->orvc = NULL;
- }
- if (rvc->valid_info) {
- CFReleaseNull(rvc->valid_info);
- }
-}
-
-// Forward declaration
-static void SecRVCSetFinishedWithoutNetwork(SecRVCRef rvc);
-
-static void SecRVCInit(SecRVCRef rvc, SecPathBuilderRef builder, CFIndex certIX) {
- secdebug("alloc", "rvc %p", rvc);
- rvc->builder = builder;
- rvc->certIX = certIX;
- rvc->orvc = SecORVCCreate(rvc, builder, certIX);
- if (!rvc->orvc) {
- SecRVCDelete(rvc);
- SecRVCSetFinishedWithoutNetwork(rvc);
- } else {
- rvc->done = false;
- }
-}
-
-static bool SecRVCShouldCheckOCSP(SecRVCRef rvc) {
- return true;
-}
-
-static bool SecRVCPolicyConstraintsPermitPolicy(SecValidPolicy *constraints, CFIndex count, SecPolicyRef policy) {
- if (!constraints || !policy) {
- return true; /* nothing to constrain */
- }
- SecValidPolicy policyType = kSecValidPolicyAny;
- CFStringRef policyName = SecPolicyGetName(policy);
- /* determine if the policy is a candidate for being constrained */
- if (CFEqualSafe(policyName, kSecPolicyNameSSLServer) ||
- CFEqualSafe(policyName, kSecPolicyNameEAPServer) ||
- CFEqualSafe(policyName, kSecPolicyNameIPSecServer)) {
- policyType = kSecValidPolicyServerAuthentication;
- } else if (CFEqualSafe(policyName, kSecPolicyNameSSLClient) ||
- CFEqualSafe(policyName, kSecPolicyNameEAPClient) ||
- CFEqualSafe(policyName, kSecPolicyNameIPSecClient)) {
- policyType = kSecValidPolicyClientAuthentication;
- } else if (CFEqualSafe(policyName, kSecPolicyNameSMIME)) {
- policyType = kSecValidPolicyEmailProtection;
- } else if (CFEqualSafe(policyName, kSecPolicyNameCodeSigning)) {
- policyType = kSecValidPolicyCodeSigning;
- } else if (CFEqualSafe(policyName, kSecPolicyNameTimeStamping)) {
- policyType = kSecValidPolicyTimeStamping;
- }
- if (policyType == kSecValidPolicyAny) {
- return true; /* policy not subject to constraint */
- }
- /* policy is subject to constraint; do the constraints allow it? */
- bool result = false;
- for (CFIndex ix = 0; ix < count; ix++) {
- SecValidPolicy allowedPolicy = constraints[ix];
- if (allowedPolicy == kSecValidPolicyAny ||
- allowedPolicy == policyType) {
- result = true;
- break;
- }
- }
- if (!result) {
- secnotice("rvc", "%@ not allowed by policy constraints on issuing CA", policyName);
- }
- return result;
-}
-
-static bool SecRVCGetPolicyConstraints(CFDataRef data, SecValidPolicy **constraints, CFIndex *count) {
- /* Sanity-check the input policy constraints data, returning pointer and
- * count values in output arguments. Function result is true if successful.
- *
- * The first byte of the policy constraints data contains the number of entries,
- * followed by an array of 0..n policy constraint values of type SecValidPolicy.
- * The maximum number of defined policies is not expected to approach 127, i.e.
- * the largest value which can be expressed in a signed byte.
- */
- bool result = false;
- CFIndex length = 0;
- SecValidPolicy *p = NULL;
- if (data) {
- length = CFDataGetLength(data);
- p = (SecValidPolicy *)CFDataGetBytePtr(data);
- }
- /* Verify that count is 0 or greater, and equal to remaining number of bytes */
- CFIndex c = (length > 0) ? *p++ : -1;
- if (c < 0 || c != (length - 1)) {
- secerror("invalid policy constraints array");
- } else {
- if (constraints) {
- *constraints = p;
- }
- if (count) {
- *count = c;
- }
- result = true;
- }
- return result;
-}
-
-static void SecRVCProcessValidPolicyConstraints(SecRVCRef rvc) {
- if (!rvc || !rvc->valid_info || !rvc->builder) {
- return;
- }
- if (!rvc->valid_info->hasPolicyConstraints) {
- return;
- }
- CFIndex count = 0;
- SecValidPolicy *constraints = NULL;
- if (!SecRVCGetPolicyConstraints(rvc->valid_info->policyConstraints, &constraints, &count)) {
- return;
- }
- secdebug("rvc", "found policy constraints for cert at index %ld", rvc->certIX);
-
- /* check that policies being verified are permitted by the policy constraints */
- bool policyDeniedByConstraints = false;
- CFIndex ix, initialPVCCount = SecPathBuilderGetPVCCount(rvc->builder);
- for (ix = 0; ix < initialPVCCount; ix++) {
- SecPVCRef pvc = SecPathBuilderGetPVCAtIndex(rvc->builder, ix);
- CFArrayRef policies = CFRetainSafe(pvc->policies);
- CFIndex policyCount = (policies) ? CFArrayGetCount(policies) : 0;
- for (CFIndex policyIX = 0; policyIX < policyCount; policyIX++) {
- SecPolicyRef policy = (SecPolicyRef)CFArrayGetValueAtIndex(policies, policyIX);
- if (!SecRVCPolicyConstraintsPermitPolicy(constraints, count, policy)) {
- policyDeniedByConstraints = true;
- SecPVCSetResultForced(pvc, kSecPolicyCheckIssuerPolicyConstraints, rvc->certIX,
- kCFBooleanFalse, true);
- pvc->result = kSecTrustResultRecoverableTrustFailure;
- if (!rvc->valid_info->overridable) {
- /* error for this check should be non-recoverable */
- pvc->result = kSecTrustResultFatalTrustFailure;
- }
- }
- }
- CFReleaseSafe(policies);
- }
- TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(rvc->builder);
- if (analytics) {
- TAValidStatus status = (policyDeniedByConstraints) ? TAValidPolicyConstrainedDenied : TAValidPolicyConstrainedOK;
- analytics->valid_status |= status;
- }
-}
-
-static void SecRVCProcessValidDateConstraints(SecRVCRef rvc) {
- if (!rvc || !rvc->valid_info || !rvc->builder) {
- return;
- }
- if (!rvc->valid_info->hasDateConstraints) {
- return;
- }
- SecCertificateRef certificate = SecPathBuilderGetCertificateAtIndex(rvc->builder, rvc->certIX);
- if (!certificate) {
- return;
- }
- CFAbsoluteTime certIssued = SecCertificateNotValidBefore(certificate);
- CFAbsoluteTime caNotBefore = -3155760000.0; /* default: 1901-01-01 00:00:00-0000 */
- CFAbsoluteTime caNotAfter = 31556908800.0; /* default: 3001-01-01 00:00:00-0000 */
- if (rvc->valid_info->notBeforeDate) {
- caNotBefore = CFDateGetAbsoluteTime(rvc->valid_info->notBeforeDate);
- }
- if (rvc->valid_info->notAfterDate) {
- caNotAfter = CFDateGetAbsoluteTime(rvc->valid_info->notAfterDate);
- /* per the Valid specification, if this date is in the past, we need to check CT. */
- CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
- if (caNotAfter < now) {
- rvc->valid_info->requireCT = true;
- }
- }
- if ((certIssued < caNotBefore) && (rvc->certIX > 0)) {
- /* not-before constraint is only applied to leaf certificate, for now. */
- return;
- }
-
- TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(rvc->builder);
- if ((certIssued < caNotBefore) || (certIssued > caNotAfter)) {
- /* We are outside the constrained validity period. */
- secnotice("rvc", "certificate issuance date not within the allowed range for this CA%s",
- (rvc->valid_info->overridable) ? "" : " (non-recoverable error)");
- if (analytics) {
- analytics->valid_status |= TAValidDateConstrainedRevoked;
- }
- if (rvc->valid_info->overridable) {
- /* error is recoverable, treat certificate as untrusted
- (note this date check is different from kSecPolicyCheckTemporalValidity) */
- SecPathBuilderSetResultInPVCs(rvc->builder, kSecPolicyCheckGrayListedKey, rvc->certIX,
- kCFBooleanFalse, true);
- } else {
- /* error is non-overridable, treat certificate as revoked */
- SInt32 reason = 0; /* unspecified reason code */
- CFNumberRef cfreason = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &reason);
- SecPathBuilderSetResultInPVCs(rvc->builder, kSecPolicyCheckRevocation, rvc->certIX,
- cfreason, true);
- SecCertificatePathVCRef path = SecPathBuilderGetPath(rvc->builder);
- if (path) {
- SecCertificatePathVCSetRevocationReasonForCertificateAtIndex(path, rvc->certIX, cfreason);
- }
- CFReleaseNull(cfreason);
- }
- } else if (analytics) {
- analytics->valid_status |= TAValidDateConstrainedOK;
- }
-}
-
-bool SecRVCHasDefinitiveValidInfo(SecRVCRef rvc) {
- if (!rvc || !rvc->valid_info) {
- return false;
- }
- SecValidInfoRef info = rvc->valid_info;
- /* outcomes as defined in Valid server specification */
- if (info->format == kSecValidInfoFormatSerial ||
- info->format == kSecValidInfoFormatSHA256) {
- if (info->noCACheck || info->complete || info->isOnList) {
- return true;
- }
- } else { /* info->format == kSecValidInfoFormatNto1 */
- if (info->noCACheck || (info->complete && !info->isOnList)) {
- return true;
- }
- }
- return false;
-}
-
-bool SecRVCHasRevokedValidInfo(SecRVCRef rvc) {
- if (!rvc || !rvc->valid_info) {
- return false;
- }
- SecValidInfoRef info = rvc->valid_info;
- /* either not present on an allowlist, or present on a blocklist */
- return (!info->isOnList && info->valid) || (info->isOnList && !info->valid);
-}
-
-void SecRVCSetValidDeterminedErrorResult(SecRVCRef rvc) {
- if (!rvc || !rvc->valid_info || !rvc->builder) {
- return;
- }
- if (rvc->valid_info->overridable) {
- /* error is recoverable, treat certificate as untrusted */
- SecPathBuilderSetResultInPVCs(rvc->builder, kSecPolicyCheckGrayListedLeaf, rvc->certIX,
- kCFBooleanFalse, true);
- return;
- }
- /* error is fatal at this point */
- if (!SecRVCHasRevokedValidInfo(rvc) || rvc->valid_info->noCACheck) {
- /* result key should indicate blocked instead of revoked,
- * but result must be non-recoverable */
- SecPathBuilderSetResultInPVCs(rvc->builder, kSecPolicyCheckBlackListedLeaf, rvc->certIX,
- kCFBooleanFalse, true);
- return;
- }
- SInt32 reason = 0; /* unspecified, since the Valid db doesn't tell us */
- CFNumberRef cfreason = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &reason);
- SecPathBuilderSetResultInPVCs(rvc->builder, kSecPolicyCheckRevocation, rvc->certIX,
- cfreason, true);
- SecCertificatePathVCRef path = SecPathBuilderGetPath(rvc->builder);
- if (path) {
- SecCertificatePathVCSetRevocationReasonForCertificateAtIndex(path, rvc->certIX, cfreason);
- }
- CFReleaseNull(cfreason);
-}
-
-static void SecRVCProcessValidInfoResults(SecRVCRef rvc) {
- if (!rvc || !rvc->valid_info || !rvc->builder) {
- return;
- }
- SecCertificatePathVCRef path = SecPathBuilderGetPath(rvc->builder);
- SecValidInfoRef info = rvc->valid_info;
-
- bool definitive = SecRVCHasDefinitiveValidInfo(rvc);
- bool revoked = SecRVCHasRevokedValidInfo(rvc);
-
- /* set analytics */
- TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(rvc->builder);
- if (analytics) {
- if (revoked) {
- analytics->valid_status |= definitive ? TAValidDefinitelyRevoked : TAValidProbablyRevoked;
- } else {
- analytics->valid_status |= definitive ? TAValidDefinitelyOK : TAValidProbablyOK;
- }
- analytics->valid_require_ct |= info->requireCT;
- analytics->valid_known_intermediates_only |= info->knownOnly;
- }
-
- /* Handle no-ca cases */
- if (info->noCACheck) {
- bool allowed = (info->valid && info->complete && info->isOnList);
- if (revoked) {
- /* definitely revoked */
- SecRVCSetValidDeterminedErrorResult(rvc);
- } else if (allowed) {
- /* definitely not revoked (allowlisted) */
- SecCertificatePathVCSetIsAllowlisted(path, true);
- }
- /* no-ca is definitive; no need to check further. */
- secdebug("validupdate", "rvc: definitely %s cert %" PRIdCFIndex,
- (allowed) ? "allowed" : "revoked", rvc->certIX);
- rvc->done = true;
- return;
- }
-
- /* Handle policy constraints, if present. */
- SecRVCProcessValidPolicyConstraints(rvc);
-
- /* Handle date constraints, if present.
- * Note: a not-after date may set the CT requirement,
- * so check requireCT after this function is called. */
- SecRVCProcessValidDateConstraints(rvc);
-
- /* Set CT requirement on path, if present. */
- if (info->requireCT) {
- SecPathCTPolicy ctp = kSecPathCTRequired;
- if (info->overridable) {
- ctp = kSecPathCTRequiredOverridable;
- }
- SecCertificatePathVCSetRequiresCT(path, ctp);
- }
-
- /* Trigger OCSP for any non-definitive or revoked cases */
- if (!definitive || revoked) {
- info->checkOCSP = true;
- }
-
- if (info->checkOCSP) {
- CFIndex count = SecPathBuilderGetCertificateCount(rvc->builder);
- CFIndex issuerIX = rvc->certIX + 1;
- if (issuerIX >= count) {
- /* cannot perform a revocation check on the last cert in the
- chain, since we don't have its issuer. */
- return;
- }
- secdebug("validupdate", "rvc: %s%s cert %" PRIdCFIndex " (will check OCSP)",
- (info->complete) ? "" : "possibly ", (info->valid) ? "allowed" : "revoked",
- rvc->certIX);
- SecPathBuilderSetRevocationMethod(rvc->builder, kSecPolicyCheckRevocationAny);
- if (analytics) {
- /* Valid DB results caused us to do OCSP */
- analytics->valid_trigger_ocsp = true;
- }
- }
-}
-
-static bool SecRVCCheckValidInfoDatabase(SecRVCRef rvc) {
- /* Skip checking for OCSP Signer verification */
- if (SecPathBuilderGetPVCCount(rvc->builder) == 1) {
- SecPVCRef pvc = SecPathBuilderGetPVCAtIndex(rvc->builder, 0);
- if (!pvc) { return false; }
- SecPolicyRef policy = (SecPolicyRef)CFArrayGetValueAtIndex(pvc->policies, 0);
- CFStringRef policyName = (policy) ? SecPolicyGetName(policy) : NULL;
- if (policyName && CFEqual(policyName, CFSTR("OCSPSigner"))) {
- return false;
- }
- }
-
- /* Make sure revocation db info is up-to-date.
- * We don't care if the builder is allowed to access the network because
- * the network fetching does not block the trust evaluation. */
- SecRevocationDbCheckNextUpdate();
-
- /* Check whether we have valid db info for this cert,
- given the cert and its issuer */
- SecValidInfoRef info = NULL;
- CFIndex count = SecPathBuilderGetCertificateCount(rvc->builder);
- if (count) {
- bool isSelfSigned = false;
- SecCertificateRef cert = NULL;
- SecCertificateRef issuer = NULL;
- CFIndex issuerIX = rvc->certIX + 1;
- if (count > issuerIX) {
- issuer = SecPathBuilderGetCertificateAtIndex(rvc->builder, issuerIX);
- } else if (count == issuerIX) {
- CFIndex rootIX = SecCertificatePathVCSelfSignedIndex(SecPathBuilderGetPath(rvc->builder));
- if (rootIX == rvc->certIX) {
- issuer = SecPathBuilderGetCertificateAtIndex(rvc->builder, rootIX);
- isSelfSigned = true;
- }
- }
- cert = SecPathBuilderGetCertificateAtIndex(rvc->builder, rvc->certIX);
- if (!isSelfSigned) {
- /* skip revocation db check for self-signed certificates [33137065] */
- info = SecRevocationDbCopyMatching(cert, issuer);
- }
- SecValidInfoSetAnchor(info, SecPathBuilderGetCertificateAtIndex(rvc->builder, count-1));
- }
- if (info) {
- SecValidInfoRef old_info = rvc->valid_info;
- rvc->valid_info = info;
- if (old_info) {
- CFReleaseNull(old_info);
- }
- return true;
- }
- return false;
-}
-
-static void SecRVCCheckRevocationCaches(SecRVCRef rvc) {
- /* Don't check OCSP cache if CRLs enabled and policy requested CRL only */
- if (SecRVCShouldCheckOCSP(rvc) && (rvc->orvc->ocspRequest)) {
- secdebug("ocsp", "Checking cached responses for cert %ld", rvc->certIX);
- SecOCSPResponseRef response = NULL;
- if (SecPathBuilderGetCheckRevocationOnline(rvc->builder)) {
- CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
- response = SecOCSPCacheCopyMatchingWithMinInsertTime(rvc->orvc->ocspRequest, NULL, now - kSecOCSPResponseOnlineTTL);
- } else {
- response = SecOCSPCacheCopyMatching(rvc->orvc->ocspRequest, NULL);
- }
- SecORVCConsumeOCSPResponse(rvc->orvc, response, NULL_TIME, false, true);
- TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(rvc->builder);
- if (rvc->orvc->done && analytics) {
- /* We found a valid OCSP response in the cache */
- analytics->ocsp_cache_hit = true;
- }
- }
-}
-
-static void SecRVCUpdatePVC(SecRVCRef rvc) {
- SecRVCProcessValidInfoResults(rvc); /* restore the results we got from Valid */
- if (rvc->orvc) { SecORVCUpdatePVC(rvc->orvc); }
-}
-
-static void SecRVCSetFinishedWithoutNetwork(SecRVCRef rvc) {
- rvc->done = true;
- SecRVCUpdatePVC(rvc);
- (void)SecPathBuilderDecrementAsyncJobCount(rvc->builder);
-}
-
-static bool SecRVCFetchNext(SecRVCRef rvc) {
- bool OCSP_fetch_finished = true;
- TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(rvc->builder);
- /* Don't send OCSP request only if CRLs enabled and policy requested CRL only */
- if (SecRVCShouldCheckOCSP(rvc)) {
- SecCertificatePathVCRef path = SecPathBuilderGetPath(rvc->builder);
- SecCertificateRef cert = SecCertificatePathVCGetCertificateAtIndex(path, rvc->certIX);
- OCSP_fetch_finished = SecORVCBeginFetches(rvc->orvc, cert);
- if (analytics && !OCSP_fetch_finished) {
- /* We did a network OCSP fetch, set report appropriately */
- analytics->ocsp_network = true;
- }
- }
- if (OCSP_fetch_finished) {
- /* we didn't start an OCSP background job for this cert */
- (void)SecPathBuilderDecrementAsyncJobCount(rvc->builder);
- }
- return OCSP_fetch_finished;
-}
-
-/* The SecPathBuilder state machine calls SecPathBuilderCheckRevocation twice --
- * once in the ValidatePath state, and again in the ComputeDetails state. In the
- * ValidatePath state we've not yet run the path checks, so for callers who set
- * kSecRevocationCheckIfTrusted, we don't do any networking on that first call.
- * Here, if we've already done revocation before (so we're in ComputeDetails now),
- * we need to recheck (and enable networking) for trusted chains and
- * kSecRevocationCheckIfTrusted. Otherwise, we skip the checks to save on the processing
- * but update the PVCs with the revocation results from the last check. */
-static bool SecRevocationDidCheckRevocation(SecPathBuilderRef builder, bool *first_check_done) {
- SecCertificatePathVCRef path = SecPathBuilderGetPath(builder);
- if (!SecCertificatePathVCIsRevocationDone(path)) {
- return false;
- }
- if (first_check_done) {
- *first_check_done = true;
- }
-
- SecPVCRef resultPVC = SecPathBuilderGetResultPVC(builder);
- bool recheck = false;
- if (SecPathBuilderGetCheckRevocationIfTrusted(builder) && SecPVCIsOkResult(resultPVC)) {
- recheck = true;
- secdebug("rvc", "Rechecking revocation because network now allowed");
- } else {
- secdebug("rvc", "Not rechecking revocation");
- }
-
- if (recheck) {
- // reset the RVCs for the second pass
- SecCertificatePathVCDeleteRVCs(path);
- } else {
- CFIndex certIX, certCount = SecPathBuilderGetCertificateCount(builder);
- for (certIX = 0; certIX < certCount; ++certIX) {
- SecRVCRef rvc = SecCertificatePathVCGetRVCAtIndex(path, certIX);
- if (rvc) {
- SecRVCUpdatePVC(rvc);
- }
- }
- }
-
- return !recheck;
-}
-
-static bool SecRevocationCanAccessNetwork(SecPathBuilderRef builder, bool first_check_done) {
- /* CheckRevocationIfTrusted overrides NoNetworkAccess for revocation */
- if (SecPathBuilderGetCheckRevocationIfTrusted(builder)) {
- if (first_check_done) {
- /* We're on the second pass. We need to now allow networking for revocation.
- * SecRevocationDidCheckRevocation takes care of not running a second pass
- * if the chain isn't trusted. */
- return true;
- } else {
- /* We're on the first pass of the revocation checks, where we aren't
- * supposed to do networking because we don't know if the chain
- * is trusted yet. */
- return false;
- }
- }
- return SecPathBuilderCanAccessNetwork(builder);
-}
-
-void SecPathBuilderCheckKnownIntermediateConstraints(SecPathBuilderRef builder) {
- SecCertificatePathVCRef path = SecPathBuilderGetPath(builder);
- if (!path) {
- return;
- }
- /* only perform this check once per path! */
- CFIndex certIX = kCFNotFound;
- if (SecCertificatePathVCCheckedIssuers(path)) {
- certIX = SecCertificatePathVCUnknownCAIndex(path);
- goto checkedIssuers;
- }
- /* check full path: start with anchor and decrement to leaf */
- bool parentConstrained = false;
- CFIndex certCount = SecPathBuilderGetCertificateCount(builder);
- for (certIX = certCount - 1; certIX >= 0; --certIX) {
- SecRVCRef rvc = SecCertificatePathVCGetRVCAtIndex(path, certIX);
- if (!rvc) {
- continue;
- }
- if (parentConstrained && !rvc->valid_info) {
- /* Parent had the known-only constraint, but our issuer is unknown.
- Bump index to point back at the issuer since it fails the constraint. */
- certIX++;
- break;
- }
- parentConstrained = (rvc->valid_info && rvc->valid_info->knownOnly);
- if (parentConstrained) {
- secdebug("validupdate", "Valid db found a known-intermediate constraint on %@ (index=%ld)",
- rvc->valid_info->issuerHash, certIX+1);
- if (certIX == 0) {
- /* check special case: unknown constrained CA in leaf position */
- SecCertificateRef cert = SecCertificatePathVCGetCertificateAtIndex(path, certIX);
- if (cert && SecCertificateIsCA(cert) && !SecRevocationDbContainsIssuer(cert)) {
- /* leaf is a CA which violates the constraint */
- break;
- }
- }
- }
- }
- /* At this point, certIX will either be -1, indicating no CA was found
- which failed a known-intermediates-only constraint on its parent, or it
- will be the index of the first unknown CA which fails the constraint. */
- if (certIX >= 0) {
- secnotice("validupdate", "CA at index %ld violates known-intermediate constraint", certIX);
- TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(builder);
- if (analytics) {
- analytics->valid_unknown_intermediate = true;
- }
- }
- SecCertificatePathVCSetUnknownCAIndex(path, certIX);
- SecCertificatePathVCSetCheckedIssuers(path, true);
-
-checkedIssuers:
- if (certIX >= 0) {
- /* Error is set on CA certificate which failed the constraint. */
- SecRVCSetValidDeterminedErrorResult(SecCertificatePathVCGetRVCAtIndex(path, certIX));
- }
-}
-
-bool SecPathBuilderCheckRevocation(SecPathBuilderRef builder) {
- secdebug("rvc", "checking revocation");
- CFIndex certIX, certCount = SecPathBuilderGetCertificateCount(builder);
- SecCertificatePathVCRef path = SecPathBuilderGetPath(builder);
- if (certCount <= 1) {
- /* Can't verify without an issuer; we're done */
- return true;
- }
-
- bool first_check_done = false;
- if (SecRevocationDidCheckRevocation(builder, &first_check_done)) {
- return true;
- }
-
- /* Setup things so we check revocation status of all certs. */
- SecCertificatePathVCAllocateRVCs(path, certCount);
-
- /* Note that if we are multi threaded and a job completes after it
- is started but before we return from this function, we don't want
- a callback to decrement asyncJobCount to zero before we finish issuing
- all the jobs. To avoid this we pretend we issued certCount async jobs,
- and decrement pvc->asyncJobCount for each cert that we don't start a
- background fetch for. We include the root, even though we'll never start
- an async job for it so that we count all active threads for this eval. */
- SecPathBuilderSetAsyncJobCount(builder, (unsigned int)(certCount));
-
- /* Loop though certificates again and issue an ocsp fetch if the
- revocation status checking isn't done yet (and we have an issuer!) */
- for (certIX = 0; certIX < certCount; ++certIX) {
- secdebug("rvc", "checking revocation for cert: %ld", certIX);
- SecRVCRef rvc = SecCertificatePathVCGetRVCAtIndex(path, certIX);
- if (!rvc) {
- continue;
- }
-
- SecRVCInit(rvc, builder, certIX);
-
- /* RFC 6960: id-pkix-ocsp-nocheck extension says that we shouldn't check revocation. */
- if (SecCertificateHasOCSPNoCheckMarkerExtension(SecCertificatePathVCGetCertificateAtIndex(path, certIX)))
- {
- secdebug("rvc", "skipping revocation checks for no-check cert: %ld", certIX);
- TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(builder);
- if (analytics) {
- /* This certificate has OCSP No-Check, so add to reporting analytics */
- analytics->ocsp_no_check = true;
- }
- SecRVCSetFinishedWithoutNetwork(rvc);
- }
-
- if (rvc->done) {
- continue;
- }
-
-#if !TARGET_OS_BRIDGE
- /* Check valid database first (separate from OCSP response cache) */
- if (SecRVCCheckValidInfoDatabase(rvc)) {
- SecRVCProcessValidInfoResults(rvc);
- }
-#endif
- /* Any other revocation method requires an issuer certificate to verify the response;
- * skip the last cert in the chain since it doesn't have one. */
- if (certIX + 1 >= certCount) {
- continue;
- }
-
- /* Ignore stapled OCSP responses only if CRLs are enabled and the
- * policy specifically requested CRLs only. */
- if (SecRVCShouldCheckOCSP(rvc)) {
- /* If we have any OCSP stapled responses, check those first */
- SecORVCProcessStapledResponses(rvc->orvc);
- }
-
-#if TARGET_OS_BRIDGE
- /* The bridge has no writeable storage and no network. Nothing else we can
- * do here. */
- SecRVCSetFinishedWithoutNetwork(rvc);
- continue;
-#else // !TARGET_OS_BRIDGE
- /* Then check the caches for revocation results. */
- SecRVCCheckRevocationCaches(rvc);
-
- /* The check is done if we found cached responses from either method. */
- if (rvc->done || rvc->orvc->done) {
- secdebug("rvc", "found cached response for cert: %ld", certIX);
- SecRVCSetFinishedWithoutNetwork(rvc);
- continue;
- }
-
- /* If we got a cached response that is no longer valid (which can only be true for
- * revoked responses), let's try to get a fresher response even if no one asked.
- * This check resolves unrevocation events after the nextUpdate time. */
- bool old_cached_response = (!rvc->done && rvc->orvc->ocspResponse);
-
- /* If the cert is EV or if revocation checking was explicitly enabled, attempt to fire off an
- async http request for this cert's revocation status, unless we already successfully checked
- the revocation status of this cert based on the cache or stapled responses. */
- bool allow_fetch = SecRevocationCanAccessNetwork(builder, first_check_done) &&
- (SecCertificatePathVCIsEV(path) || SecCertificatePathVCIsOptionallyEV(path) ||
- SecPathBuilderGetRevocationMethod(builder) || old_cached_response);
- if (rvc->done || !allow_fetch) {
- /* We got a cache hit or we aren't allowed to access the network */
- SecRVCUpdatePVC(rvc);
- /* We didn't really start any background jobs for this cert. */
- (void)SecPathBuilderDecrementAsyncJobCount(builder);
- } else {
- (void)SecRVCFetchNext(rvc);
- }
-#endif // !TARGET_OS_BRIDGE
- }
-
- /* Return false if there are still async jobs running. */
- /* builder->asyncJobCount is atomic, so we know that if the job count is 0, all other
- * threads are finished. If the job count is > 0, other threads will decrement the job
- * count and SecPathBuilderStep to crank the state machine when the job count is 0. */
- return (SecPathBuilderDecrementAsyncJobCount(builder) == 0);
-}
-
-CFAbsoluteTime SecRVCGetEarliestNextUpdate(SecRVCRef rvc) {
- CFAbsoluteTime enu = NULL_TIME;
- if (!rvc || !rvc->orvc) { return enu; }
- enu = rvc->orvc->nextUpdate;
- return enu;
-}