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 <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>
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;
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);
if (rvc->builder) {
CFMutableDictionaryRef info = SecPathBuilderGetInfo(rvc->builder);
if (info) {
CFDictionarySetValue(info, kSecTrustRevocationReason, 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);
}
}
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(clientAuditToken,
signers, issuers, true, false,
policies, NULL, NULL, NULL,
verifyTime, NULL, NULL,
SecOCSPEvaluateCompleted, completed);
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
bool sr_valid = SecOCSPSingleResponseCalculateValidity(sr, kSecDefaultOCSPResponseTTL, verifyTime);
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);
}
}
#if ENABLE_CRLS
#include <../trustd/macOS/SecTrustOSXEntryPoints.h>
#define kSecDefaultCRLTTL kSecDefaultOCSPResponseTTL
struct OpaqueSecCRVC {
async_ocspd_t async_ocspd;
SecPathBuilderRef builder;
SecRVCRef rvc;
OSStatus status;
CFIndex certIX;
CFIndex distributionPointIX;
CFURLRef distributionPoint;
CFAbsoluteTime nextUpdate;
bool done;
};
static void SecCRVCFinish(SecCRVCRef crvc) {
}
#define MAX_CRL_DPS 3
#define CRL_REQUEST_THRESHOLD 10
static CFURLRef SecCRVCGetNextDistributionPoint(SecCRVCRef rvc) {
SecCertificateRef cert = SecPathBuilderGetCertificateAtIndex(rvc->builder, rvc->certIX);
CFArrayRef crlDPs = SecCertificateGetCRLDistributionPoints(cert);
if (crlDPs) {
CFIndex crlDPCount = CFArrayGetCount(crlDPs);
if (crlDPCount >= CRL_REQUEST_THRESHOLD) {
secnotice("rvc", "too many CRL DP entries (%ld)", (long)crlDPCount);
return NULL;
}
while (rvc->distributionPointIX < crlDPCount && rvc->distributionPointIX < MAX_CRL_DPS) {
CFURLRef distributionPoint = CFArrayGetValueAtIndex(crlDPs, rvc->distributionPointIX);
rvc->distributionPointIX++;
CFStringRef scheme = CFURLCopyScheme(distributionPoint);
if (scheme) {
bool valid_DP = (CFEqual(CFSTR("http"), scheme) ||
CFEqual(CFSTR("https"), scheme) ||
CFEqual(CFSTR("ldap"), scheme));
CFRelease(scheme);
if (valid_DP)
return distributionPoint;
}
}
}
return NULL;
}
static void SecCRVCGetCRLStatus(SecCRVCRef rvc) {
SecCertificateRef cert = SecPathBuilderGetCertificateAtIndex(rvc->builder, rvc->certIX);
SecCertificatePathVCRef path = SecPathBuilderGetPath(rvc->builder);
CFArrayRef serializedCertPath = SecCertificatePathVCCreateSerialized(path);
secdebug("rvc", "searching CRL cache for cert: %ld", rvc->certIX);
rvc->status = SecTrustLegacyCRLStatus(cert, serializedCertPath, rvc->distributionPoint);
CFReleaseNull(serializedCertPath);
if (rvc->status == errSecSuccess || rvc->status == errSecCertificateRevoked) {
rvc->done = true;
rvc->nextUpdate = SecPathBuilderGetVerifyTime(rvc->builder) + kSecDefaultCRLTTL;
}
}
static void SecCRVCCheckRevocationCache(SecCRVCRef rvc) {
while ((rvc->distributionPoint = SecCRVCGetNextDistributionPoint(rvc))) {
SecCRVCGetCRLStatus(rvc);
if (rvc->status == errSecCertificateRevoked) {
return;
}
}
}
static bool SecCRVCFetchNext(SecCRVCRef rvc) {
while ((rvc->distributionPoint = SecCRVCGetNextDistributionPoint(rvc))) {
SecCertificateRef cert = SecPathBuilderGetCertificateAtIndex(rvc->builder, rvc->certIX);
SecCertificatePathVCRef path = SecPathBuilderGetPath(rvc->builder);
CFArrayRef serializedCertPath = SecCertificatePathVCCreateSerialized(path);
secinfo("rvc", "fetching CRL for cert: %ld", rvc->certIX);
if (!SecTrustLegacyCRLFetch(&rvc->async_ocspd, rvc->distributionPoint,
CFAbsoluteTimeGetCurrent(), cert, serializedCertPath)) {
CFDataRef clientAuditToken = NULL;
SecTaskRef task = NULL;
audit_token_t auditToken = {};
clientAuditToken = SecPathBuilderCopyClientAuditToken(rvc->builder);
require(clientAuditToken, out);
require(sizeof(auditToken) == CFDataGetLength(clientAuditToken), out);
CFDataGetBytes(clientAuditToken, CFRangeMake(0, sizeof(auditToken)), (uint8_t *)&auditToken);
require(task = SecTaskCreateWithAuditToken(NULL, auditToken), out);
secnotice("rvc", "asynchronously fetching CRL (%@) for client (%@)",
rvc->distributionPoint, task);
out:
CFReleaseNull(clientAuditToken);
CFReleaseNull(task);
return false;
}
}
rvc->done = true;
return true;
}
static void SecCRVCUpdatePVC(SecCRVCRef rvc) {
if (rvc->status == errSecCertificateRevoked) {
secdebug("rvc", "CRL revoked cert %" PRIdCFIndex, rvc->certIX);
SInt32 reason = 0; CFNumberRef cfreason = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &reason);
SecPathBuilderSetResultInPVCs(rvc->builder, kSecPolicyCheckRevocation, rvc->certIX,
cfreason, true);
if (rvc->builder) {
CFMutableDictionaryRef info = SecPathBuilderGetInfo(rvc->builder);
if (info) {
CFDictionarySetValue(info, kSecTrustRevocationReason, cfreason);
}
}
CFReleaseNull(cfreason);
}
}
static void SecCRVCFetchCompleted(async_ocspd_t *ocspd) {
SecCRVCRef rvc = ocspd->info;
SecPathBuilderRef builder = rvc->builder;
TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(builder);
if (analytics) {
analytics->crl_fetch_time += (mach_absolute_time() - ocspd->start_time);
}
if (ocspd->response == errSecSuccess || ocspd->response == errSecCertificateRevoked) {
rvc->status = ocspd->response;
rvc->done = true;
rvc->nextUpdate = SecPathBuilderGetVerifyTime(rvc->builder) + kSecDefaultCRLTTL;
secdebug("rvc", "got CRL response for cert: %ld", rvc->certIX);
SecCRVCUpdatePVC(rvc);
if (!SecPathBuilderDecrementAsyncJobCount(builder)) {
secdebug("rvc", "done with all async jobs");
SecPathBuilderStep(builder);
}
} else {
if (analytics) {
analytics->crl_fetch_failed++;
}
if(SecCRVCFetchNext(rvc)) {
if (!SecPathBuilderDecrementAsyncJobCount(builder)) {
secdebug("rvc", "done with all async jobs");
SecPathBuilderStep(builder);
}
}
}
}
static SecCRVCRef SecCRVCCreate(SecRVCRef rvc, SecPathBuilderRef builder, CFIndex certIX) {
SecCRVCRef crvc = NULL;
crvc = malloc(sizeof(struct OpaqueSecCRVC));
if (crvc) {
memset(crvc, 0, sizeof(struct OpaqueSecCRVC));
crvc->builder = builder;
crvc->rvc = rvc;
crvc->certIX = certIX;
crvc->status = errSecInternal;
crvc->distributionPointIX = 0;
crvc->distributionPoint = NULL;
crvc->nextUpdate = NULL_TIME;
crvc->async_ocspd.queue = SecPathBuilderGetQueue(builder);
crvc->async_ocspd.completed = SecCRVCFetchCompleted;
crvc->async_ocspd.response = errSecInternal;
crvc->async_ocspd.info = crvc;
crvc->done = false;
}
return crvc;
}
static bool SecRVCShouldCheckCRL(SecRVCRef rvc) {
CFStringRef revocation_method = SecPathBuilderGetRevocationMethod(rvc->builder);
TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(rvc->builder);
if (revocation_method &&
CFEqual(kSecPolicyCheckRevocationCRL, revocation_method)) {
secinfo("rvc", "client told us to check CRL");
if (analytics) {
analytics->crl_client = true;
}
return true;
}
SecCertificateRef cert = SecPathBuilderGetCertificateAtIndex(rvc->builder, rvc->certIX);
CFArrayRef ocspResponders = SecCertificateGetOCSPResponders(cert);
if ((!ocspResponders || CFArrayGetCount(ocspResponders) == 0) &&
(revocation_method && !CFEqual(kSecPolicyCheckRevocationOCSP, revocation_method))) {
secinfo("rvc", "client told us to check revocation and CRL is only option for cert: %ld", rvc->certIX);
if (analytics) {
analytics->crl_cert = true;
}
return true;
}
return false;
}
#endif
void SecRVCDelete(SecRVCRef rvc) {
secdebug("alloc", "delete rvc %p", rvc);
if (rvc->orvc) {
SecORVCFinish(rvc->orvc);
free(rvc->orvc);
rvc->orvc = NULL;
}
#if ENABLE_CRLS
if (rvc->crvc) {
SecCRVCFinish(rvc->crvc);
free(rvc->crvc);
rvc->crvc = NULL;
}
#endif
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 ENABLE_CRLS
rvc->crvc = SecCRVCCreate(rvc, builder, certIX);
#endif
if (!rvc->orvc
#if ENABLE_CRLS
|| !rvc->crvc
#endif
) {
SecRVCDelete(rvc);
SecRVCSetFinishedWithoutNetwork(rvc);
} else {
rvc->done = false;
}
}
#if ENABLE_CRLS
static bool SecRVCShouldCheckOCSP(SecRVCRef rvc) {
CFStringRef revocation_method = SecPathBuilderGetRevocationMethod(rvc->builder);
if (!revocation_method
|| !CFEqual(revocation_method, kSecPolicyCheckRevocationCRL)) {
return true;
}
return false;
}
#else
static bool SecRVCShouldCheckOCSP(SecRVCRef rvc) {
return true;
}
#endif
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 |= TAValidDateContrainedRevoked;
}
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);
CFMutableDictionaryRef info = SecPathBuilderGetInfo(rvc->builder);
if (info) {
CFDictionarySetValue(info, kSecTrustRevocationReason, 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);
CFMutableDictionaryRef info = SecPathBuilderGetInfo(rvc->builder);
if (info) {
CFDictionarySetValue(info, kSecTrustRevocationReason, 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);
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);
}
secdebug("validupdate", "rvc: definitely %s cert %" PRIdCFIndex,
(allowed) ? "allowed" : "revoked", rvc->certIX);
rvc->done = true;
return;
}
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;
}
}
#if ENABLE_CRLS
if (SecRVCShouldCheckCRL(rvc)) {
SecCRVCCheckRevocationCache(rvc->crvc);
}
#endif
}
static void SecRVCUpdatePVC(SecRVCRef rvc) {
SecRVCProcessValidInfoResults(rvc);
if (rvc->orvc) { SecORVCUpdatePVC(rvc->orvc); }
#if ENABLE_CRLS
if (rvc->crvc) { SecCRVCUpdatePVC(rvc->crvc); }
#endif
}
static void SecRVCSetFinishedWithoutNetwork(SecRVCRef rvc) {
rvc->done = true;
SecRVCUpdatePVC(rvc);
(void)SecPathBuilderDecrementAsyncJobCount(rvc->builder);
#if ENABLE_CRLS
(void)SecPathBuilderDecrementAsyncJobCount(rvc->builder);
#endif
}
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);
}
#if ENABLE_CRLS
bool CRL_fetch_finished = true;
if (SecRVCShouldCheckCRL(rvc)) {
rvc->crvc->distributionPointIX = 0;
CRL_fetch_finished &= SecCRVCFetchNext(rvc->crvc);
}
if (CRL_fetch_finished) {
(void)SecPathBuilderDecrementAsyncJobCount(rvc->builder);
} else if (analytics) {
analytics->crl_fetches++;
}
OCSP_fetch_finished &= CRL_fetch_finished;
#endif
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);
#if !ENABLE_CRLS
SecPathBuilderSetAsyncJobCount(builder, (unsigned int)(certCount));
#else
SecPathBuilderSetAsyncJobCount(builder, 2 * (unsigned int)(certCount) - 1);
#endif
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 (SecCertificateHasMarkerExtension(SecCertificatePathVCGetCertificateAtIndex(path, certIX),
CFSTR("1.3.6.1.5.5.7.48.1.5"))) {
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;
#endif
SecRVCCheckRevocationCaches(rvc);
if (rvc->done || rvc->orvc->done
#if ENABLE_CRLS
|| rvc->crvc->done
#endif
) {
secdebug("rvc", "found cached response for cert: %ld", certIX);
SecRVCSetFinishedWithoutNetwork(rvc);
continue;
}
bool old_cached_response = (!rvc->done && rvc->orvc->ocspResponse);
bool allow_fetch = SecRevocationCanAccessNetwork(builder, first_check_done) &&
(SecCertificatePathVCIsEV(path) || SecCertificatePathVCIsOptionallyEV(path) ||
SecPathBuilderGetRevocationMethod(builder) || old_cached_response);
if (rvc->done || !allow_fetch) {
SecRVCUpdatePVC(rvc);
(void)SecPathBuilderDecrementAsyncJobCount(builder);
#if ENABLE_CRLS
(void)SecPathBuilderDecrementAsyncJobCount(builder);
#endif
} else {
(void)SecRVCFetchNext(rvc);
}
}
return (SecPathBuilderDecrementAsyncJobCount(builder) == 0);
}
CFAbsoluteTime SecRVCGetEarliestNextUpdate(SecRVCRef rvc) {
CFAbsoluteTime enu = NULL_TIME;
if (!rvc || !rvc->orvc) { return enu; }
enu = rvc->orvc->nextUpdate;
#if ENABLE_CRLS
CFAbsoluteTime crlNextUpdate = rvc->crvc->nextUpdate;
if (enu == NULL_TIME ||
((crlNextUpdate > NULL_TIME) && (enu > crlNextUpdate))) {
enu = crlNextUpdate;
}
#endif
return enu;
}