SecRevocationServer.c [plain text]
#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 "trust/trustd/SecTrustServer.h"
#include "trust/trustd/SecOCSPRequest.h"
#include "trust/trustd/SecOCSPResponse.h"
#include "trust/trustd/SecOCSPCache.h"
#include "trust/trustd/SecRevocationDb.h"
#include "trust/trustd/SecCertificateServer.h"
#include "trust/trustd/SecPolicyServer.h"
#include "trust/trustd/SecRevocationNetworking.h"
#include "trust/trustd/SecRevocationServer.h"
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));
}
static bool SecOCSPSingleResponseProcess(SecOCSPSingleResponseRef this,
SecORVCRef rvc) {
bool processed;
switch (this->certStatus) {
case CS_Good:
secdebug("ocsp", "CS_Good for cert %" PRIdCFIndex, rvc->certIX);
rvc->nextUpdate = this->nextUpdate == NULL_TIME ? this->thisUpdate + kSecDefaultOCSPResponseTTL : this->nextUpdate;
rvc->thisUpdate = this->thisUpdate;
processed = true;
break;
case CS_Revoked:
secdebug("ocsp", "CS_Revoked for cert %" PRIdCFIndex, rvc->certIX);
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:
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);
rvc->thisUpdate = SecOCSPResponseProducedAt(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;
}
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);
SecPathBuilderSetCanAccessNetwork(oBuilder, false);
SecPathBuilderStep(oBuilder);
CFReleaseNull(clientAuditToken);
CFReleaseNull(policies);
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)) {
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;
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 {
secnotice("ocsp", "ocsp response signed by certificate which "
"does not satisfy ocspSigner policy");
trusted = false;
}
CFReleaseNull(signerCerts);
CFReleaseNull(issuerCerts);
}
} else {
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 ,
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));
require_quiet(!rvc->ocspSingleResponse || rvc->ocspSingleResponse->thisUpdate < sr->thisUpdate, errOut);
CFAbsoluteTime verifyTime = CFAbsoluteTimeGetCurrent();
#if TARGET_OS_IPHONE
if (!fromCache) {
require_quiet(SecOCSPResponseVerify(ocspResponse, rvc,
sr->certStatus == CS_Revoked ? SecOCSPResponseProducedAt(ocspResponse) : verifyTime), errOut);
}
#else
require_quiet(SecOCSPResponseVerify(ocspResponse, rvc,
sr->certStatus == CS_Revoked ? SecOCSPResponseProducedAt(ocspResponse) : verifyTime), errOut);
#endif
TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(rvc->builder);
if (analytics && SecOCSPResponseIsWeakHash(ocspResponse)) {
analytics->ocsp_weak_hash = true;
}
bool sr_valid = SecOCSPSingleResponseCalculateValidity(sr, kSecDefaultOCSPResponseTTL, verifyTime);
if (sr_valid) {
rvc->rvc->revocation_checked = true;
}
if (sr->certStatus == CS_Good) {
require_quiet(sr_valid && SecOCSPResponseCalculateValidity(ocspResponse, maxAge, kSecDefaultOCSPResponseTTL, verifyTime), errOut);
} else if (sr->certStatus == CS_Revoked) {
ocspResponse->expireTime = SecCertificateNotValidAfter(SecPathBuilderGetCertificateAtIndex(rvc->builder, rvc->certIX));
}
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) {
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);
}
}
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;
}
SecValidPolicy policyType = kSecValidPolicyAny;
CFStringRef policyName = SecPolicyGetName(policy);
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;
}
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) {
bool result = false;
CFIndex length = 0;
SecValidPolicy *p = NULL;
if (data) {
length = CFDataGetLength(data);
p = (SecValidPolicy *)CFDataGetBytePtr(data);
}
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);
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;
if (rvc->valid_info->overridable) {
SecPVCSetResultForcedWithTrustResult(pvc, kSecPolicyCheckIssuerPolicyConstraints, rvc->certIX,
kCFBooleanFalse, true, kSecTrustResultRecoverableTrustFailure);
} else {
SecPVCSetResultForced(pvc, kSecPolicyCheckIssuerPolicyConstraints, rvc->certIX, kCFBooleanFalse, true);
}
}
}
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;
CFAbsoluteTime caNotAfter = 31556908800.0;
if (rvc->valid_info->notBeforeDate) {
caNotBefore = CFDateGetAbsoluteTime(rvc->valid_info->notBeforeDate);
}
if (rvc->valid_info->notAfterDate) {
caNotAfter = CFDateGetAbsoluteTime(rvc->valid_info->notAfterDate);
CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
if (caNotAfter < now) {
rvc->valid_info->requireCT = true;
}
}
if ((certIssued < caNotBefore) && (rvc->certIX > 0)) {
return;
}
TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(rvc->builder);
if ((certIssued < caNotBefore) || (certIssued > caNotAfter)) {
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) {
SecPathBuilderSetResultInPVCs(rvc->builder, kSecPolicyCheckGrayListedKey, rvc->certIX,
kCFBooleanFalse, true);
} else {
SInt32 reason = 0;
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;
if (info->format == kSecValidInfoFormatSerial ||
info->format == kSecValidInfoFormatSHA256) {
if (info->noCACheck || info->complete || info->isOnList) {
return true;
}
} else {
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;
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) {
SecPathBuilderSetResultInPVCs(rvc->builder, kSecPolicyCheckGrayListedLeaf, rvc->certIX,
kCFBooleanFalse, true);
return;
}
if (!SecRVCHasRevokedValidInfo(rvc) || rvc->valid_info->noCACheck) {
SecPathBuilderSetResultInPVCs(rvc->builder, kSecPolicyCheckBlackListedLeaf, rvc->certIX,
kCFBooleanFalse, true);
return;
}
SInt32 reason = 0;
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);
}
bool SecRVCRevocationChecked(SecRVCRef rvc) {
return rvc->revocation_checked;
}
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);
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;
}
if (info->noCACheck) {
bool allowed = (info->valid && info->complete && info->isOnList);
if (revoked) {
SecRVCSetValidDeterminedErrorResult(rvc);
} else if (allowed) {
SecCertificatePathVCSetIsAllowlisted(path, true);
rvc->revocation_checked = true;
}
secdebug("validupdate", "rvc: definitely %s cert %" PRIdCFIndex,
(allowed) ? "allowed" : "revoked", rvc->certIX);
rvc->done = true;
return;
}
SecRVCProcessValidPolicyConstraints(rvc);
SecRVCProcessValidDateConstraints(rvc);
if (info->requireCT) {
SecPathCTPolicy ctp = kSecPathCTRequired;
if (info->overridable) {
ctp = kSecPathCTRequiredOverridable;
}
SecCertificatePathVCSetRequiresCT(path, ctp);
}
if (!definitive || revoked) {
info->checkOCSP = true;
}
if (info->checkOCSP) {
CFIndex count = SecPathBuilderGetCertificateCount(rvc->builder);
CFIndex issuerIX = rvc->certIX + 1;
if (issuerIX >= count) {
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) {
analytics->valid_trigger_ocsp = true;
}
}
}
static bool SecRVCCheckValidInfoDatabase(SecRVCRef rvc) {
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;
}
}
SecRevocationDbCheckNextUpdate();
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) {
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) {
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) {
analytics->ocsp_cache_hit = true;
}
}
}
static void SecRVCUpdatePVC(SecRVCRef rvc) {
SecRVCProcessValidInfoResults(rvc);
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);
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) {
analytics->ocsp_network = true;
}
}
if (OCSP_fetch_finished) {
(void)SecPathBuilderDecrementAsyncJobCount(rvc->builder);
}
return OCSP_fetch_finished;
}
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) {
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) {
if (SecPathBuilderGetCheckRevocationIfTrusted(builder)) {
if (first_check_done) {
return true;
} else {
return false;
}
}
return SecPathBuilderCanAccessNetwork(builder);
}
void SecPathBuilderCheckKnownIntermediateConstraints(SecPathBuilderRef builder) {
SecCertificatePathVCRef path = SecPathBuilderGetPath(builder);
if (!path) {
return;
}
CFIndex certIX = kCFNotFound;
if (SecCertificatePathVCCheckedIssuers(path)) {
certIX = SecCertificatePathVCUnknownCAIndex(path);
goto checkedIssuers;
}
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) {
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) {
SecCertificateRef cert = SecCertificatePathVCGetCertificateAtIndex(path, certIX);
if (cert && SecCertificateIsCA(cert) && !SecRevocationDbContainsIssuer(cert)) {
break;
}
}
}
}
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) {
SecRVCSetValidDeterminedErrorResult(SecCertificatePathVCGetRVCAtIndex(path, certIX));
}
}
bool SecPathBuilderCheckRevocation(SecPathBuilderRef builder) {
secdebug("rvc", "checking revocation");
CFIndex certIX, certCount = SecPathBuilderGetCertificateCount(builder);
SecCertificatePathVCRef path = SecPathBuilderGetPath(builder);
if (certCount <= 1) {
return true;
}
bool first_check_done = false;
if (SecRevocationDidCheckRevocation(builder, &first_check_done)) {
return true;
}
SecCertificatePathVCAllocateRVCs(path, certCount);
SecPathBuilderSetAsyncJobCount(builder, (unsigned int)(certCount));
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);
if (SecCertificateHasOCSPNoCheckMarkerExtension(SecCertificatePathVCGetCertificateAtIndex(path, certIX)))
{
secdebug("rvc", "skipping revocation checks for no-check cert: %ld", certIX);
TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(builder);
if (analytics) {
analytics->ocsp_no_check = true;
}
SecRVCSetFinishedWithoutNetwork(rvc);
}
if (rvc->done) {
continue;
}
#if !TARGET_OS_BRIDGE
if (SecRVCCheckValidInfoDatabase(rvc)) {
SecRVCProcessValidInfoResults(rvc);
}
#endif
if (certIX + 1 >= certCount) {
continue;
}
if (SecRVCShouldCheckOCSP(rvc)) {
SecORVCProcessStapledResponses(rvc->orvc);
}
#if TARGET_OS_BRIDGE
SecRVCSetFinishedWithoutNetwork(rvc);
continue;
#else // !TARGET_OS_BRIDGE
SecRVCCheckRevocationCaches(rvc);
if (rvc->done || rvc->orvc->done) {
secdebug("rvc", "found cached response for cert: %ld", certIX);
SecRVCSetFinishedWithoutNetwork(rvc);
continue;
}
bool old_cached_response = (!rvc->done && rvc->orvc->ocspResponse);
bool has_ca_revocation = (certIX < SecCertificatePathVCIndexOfCAWithRevocationAdditions(path));
bool allow_fetch = SecRevocationCanAccessNetwork(builder, first_check_done) &&
(SecCertificatePathVCIsEV(path) || SecCertificatePathVCIsOptionallyEV(path) ||
SecPathBuilderGetRevocationMethod(builder) || old_cached_response || has_ca_revocation);
if (rvc->done || !allow_fetch) {
SecRVCUpdatePVC(rvc);
(void)SecPathBuilderDecrementAsyncJobCount(builder);
} else {
(void)SecRVCFetchNext(rvc);
}
#endif // !TARGET_OS_BRIDGE
}
return (SecPathBuilderDecrementAsyncJobCount(builder) == 0);
}
CFAbsoluteTime SecRVCGetEarliestNextUpdate(SecRVCRef rvc) {
CFAbsoluteTime enu = NULL_TIME;
if (!rvc || !rvc->orvc) { return enu; }
enu = rvc->orvc->nextUpdate;
return enu;
}
CFAbsoluteTime SecRVCGetLatestThisUpdate(SecRVCRef rvc) {
if (!rvc || !rvc->orvc) { return -DBL_MAX; }
return rvc->orvc->thisUpdate;
}