#include <securityd/SecTrustServer.h>
#include <securityd/SecPolicyServer.h>
#include <securityd/SecTrustLoggingServer.h>
#include <securityd/SecCertificateSource.h>
#include <securityd/SecRevocationServer.h>
#include <securityd/SecCertificateServer.h>
#include <securityd/SecPinningDb.h>
#include <utilities/SecIOFormat.h>
#include <utilities/SecDispatchRelease.h>
#include <utilities/SecAppleAnchorPriv.h>
#include <Security/SecTrustPriv.h>
#include <Security/SecItem.h>
#include <Security/SecCertificateInternal.h>
#include <Security/SecCertificatePath.h>
#include <Security/SecFramework.h>
#include <Security/SecPolicyPriv.h>
#include <Security/SecPolicyInternal.h>
#include <Security/SecTrustSettingsPriv.h>
#include <Security/SecTask.h>
#include <CoreFoundation/CFRuntime.h>
#include <CoreFoundation/CFSet.h>
#include <CoreFoundation/CFString.h>
#include <CoreFoundation/CFNumber.h>
#include <CoreFoundation/CFArray.h>
#include <CoreFoundation/CFPropertyList.h>
#include <AssertMacros.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <sys/codesign.h>
#include <Security/SecBase.h>
#include "SecRSAKey.h"
#include <libDER/oids.h>
#include <utilities/debugging.h>
#include <utilities/SecCFWrappers.h>
#include <Security/SecInternal.h>
#include <ipc/securityd_client.h>
#include <CommonCrypto/CommonDigest.h>
#include "OTATrustUtilities.h"
#include "personalization.h"
#include <utilities/SecInternalReleasePriv.h>
#if TARGET_OS_OSX
#include <Security/SecTaskPriv.h>
#endif
#define MAX_CHAIN_LENGTH 15
#define ACCEPT_PATH_SCORE 10000000
static void SecPathBuilderExtendPaths(void *context, CFArrayRef parents);
struct SecPathBuilder {
dispatch_queue_t queue;
CFDataRef clientAuditToken;
SecCertificateSourceRef certificateSource;
SecCertificateSourceRef itemCertificateSource;
SecCertificateSourceRef anchorSource;
SecCertificateSourceRef appleAnchorSource;
CFMutableArrayRef anchorSources;
CFIndex nextParentSource;
CFMutableArrayRef parentSources;
CFArrayRef ocspResponses; CFArrayRef signedCertificateTimestamps; CFArrayRef trustedLogs; CFAbsoluteTime verifyTime;
CFArrayRef exceptions;
CFMutableSetRef allPaths;
CFMutableArrayRef partialPaths;
CFMutableArrayRef rejectedPaths;
CFMutableArrayRef candidatePaths;
CFIndex partialIX;
bool considerRejected;
bool considerPartials;
bool canAccessNetwork;
SecPVCRef * pvcs;
CFIndex pvcCount;
SecCertificatePathVCRef path;
unsigned int asyncJobCount;
bool online_revocation;
CFStringRef revocation_check_method;
SecCertificatePathVCRef bestPath;
CFMutableDictionaryRef info;
CFIndex activations;
bool (*state)(SecPathBuilderRef);
SecPathBuilderCompleted completed;
const void *context;
};
static bool SecPathBuilderProcessLeaf(SecPathBuilderRef builder);
static bool SecPathBuilderGetNext(SecPathBuilderRef builder);
static bool SecPathBuilderValidatePath(SecPathBuilderRef builder);
static bool SecPathBuilderDidValidatePath(SecPathBuilderRef builder);
static bool SecPathBuilderComputeDetails(SecPathBuilderRef builder);
static bool SecPathBuilderReportResult(SecPathBuilderRef builder);
static bool SecPathBuilderIsAnchor(SecPathBuilderRef builder,
SecCertificateRef certificate, SecCertificateSourceRef *foundInSource);
static void SecPathBuilderSetPath(SecPathBuilderRef builder, SecCertificatePathVCRef path);
static void SecPathBuilderInit(SecPathBuilderRef builder,
CFDataRef clientAuditToken, CFArrayRef certificates,
CFArrayRef anchors, bool anchorsOnly, bool keychainsAllowed,
CFArrayRef policies, CFArrayRef ocspResponses,
CFArrayRef signedCertificateTimestamps, CFArrayRef trustedLogs,
CFAbsoluteTime verifyTime, CFArrayRef accessGroups, CFArrayRef exceptions,
SecPathBuilderCompleted completed, const void *context) {
secdebug("alloc", "%p", builder);
CFAllocatorRef allocator = kCFAllocatorDefault;
builder->clientAuditToken = (CFDataRef)
((clientAuditToken) ? CFRetain(clientAuditToken) : NULL);
builder->queue = dispatch_queue_create("builder", DISPATCH_QUEUE_SERIAL);
builder->nextParentSource = 1;
#if !TARGET_OS_WATCH
builder->canAccessNetwork = true;
#endif
builder->anchorSources = CFArrayCreateMutable(allocator, 0, NULL);
builder->parentSources = CFArrayCreateMutable(allocator, 0, NULL);
builder->allPaths = CFSetCreateMutable(allocator, 0,
&kCFTypeSetCallBacks);
builder->partialPaths = CFArrayCreateMutable(allocator, 0, NULL);
builder->rejectedPaths = CFArrayCreateMutable(allocator, 0, NULL);
builder->candidatePaths = CFArrayCreateMutable(allocator, 0, NULL);
builder->pvcs = malloc(sizeof(SecPVCRef));
builder->pvcs[0] = malloc(sizeof(struct OpaqueSecPVC));
SecPVCInit(builder->pvcs[0], builder, policies);
builder->pvcCount = 1;
builder->verifyTime = verifyTime;
builder->exceptions = CFRetainSafe(exceptions);
builder->certificateSource =
SecMemoryCertificateSourceCreate(certificates);
if (anchors) {
builder->anchorSource = SecMemoryCertificateSourceCreate(anchors);
}
bool allowNonProduction = false;
builder->appleAnchorSource = SecMemoryCertificateSourceCreate(SecGetAppleTrustAnchors(allowNonProduction));
#if !TARGET_OS_BRIDGE
CFArrayAppendValue(builder->parentSources, builder->certificateSource);
builder->itemCertificateSource = SecItemCertificateSourceCreate(accessGroups);
if (keychainsAllowed) {
CFArrayAppendValue(builder->parentSources, builder->itemCertificateSource);
#if TARGET_OS_OSX
if (kSecLegacyCertificateSource->contains && kSecLegacyCertificateSource->copyParents) {
CFArrayAppendValue(builder->parentSources, kSecLegacyCertificateSource);
}
#endif
}
if (anchorsOnly) {
CFArrayAppendValue(builder->parentSources, builder->appleAnchorSource);
CFArrayAppendValue(builder->parentSources, kSecSystemAnchorSource);
#if TARGET_OS_IPHONE
CFArrayAppendValue(builder->parentSources, kSecUserAnchorSource);
#endif
}
if (keychainsAllowed && builder->canAccessNetwork) {
CFArrayAppendValue(builder->parentSources, kSecCAIssuerSource);
}
#else
CFArrayAppendValue(builder->parentSources, builder->certificateSource);
if (anchorsOnly) {
CFArrayAppendValue(builder->parentSources, builder->appleAnchorSource);
}
#endif
#if !TARGET_OS_BRIDGE
if (builder->anchorSource) {
CFArrayAppendValue(builder->anchorSources, builder->anchorSource);
}
if (!anchorsOnly) {
CFArrayAppendValue(builder->anchorSources, builder->appleAnchorSource);
#if TARGET_OS_IPHONE
CFArrayAppendValue(builder->anchorSources, kSecUserAnchorSource);
#else
if (keychainsAllowed && kSecLegacyAnchorSource->contains && kSecLegacyAnchorSource->copyParents) {
CFArrayAppendValue(builder->anchorSources, kSecLegacyAnchorSource);
}
#endif
CFArrayAppendValue(builder->anchorSources, kSecSystemAnchorSource);
}
#else
if (builder->anchorSource) {
CFArrayAppendValue(builder->anchorSources, builder->anchorSource);
}
if (!anchorsOnly) {
CFArrayAppendValue(builder->anchorSources, builder->appleAnchorSource);
}
#endif
builder->ocspResponses = CFRetainSafe(ocspResponses);
builder->signedCertificateTimestamps = CFRetainSafe(signedCertificateTimestamps);
if(trustedLogs) {
builder->trustedLogs = CFRetainSafe(trustedLogs);
} else {
SecOTAPKIRef otapkiref = SecOTAPKICopyCurrentOTAPKIRef();
builder->trustedLogs = SecOTAPKICopyTrustedCTLogs(otapkiref);
CFReleaseSafe(otapkiref);
}
SecCertificateRef leaf =
(SecCertificateRef)CFArrayGetValueAtIndex(certificates, 0);
SecCertificatePathVCRef path = SecCertificatePathVCCreate(NULL, leaf, NULL);
CFSetAddValue(builder->allPaths, path);
CFArrayAppendValue(builder->partialPaths, path);
builder->path = CFRetainSafe(path);
SecPathBuilderSetPath(builder, path);
CFRelease(path);
builder->state = SecPathBuilderProcessLeaf;
builder->completed = completed;
builder->context = context;
}
SecPathBuilderRef SecPathBuilderCreate(CFDataRef clientAuditToken,
CFArrayRef certificates, CFArrayRef anchors, bool anchorsOnly,
bool keychainsAllowed, CFArrayRef policies, CFArrayRef ocspResponses,
CFArrayRef signedCertificateTimestamps, CFArrayRef trustedLogs,
CFAbsoluteTime verifyTime, CFArrayRef accessGroups, CFArrayRef exceptions,
SecPathBuilderCompleted completed, const void *context) {
SecPathBuilderRef builder = malloc(sizeof(*builder));
memset(builder, 0, sizeof(*builder));
SecPathBuilderInit(builder, clientAuditToken, certificates,
anchors, anchorsOnly, keychainsAllowed, policies, ocspResponses,
signedCertificateTimestamps, trustedLogs, verifyTime,
accessGroups, exceptions, completed, context);
return builder;
}
static void SecPathBuilderForEachPVC(SecPathBuilderRef builder,void (^operation)(SecPVCRef pvc, bool *stop)) {
if (!builder->pvcs) { return; }
bool stop = false;
CFIndex ix;
for (ix = 0; ix < builder->pvcCount; ix++) {
if (!builder->pvcs[ix]) { continue; }
operation(builder->pvcs[ix], &stop);
if (stop) { break; }
}
}
static void SecPathBuilderDestroy(SecPathBuilderRef builder) {
secdebug("alloc", "%p", builder);
dispatch_release_null(builder->queue);
if (builder->anchorSource) {
SecMemoryCertificateSourceDestroy(builder->anchorSource);
builder->anchorSource = NULL;
}
if (builder->certificateSource) {
SecMemoryCertificateSourceDestroy(builder->certificateSource);
builder->certificateSource = NULL;
}
if (builder->itemCertificateSource) {
SecItemCertificateSourceDestroy(builder->itemCertificateSource);
builder->itemCertificateSource = NULL;
}
if (builder->appleAnchorSource) {
SecMemoryCertificateSourceDestroy(builder->appleAnchorSource);
builder->appleAnchorSource = NULL;
}
CFReleaseNull(builder->clientAuditToken);
CFReleaseNull(builder->anchorSources);
CFReleaseNull(builder->parentSources);
CFReleaseNull(builder->allPaths);
CFReleaseNull(builder->partialPaths);
CFReleaseNull(builder->rejectedPaths);
CFReleaseNull(builder->candidatePaths);
CFReleaseNull(builder->ocspResponses);
CFReleaseNull(builder->signedCertificateTimestamps);
CFReleaseNull(builder->trustedLogs);
CFReleaseNull(builder->path);
CFReleaseNull(builder->revocation_check_method);
CFReleaseNull(builder->info);
CFReleaseNull(builder->exceptions);
if (builder->pvcs) {
CFIndex ix;
for (ix = 0; ix < builder->pvcCount; ix++) {
if (builder->pvcs[ix]) {
SecPVCDelete(builder->pvcs[ix]);
free(builder->pvcs[ix]);
}
}
free(builder->pvcs);
builder->pvcs = NULL;
}
}
static void SecPathBuilderSetPath(SecPathBuilderRef builder, SecCertificatePathVCRef path) {
bool samePath = ((!path && !builder->path) || (path && builder->path && CFEqual(path, builder->path)));
if (!samePath) {
CFRetainAssign(builder->path, path);
}
CFReleaseNull(builder->info);
SecPathBuilderForEachPVC(builder, ^(SecPVCRef pvc, bool * __unused stop) {
SecPVCSetPath(pvc, path);
});
}
bool SecPathBuilderCanAccessNetwork(SecPathBuilderRef builder) {
return builder->canAccessNetwork;
}
void SecPathBuilderSetCanAccessNetwork(SecPathBuilderRef builder, bool allow) {
if (builder->canAccessNetwork != allow) {
builder->canAccessNetwork = allow;
if (allow) {
#if !TARGET_OS_WATCH
secinfo("http", "network access re-enabled by policy");
CFArrayAppendValue(builder->parentSources, kSecCAIssuerSource);
#else
secnotice("http", "network access not allowed on WatchOS");
builder->canAccessNetwork = false;
#endif
} else {
secinfo("http", "network access disabled by policy");
CFIndex ix = CFArrayGetFirstIndexOfValue(builder->parentSources,
CFRangeMake(0, CFArrayGetCount(builder->parentSources)),
kSecCAIssuerSource);
if (ix >= 0)
CFArrayRemoveValueAtIndex(builder->parentSources, ix);
}
}
}
CFArrayRef SecPathBuilderCopyOCSPResponses(SecPathBuilderRef builder)
{
return CFRetainSafe(builder->ocspResponses);
}
CFArrayRef SecPathBuilderCopySignedCertificateTimestamps(SecPathBuilderRef builder)
{
return CFRetainSafe(builder->signedCertificateTimestamps);
}
CFArrayRef SecPathBuilderCopyTrustedLogs(SecPathBuilderRef builder)
{
return CFRetainSafe(builder->trustedLogs);
}
SecCertificatePathVCRef SecPathBuilderGetBestPath(SecPathBuilderRef builder)
{
return builder->bestPath;
}
SecCertificatePathVCRef SecPathBuilderGetPath(SecPathBuilderRef builder) {
return builder->path;
}
CFAbsoluteTime SecPathBuilderGetVerifyTime(SecPathBuilderRef builder) {
return builder->verifyTime;
}
CFIndex SecPathBuilderGetCertificateCount(SecPathBuilderRef builder) {
return SecCertificatePathVCGetCount(builder->path);
}
SecCertificateRef SecPathBuilderGetCertificateAtIndex(SecPathBuilderRef builder, CFIndex ix) {
return SecCertificatePathVCGetCertificateAtIndex(builder->path, ix);
}
bool SecPathBuilderIsAnchored(SecPathBuilderRef builder) {
return SecCertificatePathVCIsAnchored(builder->path);
}
unsigned int SecPathBuilderDecrementAsyncJobCount(SecPathBuilderRef builder) {
return --builder->asyncJobCount;
}
void SecPathBuilderSetAsyncJobCount(SecPathBuilderRef builder, unsigned int jobCount) {
builder->asyncJobCount = jobCount;
secdebug("rvc", "set asyncJobCount to %d", builder->asyncJobCount);
}
CFMutableDictionaryRef SecPathBuilderGetInfo(SecPathBuilderRef builder) {
return builder->info;
}
CFStringRef SecPathBuilderGetRevocationMethod(SecPathBuilderRef builder) {
return builder->revocation_check_method;
}
void SecPathBuilderSetRevocationMethod(SecPathBuilderRef builder, CFStringRef method) {
CFRetainAssign(builder->revocation_check_method, method);
secdebug("rvc", "deferred revocation checking enabled using %@ method", method);
}
bool SecPathBuilderGetCheckRevocationOnline(SecPathBuilderRef builder) {
return builder->online_revocation;
}
void SecPathBuilderSetCheckRevocationOnline(SecPathBuilderRef builder) {
builder->online_revocation = true;
secdebug("rvc", "revocation force online check");
}
CFArrayRef SecPathBuilderGetExceptions(SecPathBuilderRef builder) {
return builder->exceptions;
}
CFIndex SecPathBuilderGetPVCCount(SecPathBuilderRef builder) {
return builder->pvcCount;
}
SecPVCRef SecPathBuilderGetPVCAtIndex(SecPathBuilderRef builder, CFIndex ix) {
if (ix > (builder->pvcCount - 1)) {
return NULL;
}
return builder->pvcs[ix];
}
void SecPathBuilderSetResultInPVCs(SecPathBuilderRef builder, CFStringRef key,
CFIndex ix, CFTypeRef result, bool force,
SecTrustResultType resultType) {
SecPathBuilderForEachPVC(builder, ^(SecPVCRef pvc, bool * __unused stop) {
SecPVCSetResultForced(pvc, key, ix, result, force);
pvc->result = resultType;
});
}
static bool SecPathBuilderIsOkResult(SecPathBuilderRef builder) {
__block bool acceptPath = false;
SecPathBuilderForEachPVC(builder, ^(SecPVCRef pvc, bool * __unused stop) {
acceptPath |= SecPVCIsOkResult(pvc);
});
return acceptPath;
}
static SecPVCRef SecPathBuilderGetResultPVC(SecPathBuilderRef builder) {
__block SecPVCRef resultPVC = NULL;
SecPathBuilderForEachPVC(builder, ^(SecPVCRef pvc, bool *stop) {
if (SecPVCIsOkResult(pvc)) {
resultPVC = pvc;
*stop = true;
}
});
if (resultPVC) { return resultPVC; }
return builder->pvcs[0];
}
static bool SecPathBuilderIsAnchorPerConstraints(SecPathBuilderRef builder, SecCertificateSourceRef source,
SecCertificateRef certificate) {
__block bool result = false;
CFArrayRef constraints = NULL;
constraints = SecCertificateSourceCopyUsageConstraints(source, certificate);
Boolean selfSigned = false;
require(errSecSuccess == SecCertificateIsSelfSigned(certificate, &selfSigned), out);
if ((NULL == source->copyUsageConstraints) ||
(constraints && (CFArrayGetCount(constraints) == 0) && selfSigned)) {
secinfo("trust", "unrestricted anchor%s",
(NULL == source->copyUsageConstraints) ? " source" : "");
result = true;
goto out;
}
require(constraints, out);
SecPathBuilderForEachPVC(builder, ^(SecPVCRef pvc, bool * __unused stop) {
SecTrustSettingsResult settingsResult = kSecTrustSettingsResultInvalid;
settingsResult = SecPVCGetTrustSettingsResult(pvc,
certificate,
constraints);
if ((selfSigned && settingsResult == kSecTrustSettingsResultTrustRoot) ||
(!selfSigned && settingsResult == kSecTrustSettingsResultTrustAsRoot)) {
secinfo("trust", "complex trust settings anchor");
result = true;
*stop = true;
}
if (settingsResult == kSecTrustSettingsResultDeny) {
secinfo("trust", "complex trust settings denied anchor");
result = true;
*stop = true;
}
});
out:
CFReleaseNull(constraints);
return result;
}
static bool SecPathBuilderIsAnchor(SecPathBuilderRef builder,
SecCertificateRef certificate, SecCertificateSourceRef *foundInSource) {
CFIndex count = CFArrayGetCount(builder->anchorSources);
CFIndex ix;
for (ix = 0; ix < count; ++ix) {
SecCertificateSourceRef source = (SecCertificateSourceRef)
CFArrayGetValueAtIndex(builder->anchorSources, ix);
if (SecCertificateSourceContains(source, certificate)) {
if (foundInSource)
*foundInSource = source;
if (SecPathBuilderIsAnchorPerConstraints(builder, source, certificate)) {
return true;
}
}
}
return false;
}
bool SecPathBuilderIsAnchorSource(SecPathBuilderRef builder, SecCertificateSourceRef source) {
CFIndex anchorCount = CFArrayGetCount(builder->anchorSources);
return CFArrayContainsValue(builder->anchorSources, CFRangeMake(0,anchorCount), source);
}
static bool SecPathBuilderIsPartial(SecPathBuilderRef builder,
SecCertificatePathVCRef path) {
SecPathBuilderSetPath(builder, path);
__block bool parentChecksFail = true;
SecPathBuilderForEachPVC(builder, ^(SecPVCRef pvc, bool * __unused stop) {
parentChecksFail &= !SecPVCParentCertificateChecks(pvc,
SecCertificatePathVCGetCount(path) - 1);
});
if (!builder->considerRejected && parentChecksFail) {
secdebug("trust", "Found rejected path %@", path);
CFArrayAppendValue(builder->rejectedPaths, path);
return false;
}
SecPathVerifyStatus vstatus = SecCertificatePathVCVerify(path);
if (vstatus == kSecPathVerifyFailed) {
secdebug("trust", "Verify failed for path %@", path);
return false;
}
if (vstatus == kSecPathVerifySuccess) {
if (SecCertificatePathVCIsAnchored(path)) {
secdebug("trust", "Adding candidate %@", path);
CFArrayAppendValue(builder->candidatePaths, path);
}
if ((SecCertificatePathVCSelfSignedIndex(path) >= 0) &&
(SecCertificatePathVCSelfSignedIndex(path) == SecCertificatePathVCGetCount(path)-1)) {
return false;
}
}
return true;
}
static void addOptionsToPolicy(SecPolicyRef policy, CFDictionaryRef newOptions) {
__block CFMutableDictionaryRef oldOptions = CFDictionaryCreateMutableCopy(NULL, 0, policy->_options);
CFDictionaryForEach(newOptions, ^(const void *key, const void *value) {
CFDictionaryAddValue(oldOptions, key, value);
});
CFAssignRetained(policy->_options, oldOptions);
}
static void SecPathBuilderAddPinningPolicies(SecPathBuilderRef builder) {
CFIndex ix, initialPVCCount = builder->pvcCount;
for (ix = 0; ix < initialPVCCount; ix++) {
CFArrayRef policies = CFRetainSafe(builder->pvcs[ix]->policies);
CFIndex policyIX;
for (policyIX = 0; policyIX < CFArrayGetCount(policies); policyIX++) {
SecPolicyRef policy = (SecPolicyRef)CFArrayGetValueAtIndex(policies, policyIX);
CFStringRef policyName = SecPolicyGetName(policy);
CFStringRef hostname = CFDictionaryGetValue(policy->_options, kSecPolicyCheckSSLHostname);
if (!hostname) { continue; }
CFMutableDictionaryRef query = CFDictionaryCreateMutable(NULL, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionaryAddValue(query, kSecPinningDbKeyPolicyName, policyName);
CFDictionaryAddValue(query, kSecPinningDbKeyHostname, hostname);
CFDictionaryRef results = SecPinningDbCopyMatching(query);
CFReleaseNull(query);
if (!results) { continue; }
CFArrayRef newRules = CFDictionaryGetValue(results, kSecPinningDbKeyRules);
CFStringRef dbPolicyName = CFDictionaryGetValue(results, kSecPinningDbKeyPolicyName);
secinfo("trust", "found pinning %lu %@ policies for hostname %@, policyName %@",
(unsigned long)CFArrayGetCount(newRules), dbPolicyName, hostname, policyName);
CFIndex newRulesIX;
for (newRulesIX = 0; newRulesIX < CFArrayGetCount(newRules); newRulesIX++) {
if (!isDictionary(CFArrayGetValueAtIndex(newRules, newRulesIX))) {
continue;
}
CFDictionaryRef newOptions = (CFDictionaryRef)CFArrayGetValueAtIndex(newRules, newRulesIX);
SecPolicyRef newPolicy = SecPolicyCreateSSL(true, hostname);
if (!newPolicy) { continue; }
addOptionsToPolicy(newPolicy, newOptions);
SecPolicySetName(newPolicy, dbPolicyName);
CFMutableArrayRef newPolicies = CFArrayCreateMutableCopy(NULL, 0, policies);
if (!newPolicies) { CFReleaseNull(newPolicy); continue; }
CFArrayReplaceValues(newPolicies, CFRangeMake(policyIX, 1), (const void **)&newPolicy, 1);
if (newRulesIX == 0) {
CFRetainAssign(builder->pvcs[ix]->policies, newPolicies);
} else {
builder->pvcs = realloc(builder->pvcs, (builder->pvcCount + 1) * sizeof(SecPVCRef));
builder->pvcs[builder->pvcCount] = malloc(sizeof(struct OpaqueSecPVC));
SecPVCInit(builder->pvcs[builder->pvcCount], builder, newPolicies);
builder->pvcCount++;
}
CFReleaseNull(newPolicy);
CFReleaseNull(newPolicies);
}
CFReleaseNull(results);
}
CFReleaseNull(policies);
}
}
static bool SecPathBuilderProcessLeaf(SecPathBuilderRef builder) {
SecPathBuilderAddPinningPolicies(builder);
SecCertificatePathVCRef path = builder->path;
SecCertificateRef leaf = SecCertificatePathVCGetCertificateAtIndex(path, 0);
SecCertificateSourceRef source = NULL;
bool isAnchor = false;
CFArrayRef constraints = NULL;
if (SecPathBuilderIsAnchor(builder, leaf, &source)) {
isAnchor = true;
}
if (source) {
constraints = SecCertificateSourceCopyUsageConstraints(source, leaf);
}
SecCertificatePathVCSetUsageConstraintsAtIndex(path, constraints, 0);
CFReleaseSafe(constraints);
if (isAnchor) {
SecCertificatePathVCSetIsAnchored(path);
CFArrayAppendValue(builder->candidatePaths, path);
}
__block bool leafChecksFail = true;
SecPathBuilderForEachPVC(builder, ^(SecPVCRef pvc, bool * __unused stop) {
SecPVCLeafChecks(pvc);
leafChecksFail &= !SecPVCIsOkResult(pvc);
});
builder->considerRejected = leafChecksFail;
builder->state = SecPathBuilderGetNext;
return true;
}
static void SecPathBuilderProcessParents(SecPathBuilderRef builder,
SecCertificatePathVCRef partial, CFArrayRef parents) {
CFIndex rootIX = SecCertificatePathVCGetCount(partial) - 1;
CFIndex num_parents = parents ? CFArrayGetCount(parents) : 0;
CFIndex parentIX;
for (parentIX = 0; parentIX < num_parents; ++parentIX) {
SecCertificateRef parent = (SecCertificateRef)
CFArrayGetValueAtIndex(parents, parentIX);
CFIndex ixOfParent = SecCertificatePathVCGetIndexOfCertificate(partial,
parent);
if (ixOfParent != kCFNotFound) {
if (ixOfParent == rootIX) {
SecCertificatePathVCSetSelfIssued(partial);
}
continue;
}
SecCertificateSourceRef source = NULL;
bool is_anchor = SecPathBuilderIsAnchor(builder, parent, &source);
CFArrayRef constraints = (source) ? SecCertificateSourceCopyUsageConstraints(source, parent) : NULL;
SecCertificatePathVCRef path = SecCertificatePathVCCreate(partial, parent, constraints);
CFReleaseSafe(constraints);
if (!path)
continue;
if (!CFSetContainsValue(builder->allPaths, path)) {
CFSetAddValue(builder->allPaths, path);
if (is_anchor)
SecCertificatePathVCSetIsAnchored(path);
if (SecPathBuilderIsPartial(builder, path)) {
CFArrayInsertValueAtIndex(builder->partialPaths,
++builder->partialIX, path);
secdebug("trust", "Adding partial for parent %" PRIdCFIndex "/%" PRIdCFIndex " %@",
parentIX + 1, num_parents, path);
}
secdebug("trust", "found new path %@", path);
}
CFRelease(path);
}
}
static void SecPathBuilderExtendPaths(void *context, CFArrayRef parents) {
SecPathBuilderRef builder = (SecPathBuilderRef)context;
SecCertificatePathVCRef partial = (SecCertificatePathVCRef)
CFArrayGetValueAtIndex(builder->partialPaths, builder->partialIX);
secdebug("async", "%@ parents %@", partial, parents);
SecPathBuilderProcessParents(builder, partial, parents);
builder->state = SecPathBuilderGetNext;
SecPathBuilderStep(builder);
}
static bool SecPathBuilderGetNext(SecPathBuilderRef builder) {
if (CFArrayGetCount(builder->candidatePaths)) {
SecCertificatePathVCRef path = (SecCertificatePathVCRef)
CFArrayGetValueAtIndex(builder->candidatePaths, 0);
CFArrayRemoveValueAtIndex(builder->candidatePaths, 0);
secdebug("trust", "SecPathBuilderGetNext returning candidate %@",
path);
SecPathBuilderSetPath(builder, path);
builder->state = SecPathBuilderValidatePath;
return true;
}
if (builder->considerRejected) {
CFIndex rejectedIX = CFArrayGetCount(builder->rejectedPaths);
if (rejectedIX) {
rejectedIX--;
SecCertificatePathVCRef path = (SecCertificatePathVCRef)
CFArrayGetValueAtIndex(builder->rejectedPaths, rejectedIX);
if (SecPathBuilderIsPartial(builder, path)) {
CFArrayInsertValueAtIndex(builder->partialPaths,
++builder->partialIX, path);
}
CFArrayRemoveValueAtIndex(builder->rejectedPaths, rejectedIX);
return true;
}
}
if (builder->partialIX < 0) {
CFIndex num_sources = CFArrayGetCount(builder->parentSources);
if (builder->nextParentSource < num_sources) {
builder->nextParentSource++;
secdebug("trust", "broading search to %" PRIdCFIndex "/%" PRIdCFIndex " sources",
builder->nextParentSource, num_sources);
} else {
if (!builder->considerRejected) {
builder->considerRejected = true;
secdebug("trust", "considering rejected paths");
} else if (!builder->considerPartials) {
builder->considerPartials = true;
secdebug("trust", "considering partials");
} else {
builder->state = SecPathBuilderComputeDetails;
return true;
}
}
builder->partialIX = CFArrayGetCount(builder->partialPaths) - 1;
secdebug("trust", "re-checking %" PRIdCFIndex " partials", builder->partialIX + 1);
return true;
}
SecCertificatePathVCRef partial = (SecCertificatePathVCRef)
CFArrayGetValueAtIndex(builder->partialPaths, builder->partialIX);
if (builder->considerPartials) {
--builder->partialIX;
SecPathBuilderSetPath(builder, partial);
builder->state = SecPathBuilderValidatePath;
return true;
}
secdebug("trust", "looking for parents of partial %" PRIdCFIndex "/%" PRIdCFIndex ": %@",
builder->partialIX + 1, CFArrayGetCount(builder->partialPaths),
partial);
CFIndex sourceIX = SecCertificatePathVCGetNextSourceIndex(partial);
CFIndex num_anchor_sources = CFArrayGetCount(builder->anchorSources);
if (sourceIX < num_anchor_sources + builder->nextParentSource) {
SecCertificateSourceRef source;
if (sourceIX < num_anchor_sources) {
source = (SecCertificateSourceRef)
CFArrayGetValueAtIndex(builder->anchorSources, sourceIX);
secdebug("trust", "searching anchor source %" PRIdCFIndex "/%" PRIdCFIndex, sourceIX + 1,
num_anchor_sources);
} else {
CFIndex parentIX = sourceIX - num_anchor_sources;
source = (SecCertificateSourceRef)
CFArrayGetValueAtIndex(builder->parentSources, parentIX);
secdebug("trust", "searching parent source %" PRIdCFIndex "/%" PRIdCFIndex, parentIX + 1,
builder->nextParentSource);
}
SecCertificatePathVCSetNextSourceIndex(partial, sourceIX + 1);
SecCertificateRef root = SecCertificatePathVCGetRoot(partial);
return SecCertificateSourceCopyParents(source, root,
builder, SecPathBuilderExtendPaths);
} else {
--builder->partialIX;
}
return true;
}
static void SecPathBuilderReject(SecPathBuilderRef builder) {
check(builder);
builder->state = SecPathBuilderGetNext;
bool bestPathIsEV = SecCertificatePathVCIsEV(builder->bestPath);
bool isEV = SecCertificatePathVCIsEV(builder->path);
if (bestPathIsEV && !isEV) {
return;
}
CFIndex bestPathScore = SecCertificatePathVCGetScore(builder->bestPath);
CFIndex score = SecCertificatePathVCScore(builder->path, builder->verifyTime);
SecCertificatePathVCSetScore(builder->path, score);
if (isEV && !bestPathIsEV) {
bestPathScore = 0;
}
if (!builder->bestPath || score > bestPathScore) {
if (builder->bestPath) {
secinfo("reject",
"replacing %sev %s score: %ld with %sev score: %" PRIdCFIndex " %@",
(bestPathIsEV ? "" : "non "),
(bestPathScore > ACCEPT_PATH_SCORE ? "accept" : "reject"),
bestPathScore,
(isEV ? "" : "non "), (long)score, builder->path);
} else {
secinfo("reject", "%sev score: %" PRIdCFIndex " %@",
(isEV ? "" : "non "), score, builder->path);
}
builder->bestPath = builder->path;
} else {
secinfo("reject", "%sev score: %" PRIdCFIndex " lower than %" PRIdCFIndex " %@",
(isEV ? "" : "non "), score, bestPathScore, builder->path);
}
}
static void SecPathBuilderAccept(SecPathBuilderRef builder) {
if (!builder) { return; }
bool isSHA2 = !SecCertificatePathVCHasWeakHash(builder->path);
bool isOptionallySHA2 = !SecCertificateIsWeakHash(SecPathBuilderGetCertificateAtIndex(builder, 0));
bool isEV = SecCertificatePathVCIsEV(builder->path);
bool isOptionallyEV = SecCertificatePathVCIsOptionallyEV(builder->path);
CFIndex bestScore = SecCertificatePathVCGetScore(builder->bestPath);
CFIndex currScore = (SecCertificatePathVCScore(builder->path, builder->verifyTime) +
ACCEPT_PATH_SCORE + (isEV ? 1000000 : 0)); SecCertificatePathVCSetScore(builder->path, currScore);
if (currScore > bestScore) {
secinfo("accept", "replacing %sev %s score: %ld with %sev score: %" PRIdCFIndex " %@",
(SecCertificatePathVCIsEV(builder->bestPath) ? "" : "non "),
(bestScore > ACCEPT_PATH_SCORE ? "accept" : "reject"),
bestScore,
(isEV ? "" : "non "), (long)currScore, builder->path);
builder->bestPath = builder->path;
}
if ((isEV || !isOptionallyEV) && (isSHA2 || !isOptionallySHA2))
builder->state = SecPathBuilderComputeDetails;
else
builder->state = SecPathBuilderGetNext;
}
static bool SecPathBuilderValidatePath(SecPathBuilderRef builder) {
if (builder->considerRejected) {
SecPathBuilderReject(builder);
return true;
}
builder->state = SecPathBuilderDidValidatePath;
bool completed = SecPathBuilderCheckRevocation(builder);
SecPathBuilderForEachPVC(builder, ^(SecPVCRef pvc, bool * __unused stop) {
SecPVCPathChecks(pvc);
});
return completed;
}
static bool SecPathBuilderDidValidatePath(SecPathBuilderRef builder) {
SecPathBuilderForEachPVC(builder, ^(SecPVCRef pvc, bool * __unused stop) {
SecPVCPathCheckRevocationRequired(pvc);
});
if (SecPathBuilderIsOkResult(builder)) {
SecPathBuilderAccept(builder);
} else {
SecPathBuilderReject(builder);
}
assert(builder->state != SecPathBuilderDidValidatePath);
return true;
}
static bool SecPathBuilderComputeDetails(SecPathBuilderRef builder) {
SecPathBuilderSetPath(builder, builder->bestPath);
__block CFIndex ix, pathLength = SecCertificatePathVCGetCount(builder->bestPath);
__block bool completed = true;
SecPathBuilderForEachPVC(builder, ^(SecPVCRef pvc, bool * __unused stop) {
SecPVCComputeDetails(pvc, builder->bestPath);
completed &= SecPathBuilderCheckRevocation(builder);
for (ix = 1; ix < pathLength; ++ix) {
SecPVCParentCertificateChecks(pvc, ix);
}
SecPVCPathChecks(pvc);
});
builder->state = SecPathBuilderReportResult;
SecPathBuilderForEachPVC(builder, ^(SecPVCRef pvc, bool * __unused stop) {
SecPVCPathCheckRevocationRequired(pvc);
});
if (SecCertificatePathVCGetScore(builder->bestPath) > ACCEPT_PATH_SCORE && !SecPathBuilderIsOkResult(builder)) {
SecCertificatePathVCResetScore(builder->bestPath);
secwarning("In ComputeDetails, we got a reject after an accept in DidValidatePath.");
}
return completed;
}
static bool SecPathBuilderReportResult(SecPathBuilderRef builder) {
builder->info = CFDictionaryCreateMutable(kCFAllocatorDefault,
0, &kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (builder->info && SecCertificatePathVCIsEV(builder->bestPath) && SecPathBuilderIsOkResult(builder)) {
#if !TARGET_OS_WATCH
if (SecCertificatePathVCIsRevocationDone(builder->bestPath)) {
CFAbsoluteTime nextUpdate = SecCertificatePathVCGetEarliestNextUpdate(builder->bestPath);
if (nextUpdate != 0) {
#else
{
{
#endif
CFDictionarySetValue(builder->info, kSecTrustInfoExtendedValidationKey,
kCFBooleanTrue);
CFDictionarySetValue(builder->info, kSecTrustExtendedValidation,
kCFBooleanTrue);
SecCertificateRef leaf = SecPathBuilderGetCertificateAtIndex(builder, 0);
CFStringRef leafCompanyName = SecCertificateCopyCompanyName(leaf);
if (leafCompanyName) {
CFDictionarySetValue(builder->info, kSecTrustInfoCompanyNameKey,
leafCompanyName);
CFDictionarySetValue(builder->info, kSecTrustOrganizationName,
leafCompanyName);
CFRelease(leafCompanyName);
}
}
}
}
if (builder->info && SecPathBuilderIsOkResult(builder) && SecCertificatePathVCIsRevocationDone(builder->bestPath)) {
CFAbsoluteTime nextUpdate = SecCertificatePathVCGetEarliestNextUpdate(builder->bestPath);
if (nextUpdate != 0) {
CFDateRef validUntil = CFDateCreate(kCFAllocatorDefault, nextUpdate);
CFDictionarySetValue(builder->info, kSecTrustInfoRevocationValidUntilKey,
validUntil);
CFDictionarySetValue(builder->info, kSecTrustRevocationValidUntilDate,
validUntil);
CFRelease(validUntil);
CFDictionarySetValue(builder->info, kSecTrustInfoRevocationKey,
kCFBooleanTrue);
CFDictionarySetValue(builder->info, kSecTrustRevocationChecked,
kCFBooleanTrue);
} else if (SecCertificatePathVCIsEV(builder->bestPath)) {
CFDictionarySetValue(builder->info, kSecTrustInfoRevocationKey,
kCFBooleanFalse);
CFDictionarySetValue(builder->info, kSecTrustRevocationChecked,
kCFBooleanFalse);
}
}
if (builder->info && SecCertificatePathVCIsCT(builder->bestPath) && SecPathBuilderIsOkResult(builder)) {
CFDictionarySetValue(builder->info, kSecTrustInfoCertificateTransparencyKey,
kCFBooleanTrue);
}
builder->state = NULL;
return false;
}
bool SecPathBuilderStep(SecPathBuilderRef builder) {
if (builder->activations) {
secdebug("async", "activations: %lu returning true",
builder->activations);
return true;
}
secdebug("async", "activations: %lu", builder->activations);
builder->activations++;
while (builder->state && builder->state(builder));
--builder->activations;
if (builder->state) {
secdebug("async", "waiting for async reply, exiting");
return true;
}
if (builder->activations) {
return false;
}
SecPVCRef pvc = SecPathBuilderGetResultPVC(builder);
SecTrustResultType result = pvc->result;
if (builder->exceptions && pvc->result == kSecTrustResultUnspecified) {
result = kSecTrustResultProceed;
}
secinfo("trust", "completed: %@ details: %@ result: %d",
builder->bestPath, pvc->details, result);
if (builder->completed) {
SecCertificatePathRef resultPath = SecCertificatePathVCCopyCertificatePath(builder->bestPath);
builder->completed(builder->context, resultPath,
pvc->details, builder->info, result);
CFReleaseNull(resultPath);
}
SecPathBuilderDestroy(builder);
free(builder);
return false;
}
dispatch_queue_t SecPathBuilderGetQueue(SecPathBuilderRef builder) {
return (builder) ? builder->queue : NULL;
}
CFDataRef SecPathBuilderCopyClientAuditToken(SecPathBuilderRef builder) {
return (builder) ? (CFDataRef)CFRetainSafe(builder->clientAuditToken) : NULL;
}
typedef void (^SecTrustServerEvaluationCompleted)(SecTrustResultType tr, CFArrayRef details, CFDictionaryRef info, SecCertificatePathRef chain, CFErrorRef error);
static void
SecTrustServerEvaluateCompleted(const void *userData,
SecCertificatePathRef chain, CFArrayRef details, CFDictionaryRef info,
SecTrustResultType result) {
SecTrustServerEvaluationCompleted evaluated = (SecTrustServerEvaluationCompleted)userData;
evaluated(result, details, info, chain, NULL);
Block_release(evaluated);
}
void
SecTrustServerEvaluateBlock(CFDataRef clientAuditToken, CFArrayRef certificates, CFArrayRef anchors, bool anchorsOnly, bool keychainsAllowed, CFArrayRef policies, CFArrayRef responses, CFArrayRef SCTs, CFArrayRef trustedLogs, CFAbsoluteTime verifyTime, CFArrayRef accessGroups, CFArrayRef exceptions, void (^evaluated)(SecTrustResultType tr, CFArrayRef details, CFDictionaryRef info, SecCertificatePathRef chain, CFErrorRef error)) {
if (!isArray(certificates) || !(CFArrayGetCount(certificates) > 0)) {
CFErrorRef certError = CFErrorCreate(NULL, kCFErrorDomainOSStatus, errSecInvalidCertificate, NULL);
evaluated(kSecTrustResultInvalid, NULL, NULL, NULL, certError);
CFReleaseSafe(certError);
return;
}
SecTrustServerEvaluationCompleted userData = Block_copy(evaluated);
SecPathBuilderRef builder = SecPathBuilderCreate(clientAuditToken,
certificates, anchors,
anchorsOnly, keychainsAllowed, policies,
responses, SCTs, trustedLogs,
verifyTime, accessGroups, exceptions,
SecTrustServerEvaluateCompleted, userData);
dispatch_async(builder->queue, ^{ SecPathBuilderStep(builder); });
}
SecTrustResultType SecTrustServerEvaluate(CFArrayRef certificates, CFArrayRef anchors, bool anchorsOnly, bool keychainsAllowed, CFArrayRef policies, CFArrayRef responses, CFArrayRef SCTs, CFArrayRef trustedLogs, CFAbsoluteTime verifyTime, __unused CFArrayRef accessGroups, CFArrayRef exceptions, CFArrayRef *pdetails, CFDictionaryRef *pinfo, SecCertificatePathRef *pchain, CFErrorRef *perror) {
dispatch_semaphore_t done = dispatch_semaphore_create(0);
__block SecTrustResultType result = kSecTrustResultInvalid;
SecTrustServerEvaluateBlock(NULL, certificates, anchors, anchorsOnly, keychainsAllowed, policies, responses, SCTs, trustedLogs, verifyTime, accessGroups, exceptions, ^(SecTrustResultType tr, CFArrayRef details, CFDictionaryRef info, SecCertificatePathRef chain, CFErrorRef error) {
result = tr;
if (tr == kSecTrustResultInvalid) {
if (perror) {
*perror = error;
CFRetainSafe(error);
}
} else {
if (pdetails) {
*pdetails = details;
CFRetainSafe(details);
}
if (pinfo) {
*pinfo = info;
CFRetainSafe(info);
}
if (pchain) {
*pchain = chain;
CFRetainSafe(chain);
}
}
dispatch_semaphore_signal(done);
});
dispatch_semaphore_wait(done, DISPATCH_TIME_FOREVER);
dispatch_release(done);
return result;
}