X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/5dd5f9ec28f304ca377c42fd7f711d6cf12b90e1..5c19dc3ae3bd8e40a9c028b0deddd50ff337692c:/OSX/sec/securityd/SecPolicyServer.c?ds=sidebyside diff --git a/OSX/sec/securityd/SecPolicyServer.c b/OSX/sec/securityd/SecPolicyServer.c new file mode 100644 index 00000000..b9e94d95 --- /dev/null +++ b/OSX/sec/securityd/SecPolicyServer.c @@ -0,0 +1,3477 @@ +/* + * Copyright (c) 2008-2015 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +/* + * SecPolicyServer.c - Trust policies dealing with certificate revocation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "OTATrustUtilities.h" + +#define ocspdErrorLog(args...) asl_log(NULL, NULL, ASL_LEVEL_ERR, ## args) + +/* Set this to 1 to dump the ocsp responses received in DER form in /tmp. */ +#ifndef DUMP_OCSPRESPONSES +#define DUMP_OCSPRESPONSES 0 +#endif + +#if DUMP_OCSPRESPONSES + +#include +#include + +static void secdumpdata(CFDataRef data, const char *name) { + int fd = open(name, O_CREAT | O_WRONLY | O_TRUNC, 0666); + write(fd, CFDataGetBytePtr(data), CFDataGetLength(data)); + close(fd); +} + +#endif + + +/******************************************************** + ****************** SecPolicy object ******************** + ********************************************************/ + +static CFMutableDictionaryRef gSecPolicyLeafCallbacks = NULL; +static CFMutableDictionaryRef gSecPolicyPathCallbacks = NULL; + +static CFArrayRef SecPolicyAnchorDigestsForEVPolicy(const DERItem *policyOID) +{ + CFArrayRef result = NULL; + SecOTAPKIRef otapkiRef = SecOTAPKICopyCurrentOTAPKIRef(); + if (NULL == otapkiRef) + { + return result; + } + + CFDictionaryRef evToPolicyAnchorDigest = SecOTAPKICopyEVPolicyToAnchorMapping(otapkiRef); + CFRelease(otapkiRef); + + if (NULL == evToPolicyAnchorDigest) + { + return result; + } + + CFArrayRef roots = NULL; + CFStringRef oid = SecDERItemCopyOIDDecimalRepresentation(kCFAllocatorDefault, policyOID); + if (oid && evToPolicyAnchorDigest) + { + result = (CFArrayRef)CFDictionaryGetValue(evToPolicyAnchorDigest, oid); + if (roots && CFGetTypeID(result) != CFArrayGetTypeID()) + { + ocspdErrorLog("EVRoot.plist has non array value"); + result = NULL; + } + CFRelease(oid); + } + CFReleaseSafe(evToPolicyAnchorDigest); + return result; +} + + +static bool SecPolicyIsEVPolicy(const DERItem *policyOID) { + return SecPolicyAnchorDigestsForEVPolicy(policyOID); +} + +static bool SecPolicyRootCACertificateIsEV(SecCertificateRef certificate, + policy_set_t valid_policies) { + /* Ensure that this certificate is a valid anchor for one of the + certificate policy oids specified in the leaf. */ + CFDataRef digest = SecCertificateGetSHA1Digest(certificate); + policy_set_t ix; + bool good_ev_anchor = false; + for (ix = valid_policies; ix; ix = ix->oid_next) { + CFArrayRef digests = SecPolicyAnchorDigestsForEVPolicy(&ix->oid); + if (digests && CFArrayContainsValue(digests, + CFRangeMake(0, CFArrayGetCount(digests)), digest)) { + secdebug("ev", "found anchor for policy oid"); + good_ev_anchor = true; + break; + } + } + require_quiet(good_ev_anchor, notEV); + + CFAbsoluteTime october2006 = 178761600; + if (SecCertificateVersion(certificate) >= 3 + && SecCertificateNotValidBefore(certificate) >= october2006) { + const SecCEBasicConstraints *bc = SecCertificateGetBasicConstraints(certificate); + require_quiet(bc && bc->isCA == true, notEV); + SecKeyUsage ku = SecCertificateGetKeyUsage(certificate); + require_quiet((ku & (kSecKeyUsageKeyCertSign | kSecKeyUsageCRLSign)) + == (kSecKeyUsageKeyCertSign | kSecKeyUsageCRLSign), notEV); + } + + CFAbsoluteTime jan2011 = 315532800; + if (SecCertificateNotValidBefore(certificate) < jan2011) { + /* At least MD5, SHA-1 with RSA 2048 or ECC NIST P-256. */ + } else { + /* At least SHA-1, SHA-256, SHA-384 or SHA-512 with RSA 2048 or + ECC NIST P-256. */ + } + + return true; +notEV: + return false; +} + +static bool SecPolicySubordinateCACertificateCouldBeEV(SecCertificateRef certificate) { + const SecCECertificatePolicies *cp; + cp = SecCertificateGetCertificatePolicies(certificate); + require_quiet(cp && cp->numPolicies > 0, notEV); + /* SecCertificateGetCRLDistributionPoints() is a noop right now */ +#if 0 + CFArrayRef cdp = SecCertificateGetCRLDistributionPoints(certificate); + require_quiet(cdp && CFArrayGetCount(cdp) > 0, notEV); +#endif + const SecCEBasicConstraints *bc = SecCertificateGetBasicConstraints(certificate); + require_quiet(bc && bc->isCA == true, notEV); + SecKeyUsage ku = SecCertificateGetKeyUsage(certificate); + require_quiet((ku & (kSecKeyUsageKeyCertSign | kSecKeyUsageCRLSign)) + == (kSecKeyUsageKeyCertSign | kSecKeyUsageCRLSign), notEV); + CFAbsoluteTime jan2011 = 315532800; + if (SecCertificateNotValidBefore(certificate) < jan2011) { + /* At least SHA-1 with RSA 1024 or ECC NIST P-256. */ + } else { + /* At least SHA-1, SHA-256, SHA-284 or SHA-512 with RSA 2028 or + ECC NIST P-256. */ + } + + return true; +notEV: + return false; +} + +bool SecPolicySubscriberCertificateCouldBeEV(SecCertificateRef certificate) { + /* 3. Subscriber Certificate. */ + + /* (a) certificate Policies */ + const SecCECertificatePolicies *cp; + cp = SecCertificateGetCertificatePolicies(certificate); + require_quiet(cp && cp->numPolicies > 0, notEV); + /* Now find at least one policy in here that has a qualifierID of id-qt 2 + and a policyQualifier that is a URI to the CPS and an EV policy OID. */ + uint32_t ix = 0; + bool found_ev_anchor_for_leaf_policy = false; + for (ix = 0; ix < cp->numPolicies; ++ix) { + if (SecPolicyIsEVPolicy(&cp->policies[ix].policyIdentifier)) { + found_ev_anchor_for_leaf_policy = true; + } + } + require_quiet(found_ev_anchor_for_leaf_policy, notEV); + + /* SecCertificateGetCRLDistributionPoints() is a noop right now */ +#if 0 + /* (b) cRLDistributionPoint + (c) authorityInformationAccess */ + CFArrayRef cdp = SecCertificateGetCRLDistributionPoints(certificate); + if (cdp) { + require_quiet(CFArrayGetCount(cdp) > 0, notEV); + } else { + CFArrayRef or = SecCertificateGetOCSPResponders(certificate); + require_quiet(or && CFArrayGetCount(or) > 0, notEV); + //CFArrayRef ci = SecCertificateGetCAIssuers(certificate); + } +#endif + + /* (d) basicConstraints + If present, the cA field MUST be set false. */ + const SecCEBasicConstraints *bc = SecCertificateGetBasicConstraints(certificate); + if (bc) { + require_quiet(bc->isCA == false, notEV); + } + + /* (e) keyUsage. */ + SecKeyUsage ku = SecCertificateGetKeyUsage(certificate); + if (ku) { + require_quiet((ku & (kSecKeyUsageKeyCertSign | kSecKeyUsageCRLSign)) == 0, notEV); + } + +#if 0 + /* The EV Cert Spec errata specifies this, though this is a check for SSL + not specifically EV. */ + + /* (e) extKeyUsage + +Either the value id-kp-serverAuth [RFC5280] or id-kp-clientAuth [RFC5280] or both values MUST be present. Other values SHOULD NOT be present. */ + SecCertificateCopyExtendedKeyUsage(certificate); +#endif + + CFAbsoluteTime jan2011 = 315532800; + if (SecCertificateNotValidAfter(certificate) < jan2011) { + /* At least SHA-1 with RSA 1024 or ECC NIST P-256. */ + } else { + /* At least SHA-1, SHA-256, SHA-284 or SHA-512 with RSA 2028 or + ECC NIST P-256. */ + } + + return true; +notEV: + return false; +} + +/******************************************************** + **************** SecPolicy Callbacks ******************* + ********************************************************/ +static void SecPolicyCheckCriticalExtensions(SecPVCRef pvc, + CFStringRef key) { +} + +static void SecPolicyCheckIdLinkage(SecPVCRef pvc, + CFStringRef key) { + CFIndex ix, count = SecPVCGetCertificateCount(pvc); + CFDataRef parentSubjectKeyID = NULL; + for (ix = count - 1; ix >= 0; --ix) { + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, ix); + /* If the previous certificate in the chain had a SubjectKeyID, + make sure it matches the current certificates AuthorityKeyID. */ + if (parentSubjectKeyID) { + /* @@@ According to RFC 2459 neither AuthorityKeyID nor + SubjectKeyID can be critical. Currenty we don't check + for this. */ + CFDataRef authorityKeyID = SecCertificateGetAuthorityKeyID(cert); + if (authorityKeyID) { + if (!CFEqual(parentSubjectKeyID, authorityKeyID)) { + /* AuthorityKeyID doesn't match issuers SubjectKeyID. */ + if (!SecPVCSetResult(pvc, key, ix, kCFBooleanFalse)) + return; + } + } + } + + parentSubjectKeyID = SecCertificateGetSubjectKeyID(cert); + } +} + +static bool keyusage_allows(SecKeyUsage keyUsage, CFTypeRef xku) { + if (!xku || CFGetTypeID(xku) != CFNumberGetTypeID()) + return false; + + SInt32 dku; + CFNumberGetValue((CFNumberRef)xku, kCFNumberSInt32Type, &dku); + SecKeyUsage ku = (SecKeyUsage)dku; + return (keyUsage & ku) == ku; +} + +static void SecPolicyCheckKeyUsage(SecPVCRef pvc, + CFStringRef key) { + SecCertificateRef leaf = SecPVCGetCertificateAtIndex(pvc, 0); + SecKeyUsage keyUsage = SecCertificateGetKeyUsage(leaf); + bool match = false; + SecPolicyRef policy = SecPVCGetPolicy(pvc); + CFTypeRef xku = CFDictionaryGetValue(policy->_options, key); + if (isArray(xku)) { + CFIndex ix, count = CFArrayGetCount(xku); + for (ix = 0; ix < count; ++ix) { + CFTypeRef ku = CFArrayGetValueAtIndex(xku, ix); + if (keyusage_allows(keyUsage, ku)) { + match = true; + break; + } + } + } else { + match = keyusage_allows(keyUsage, xku); + } + if (!match) { + SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); + } +} + +static bool extendedkeyusage_allows(CFArrayRef extendedKeyUsage, + CFTypeRef xeku) { + if (!xeku || CFGetTypeID(xeku) != CFDataGetTypeID()) + return false; + if (extendedKeyUsage) { + CFRange all = { 0, CFArrayGetCount(extendedKeyUsage) }; + return CFArrayContainsValue(extendedKeyUsage, all, xeku); + } else { + /* Certificate has no extended key usage, only a match if the policy + contains a 0 length CFDataRef. */ + return CFDataGetLength((CFDataRef)xeku) == 0; + } +} + +/* AUDIT[securityd](done): + policy->_options is a caller provided dictionary, only its cf type has + been checked. + */ +static void SecPolicyCheckExtendedKeyUsage(SecPVCRef pvc, CFStringRef key) { + SecCertificateRef leaf = SecPVCGetCertificateAtIndex(pvc, 0); + CFArrayRef leafExtendedKeyUsage = SecCertificateCopyExtendedKeyUsage(leaf); + bool match = false; + SecPolicyRef policy = SecPVCGetPolicy(pvc); + CFTypeRef xeku = CFDictionaryGetValue(policy->_options, key); + if (isArray(xeku)) { + CFIndex ix, count = CFArrayGetCount(xeku); + for (ix = 0; ix < count; ix++) { + CFTypeRef eku = CFArrayGetValueAtIndex(xeku, ix); + if (extendedkeyusage_allows(leafExtendedKeyUsage, eku)) { + match = true; + break; + } + } + } else { + match = extendedkeyusage_allows(leafExtendedKeyUsage, xeku); + } + CFReleaseSafe(leafExtendedKeyUsage); + if (!match) { + SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); + } +} + +#if 0 +static void SecPolicyCheckBasicContraintsCommon(SecPVCRef pvc, + CFStringRef key, bool strict) { + CFIndex ix, count = SecPVCGetCertificateCount(pvc); + for (ix = 0; ix < count; ++ix) { + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, ix); + const SecCEBasicConstraints *bc = + SecCertificateGetBasicConstraints(cert); + if (bc) { + if (strict) { + if (ix == 0) { + /* Leaf certificate has basic constraints extension. */ + if (!SecPVCSetResult(pvc, key, ix, kCFBooleanFalse)) + return; + } else if (!bc->critical) { + /* Basic constraints extension is not marked critical. */ + if (!SecPVCSetResult(pvc, key, ix, kCFBooleanFalse)) + return; + } + } + + if (ix > 0 || count == 1) { + if (!bc->isCA) { + /* Non leaf certificate marked as isCA false. */ + if (!SecPVCSetResult(pvc, key, ix, kCFBooleanFalse)) + return; + } + + if (bc->pathLenConstraintPresent) { + if (bc->pathLenConstraint < (uint32_t)(ix - 1)) { +#if 0 + /* @@@ If a self signed certificate is issued by + another cert that is trusted, then we are supposed + to treat the self signed cert itself as the anchor + for path length purposes. */ + CFIndex ssix = SecCertificatePathSelfSignedIndex(path); + if (ssix >= 0 && ix >= ssix) { + /* It's ok if the pathLenConstraint isn't met for + certificates signing a self signed cert in the + chain. */ + } else +#endif + { + /* Path Length Constraint Exceeded. */ + if (!SecPVCSetResult(pvc, key, ix, + kCFBooleanFalse)) + return; + } + } + } + } + } else if (strict && ix > 0) { + /* In strict mode all CA certificates *MUST* have a critical + basic constraints extension and the leaf certificate + *MUST NOT* have a basic constraints extension. */ + /* CA certificate is missing basicConstraints extension. */ + if (!SecPVCSetResult(pvc, key, ix, kCFBooleanFalse)) + return; + } + } +} +#endif + +static void SecPolicyCheckBasicContraints(SecPVCRef pvc, + CFStringRef key) { + //SecPolicyCheckBasicContraintsCommon(pvc, key, false); +} + +static void SecPolicyCheckNonEmptySubject(SecPVCRef pvc, + CFStringRef key) { + CFIndex ix, count = SecPVCGetCertificateCount(pvc); + for (ix = 0; ix < count; ++ix) { + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, ix); + /* If the certificate has a subject, or + if it doesn't, and it's the leaf and not self signed, + and also has a critical subjectAltName extension it's valid. */ + if (!SecCertificateHasSubject(cert)) { + if (ix == 0 && count > 1) { + if (!SecCertificateHasCriticalSubjectAltName(cert)) { + /* Leaf certificate with empty subject does not have + a critical subject alt name extension. */ + if (!SecPVCSetResult(pvc, key, ix, kCFBooleanFalse)) + return; + } + } else { + /* CA certificate has empty subject. */ + if (!SecPVCSetResult(pvc, key, ix, kCFBooleanFalse)) + return; + } + } + } +} + +static void SecPolicyCheckQualifiedCertStatements(SecPVCRef pvc, + CFStringRef key) { +} + +/* Compare hostname suffix to domain name. + This function does not process wildcards, and allows hostname to match + any subdomain level of the provided domain. + + To match, the last domain length chars of hostname must equal domain, + and the character immediately preceding domain in hostname (if any) + must be a dot. This means that domain 'bar.com' will match hostname + values 'host.bar.com' or 'host.sub.bar.com', but not 'host.foobar.com'. + + Characters in each string are converted to lowercase for the comparison. + Trailing '.' characters in both names will be ignored. + + Returns true on match, else false. + */ +static bool SecDomainSuffixMatch(CFStringRef hostname, CFStringRef domain) { + CFStringInlineBuffer hbuf, dbuf; + UniChar hch, dch; + CFIndex hix, dix, + hlength = CFStringGetLength(hostname), + dlength = CFStringGetLength(domain); + CFRange hrange = { 0, hlength }, drange = { 0, dlength }; + CFStringInitInlineBuffer(hostname, &hbuf, hrange); + CFStringInitInlineBuffer(domain, &dbuf, drange); + + if((hlength == 0) || (dlength == 0)) { + /* trivial case with at least one empty name */ + return (hlength == dlength) ? true : false; + } + + /* trim off trailing dots */ + hch = CFStringGetCharacterFromInlineBuffer(&hbuf, hlength-1); + dch = CFStringGetCharacterFromInlineBuffer(&dbuf, dlength-1); + if(hch == '.') { + hrange.length = --hlength; + } + if(dch == '.') { + drange.length = --dlength; + } + + /* trim off leading dot in suffix, if present */ + dch = CFStringGetCharacterFromInlineBuffer(&dbuf, 0); + if((dlength > 0) && (dch == '.')) { + drange.location++; + drange.length = --dlength; + } + + if(hlength < dlength) { + return false; + } + + /* perform case-insensitive comparison of domain suffix */ + for (hix = (hlength-dlength), + dix = drange.location; dix < drange.length; dix++) { + hch = CFStringGetCharacterFromInlineBuffer(&hbuf, hix); + dch = CFStringGetCharacterFromInlineBuffer(&dbuf, dix); + if (towlower(hch) != towlower(dch)) { + return false; + } + } + + /* require a dot prior to domain suffix, unless hostname == domain */ + if(hlength > dlength) { + hch = CFStringGetCharacterFromInlineBuffer(&hbuf, (hlength-(dlength+1))); + if(hch != '.') { + return false; + } + } + + return true; +} + +/* Compare hostname, to a server name obtained from the server's cert + Obtained from the SubjectAltName or the CommonName entry in the Subject. + Limited wildcard checking is performed here as outlined in + + RFC 2818 Section 3.1. Server Identity + + [...] Names may contain the wildcard + character * which is considered to match any single domain name + component or component fragment. E.g., *.a.com matches foo.a.com but + not bar.foo.a.com. f*.com matches foo.com but not bar.com. + [...] + + Trailing '.' characters in the hostname will be ignored. + + Returns true on match, else false. + */ +bool SecDNSMatch(CFStringRef hostname, CFStringRef servername) { + CFStringInlineBuffer hbuf, sbuf; + CFIndex hix, six, + hlength = CFStringGetLength(hostname), + slength = CFStringGetLength(servername); + CFRange hrange = { 0, hlength }, srange = { 0, slength }; + CFStringInitInlineBuffer(hostname, &hbuf, hrange); + CFStringInitInlineBuffer(servername, &sbuf, srange); + + for (hix = six = 0; six < slength; ++six) { + UniChar hch, sch = CFStringGetCharacterFromInlineBuffer(&sbuf, six); + if (sch == '*') { + if (six + 1 >= slength) { + /* Trailing '*' in servername, match until end of hostname or + trailing '.'. */ + do { + if (hix >= hlength) { + /* If we reach the end of the hostname we have a + match. */ + return true; + } + hch = CFStringGetCharacterFromInlineBuffer(&hbuf, hix++); + } while (hch != '.'); + /* We reached the end of servername and found a '.' in + hostname. Return true if hostname has a single + trailing '.' return false if there is anything after it. */ + return hix == hlength; + } + + /* Grab the character after the '*'. */ + sch = CFStringGetCharacterFromInlineBuffer(&sbuf, ++six); + if (sch != '.') { + /* We have something of the form '*foo.com'. Or '**.com' + We don't deal with that yet, since it might require + backtracking. Also RFC 2818 doesn't seem to require it. */ + return false; + } + + /* We're looking at the '.' after the '*' in something of the + form 'foo*.com' or '*.com'. Match until next '.' in hostname. */ + do { + /* Since we're not at the end of servername yet (that case + was handled above), running out of chars in hostname + means we don't have a match. */ + if (hix >= hlength) + return false; + hch = CFStringGetCharacterFromInlineBuffer(&hbuf, hix++); + } while (hch != '.'); + } else { + /* We're looking at a non wildcard character in the servername. + If we reached the end of hostname it's not a match. */ + if (hix >= hlength) + return false; + + /* Otherwise make sure the hostname matches the character in the + servername, case insensitively. */ + hch = CFStringGetCharacterFromInlineBuffer(&hbuf, hix++); + if (towlower(hch) != towlower(sch)) + return false; + } + } + + if (hix < hlength) { + /* We reached the end of servername but we have one or more characters + left to compare against in the hostname. */ + if (hix + 1 == hlength && + CFStringGetCharacterFromInlineBuffer(&hbuf, hix) == '.') { + /* Hostname has a single trailing '.', we're ok with that. */ + return true; + } + /* Anything else is not a match. */ + return false; + } + + return true; +} + +#define kSecPolicySHA1Size 20 +static const UInt8 kAppleCorpCASHA1[kSecPolicySHA1Size] = { + 0xA1, 0x71, 0xDC, 0xDE, 0xE0, 0x8B, 0x1B, 0xAE, 0x30, 0xA1, + 0xAE, 0x6C, 0xC6, 0xD4, 0x03, 0x3B, 0xFD, 0xEF, 0x91, 0xCE +}; + +/* Check whether hostname is in a particular set of allowed domains. + Returns true if OK, false if not allowed. + */ +static bool SecPolicyCheckDomain(SecPVCRef pvc, CFStringRef hostname) +{ + CFIndex count = SecPVCGetCertificateCount(pvc); + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, count - 1); + CFDataRef anchorSHA1 = SecCertificateGetSHA1Digest(cert); + + /* is this chain anchored by kAppleCorpCASHA1? */ + CFDataRef corpSHA1 = CFDataCreateWithBytesNoCopy(NULL, + kAppleCorpCASHA1, kSecPolicySHA1Size, kCFAllocatorNull); + bool isCorpSHA1 = (corpSHA1 && CFEqual(anchorSHA1, corpSHA1)); + CFReleaseSafe(corpSHA1); + if (isCorpSHA1) { + /* limit hostname to specified domains */ + const CFStringRef dnlist[] = { + CFSTR("apple.com"), + CFSTR("icloud.com"), + }; + unsigned int idx, dncount=2; + for (idx = 0; idx < dncount; idx++) { + if (SecDomainSuffixMatch(hostname, dnlist[idx])) { + return true; + } + } + return false; + } + /* %%% other CA pinning checks TBA */ + + return true; +} + +/* AUDIT[securityd](done): + policy->_options is a caller provided dictionary, only its cf type has + been checked. + */ +static void SecPolicyCheckSSLHostname(SecPVCRef pvc, + CFStringRef key) { + /* @@@ Consider what to do if the caller passes in no hostname. Should + we then still fail if the leaf has no dnsNames or IPAddresses at all? */ + SecPolicyRef policy = SecPVCGetPolicy(pvc); + CFStringRef hostName = (CFStringRef) + CFDictionaryGetValue(policy->_options, key); + if (!isString(hostName)) { + /* @@@ We can't return an error here and making the evaluation fail + won't help much either. */ + return; + } + + SecCertificateRef leaf = SecPVCGetCertificateAtIndex(pvc, 0); + bool dnsMatch = false; + CFArrayRef dnsNames = SecCertificateCopyDNSNames(leaf); + if (dnsNames) { + CFIndex ix, count = CFArrayGetCount(dnsNames); + for (ix = 0; ix < count; ++ix) { + CFStringRef dns = (CFStringRef)CFArrayGetValueAtIndex(dnsNames, ix); + if (SecDNSMatch(hostName, dns)) { + dnsMatch = true; + break; + } + } + CFRelease(dnsNames); + } + + if (!dnsMatch) { + /* Maybe hostname is an IPv4 or IPv6 address, let's compare against + the values returned by SecCertificateCopyIPAddresses() instead. */ + CFArrayRef ipAddresses = SecCertificateCopyIPAddresses(leaf); + if (ipAddresses) { + CFIndex ix, count = CFArrayGetCount(ipAddresses); + for (ix = 0; ix < count; ++ix) { + CFStringRef ipAddress = (CFStringRef)CFArrayGetValueAtIndex(ipAddresses, ix); + if (!CFStringCompare(hostName, ipAddress, kCFCompareCaseInsensitive)) { + dnsMatch = true; + break; + } + } + CFRelease(ipAddresses); + } + } + + if (!dnsMatch) { + /* Hostname mismatch or no hostnames found in certificate. */ + SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); + } + else if (!SecPolicyCheckDomain(pvc, hostName)) { + /* Hostname match, but domain not allowed for this CA */ + SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); + } + + if ((dnsMatch || pvc->details) + && SecPolicySubscriberCertificateCouldBeEV(leaf)) { + secdebug("policy", "enabling optionally_ev"); + pvc->optionally_ev = true; + /* optionally_ev => check_revocation, so we don't enable revocation + checking here, since we don't want it on for non EV ssl certs. */ +#if 0 + /* Check revocation status if the certificate asks for it (and we + support it) currently we only support ocsp. */ + CFArrayRef ocspResponders = SecCertificateGetOCSPResponders(leaf); + if (ocspResponders) { + SecPVCSetCheckRevocation(pvc); + } +#endif + } +} + +/* AUDIT[securityd](done): + policy->_options is a caller provided dictionary, only its cf type has + been checked. + */ +static void SecPolicyCheckEmail(SecPVCRef pvc, CFStringRef key) { + SecPolicyRef policy = SecPVCGetPolicy(pvc); + CFStringRef email = (CFStringRef)CFDictionaryGetValue(policy->_options, key); + bool match = false; + if (!isString(email)) { + /* We can't return an error here and making the evaluation fail + won't help much either. */ + return; + } + + SecCertificateRef leaf = SecPVCGetCertificateAtIndex(pvc, 0); + CFArrayRef addrs = SecCertificateCopyRFC822Names(leaf); + if (addrs) { + CFIndex ix, count = CFArrayGetCount(addrs); + for (ix = 0; ix < count; ++ix) { + CFStringRef addr = (CFStringRef)CFArrayGetValueAtIndex(addrs, ix); + if (!CFStringCompare(email, addr, kCFCompareCaseInsensitive)) { + match = true; + break; + } + } + CFRelease(addrs); + } + + if (!match) { + /* Hostname mismatch or no hostnames found in certificate. */ + SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); + } +} + +static void SecPolicyCheckValidIntermediates(SecPVCRef pvc, + CFStringRef key) { + CFIndex ix, count = SecPVCGetCertificateCount(pvc); + CFAbsoluteTime verifyTime = SecPVCGetVerifyTime(pvc); + for (ix = 1; ix < count - 1; ++ix) { + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, ix); + if (!SecCertificateIsValid(cert, verifyTime)) { + /* Intermediate certificate has expired. */ + if (!SecPVCSetResult(pvc, key, ix, kCFBooleanFalse)) + return; + } + } +} + +static void SecPolicyCheckValidLeaf(SecPVCRef pvc, + CFStringRef key) { + CFAbsoluteTime verifyTime = SecPVCGetVerifyTime(pvc); + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, 0); + if (!SecCertificateIsValid(cert, verifyTime)) { + /* Leaf certificate has expired. */ + if (!SecPVCSetResult(pvc, key, 0, kCFBooleanFalse)) + return; + } +} + +static void SecPolicyCheckValidRoot(SecPVCRef pvc, + CFStringRef key) { + CFIndex ix, count = SecPVCGetCertificateCount(pvc); + CFAbsoluteTime verifyTime = SecPVCGetVerifyTime(pvc); + ix = count - 1; + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, ix); + if (!SecCertificateIsValid(cert, verifyTime)) { + /* Root certificate has expired. */ + if (!SecPVCSetResult(pvc, key, ix, kCFBooleanFalse)) + return; + } +} + +/* AUDIT[securityd](done): + policy->_options is a caller provided dictionary, only its cf type has + been checked. + */ +static void SecPolicyCheckIssuerCommonName(SecPVCRef pvc, + CFStringRef key) { + CFIndex count = SecPVCGetCertificateCount(pvc); + if (count < 2) { + /* Can't check intermediates common name if there is no intermediate. */ + SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); + return; + } + + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, 1); + SecPolicyRef policy = SecPVCGetPolicy(pvc); + CFStringRef commonName = + (CFStringRef)CFDictionaryGetValue(policy->_options, key); + if (!isString(commonName)) { + /* @@@ We can't return an error here and making the evaluation fail + won't help much either. */ + return; + } + CFArrayRef commonNames = SecCertificateCopyCommonNames(cert); + if (!commonNames || CFArrayGetCount(commonNames) != 1 || + !CFEqual(commonName, CFArrayGetValueAtIndex(commonNames, 0))) { + /* Common Name mismatch. */ + SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); + } + CFReleaseSafe(commonNames); +} + +/* AUDIT[securityd](done): + policy->_options is a caller provided dictionary, only its cf type has + been checked. + */ +static void SecPolicyCheckSubjectCommonName(SecPVCRef pvc, + CFStringRef key) { + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, 0); + SecPolicyRef policy = SecPVCGetPolicy(pvc); + CFStringRef common_name = (CFStringRef)CFDictionaryGetValue(policy->_options, + key); + if (!isString(common_name)) { + /* @@@ We can't return an error here and making the evaluation fail + won't help much either. */ + return; + } + CFArrayRef commonNames = SecCertificateCopyCommonNames(cert); + if (!commonNames || CFArrayGetCount(commonNames) != 1 || + !CFEqual(common_name, CFArrayGetValueAtIndex(commonNames, 0))) { + /* Common Name mismatch. */ + SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); + } + CFReleaseSafe(commonNames); +} + +/* AUDIT[securityd](done): + policy->_options is a caller provided dictionary, only its cf type has + been checked. + */ +static void SecPolicyCheckSubjectCommonNamePrefix(SecPVCRef pvc, + CFStringRef key) { + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, 0); + SecPolicyRef policy = SecPVCGetPolicy(pvc); + CFStringRef prefix = (CFStringRef)CFDictionaryGetValue(policy->_options, + key); + if (!isString(prefix)) { + /* @@@ We can't return an error here and making the evaluation fail + won't help much either. */ + return; + } + CFArrayRef commonNames = SecCertificateCopyCommonNames(cert); + if (!commonNames || CFArrayGetCount(commonNames) != 1 || + !CFStringHasPrefix(CFArrayGetValueAtIndex(commonNames, 0), prefix)) { + /* Common Name prefix mismatch. */ + SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); + } + CFReleaseSafe(commonNames); +} + +/* AUDIT[securityd](done): + policy->_options is a caller provided dictionary, only its cf type has + been checked. + */ +static void SecPolicyCheckSubjectCommonNameTEST(SecPVCRef pvc, + CFStringRef key) { + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, 0); + SecPolicyRef policy = SecPVCGetPolicy(pvc); + CFStringRef common_name = (CFStringRef)CFDictionaryGetValue(policy->_options, + key); + if (!isString(common_name)) { + /* @@@ We can't return an error here and making the evaluation fail + won't help much either. */ + return; + } + CFArrayRef commonNames = SecCertificateCopyCommonNames(cert); + if (!commonNames || CFArrayGetCount(commonNames) != 1) { + CFStringRef cert_common_name = CFArrayGetValueAtIndex(commonNames, 0); + CFStringRef test_common_name = common_name ? + CFStringCreateWithFormat(kCFAllocatorDefault, + NULL, CFSTR("TEST %@ TEST"), common_name) : + NULL; + if (!CFEqual(common_name, cert_common_name) && + (!test_common_name || !CFEqual(test_common_name, cert_common_name))) + /* Common Name mismatch. */ + SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); + CFReleaseSafe(test_common_name); + } + CFReleaseSafe(commonNames); +} + +/* AUDIT[securityd](done): + policy->_options is a caller provided dictionary, only its cf type has + been checked. + */ +static void SecPolicyCheckNotValidBefore(SecPVCRef pvc, + CFStringRef key) { + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, 0); + SecPolicyRef policy = SecPVCGetPolicy(pvc); + CFDateRef date = (CFDateRef)CFDictionaryGetValue(policy->_options, key); + if (!isDate(date)) { + /* @@@ We can't return an error here and making the evaluation fail + won't help much either. */ + return; + } + CFAbsoluteTime at = CFDateGetAbsoluteTime(date); + if (SecCertificateNotValidBefore(cert) <= at) { + /* Leaf certificate has not valid before that is too old. */ + if (!SecPVCSetResult(pvc, key, 0, kCFBooleanFalse)) + return; + } +} + +/* AUDIT[securityd](done): + policy->_options is a caller provided dictionary, only its cf type has + been checked. + */ +static void SecPolicyCheckChainLength(SecPVCRef pvc, + CFStringRef key) { + CFIndex count = SecPVCGetCertificateCount(pvc); + SecPolicyRef policy = SecPVCGetPolicy(pvc); + CFNumberRef chainLength = + (CFNumberRef)CFDictionaryGetValue(policy->_options, key); + CFIndex value; + if (!chainLength || CFGetTypeID(chainLength) != CFNumberGetTypeID() || + !CFNumberGetValue(chainLength, kCFNumberCFIndexType, &value)) { + /* @@@ We can't return an error here and making the evaluation fail + won't help much either. */ + return; + } + if (value != count) { + /* Chain length doesn't match policy requirement. */ + if (!SecPVCSetResult(pvc, key, 0, kCFBooleanFalse)) + return; + } +} + +/* AUDIT[securityd](done): + policy->_options is a caller provided dictionary, only its cf type has + been checked. + */ +static void SecPolicyCheckAnchorSHA1(SecPVCRef pvc, + CFStringRef key) { + CFIndex count = SecPVCGetCertificateCount(pvc); + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, count - 1); + SecPolicyRef policy = SecPVCGetPolicy(pvc); + CFTypeRef value = CFDictionaryGetValue(policy->_options, key); + CFDataRef anchorSHA1 = SecCertificateGetSHA1Digest(cert); + + bool foundMatch = false; + + if (isData(value)) + foundMatch = CFEqual(anchorSHA1, value); + else if (isArray(value)) + foundMatch = CFArrayContainsValue((CFArrayRef) value, CFRangeMake(0, CFArrayGetCount((CFArrayRef) value)), anchorSHA1); + else { + /* @@@ We only support Data and Array but we can't return an error here so. + we let the evaluation fail (not much help) and assert in debug. */ + assert(false); + } + + if (!foundMatch) + if (!SecPVCSetResult(pvc, kSecPolicyCheckAnchorSHA1, 0, kCFBooleanFalse)) + return; + + return; +} + +/* + Check the SHA256 of SPKI of the first intermediate CA certificate in the path + policy->_options is a caller provided dictionary, only its cf type has + been checked. + */ +static void SecPolicyCheckIntermediateSPKISHA256(SecPVCRef pvc, + CFStringRef key) { + SecPolicyRef policy = SecPVCGetPolicy(pvc); + CFTypeRef value = CFDictionaryGetValue(policy->_options, key); + SecCertificateRef cert = NULL; + CFDataRef digest = NULL; + bool foundMatch = false; + + if (SecPVCGetCertificateCount(pvc) < 2) { + SecPVCSetResult(pvc, kSecPolicyCheckIntermediateSPKISHA256, 0, kCFBooleanFalse); + return; + } + + cert = SecPVCGetCertificateAtIndex(pvc, 1); + digest = SecCertificateCopySubjectPublicKeyInfoSHA256Digest(cert); + + if (isData(value)) + foundMatch = CFEqual(digest, value); + else if (isArray(value)) + foundMatch = CFArrayContainsValue((CFArrayRef) value, CFRangeMake(0, CFArrayGetCount((CFArrayRef) value)), digest); + else { + /* @@@ We only support Data and Array but we can't return an error here so. + we let the evaluation fail (not much help) and assert in debug. */ + assert(false); + } + + CFReleaseNull(digest); + + if (!foundMatch) { + SecPVCSetResult(pvc, kSecPolicyCheckIntermediateSPKISHA256, 0, kCFBooleanFalse); + } +} + +/* + policy->_options is a caller provided dictionary, only its cf type has + been checked. + */ +static void SecPolicyCheckAnchorApple(SecPVCRef pvc, + CFStringRef key) { + CFIndex count = SecPVCGetCertificateCount(pvc); + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, count - 1); + SecPolicyRef policy = SecPVCGetPolicy(pvc); + CFTypeRef value = CFDictionaryGetValue(policy->_options, key); + SecAppleTrustAnchorFlags flags = 0; + + if (isDictionary(value)) { + if (CFDictionaryGetValue(value, kSecPolicyAppleAnchorIncludeTestRoots)) + flags |= kSecAppleTrustAnchorFlagsIncludeTestAnchors; + } + + bool foundMatch = SecIsAppleTrustAnchor(cert, flags); + + if (!foundMatch) + if (!SecPVCSetResult(pvc, kSecPolicyCheckAnchorApple, 0, kCFBooleanFalse)) + return; + + return; +} + + +/* AUDIT[securityd](done): + policy->_options is a caller provided dictionary, only its cf type has + been checked. + */ +static void SecPolicyCheckSubjectOrganization(SecPVCRef pvc, + CFStringRef key) { + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, 0); + SecPolicyRef policy = SecPVCGetPolicy(pvc); + CFStringRef org = (CFStringRef)CFDictionaryGetValue(policy->_options, + key); + if (!isString(org)) { + /* @@@ We can't return an error here and making the evaluation fail + won't help much either. */ + return; + } + CFArrayRef organization = SecCertificateCopyOrganization(cert); + if (!organization || CFArrayGetCount(organization) != 1 || + !CFEqual(org, CFArrayGetValueAtIndex(organization, 0))) { + /* Leaf Subject Organization mismatch. */ + SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); + } + CFReleaseSafe(organization); +} + +static void SecPolicyCheckSubjectOrganizationalUnit(SecPVCRef pvc, + CFStringRef key) { + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, 0); + SecPolicyRef policy = SecPVCGetPolicy(pvc); + CFStringRef orgUnit = (CFStringRef)CFDictionaryGetValue(policy->_options, + key); + if (!isString(orgUnit)) { + /* @@@ We can't return an error here and making the evaluation fail + won't help much either. */ + return; + } + CFArrayRef organizationalUnit = SecCertificateCopyOrganizationalUnit(cert); + if (!organizationalUnit || CFArrayGetCount(organizationalUnit) != 1 || + !CFEqual(orgUnit, CFArrayGetValueAtIndex(organizationalUnit, 0))) { + /* Leaf Subject Organizational Unit mismatch. */ + SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); + } + CFReleaseSafe(organizationalUnit); +} + +/* AUDIT[securityd](done): + policy->_options is a caller provided dictionary, only its cf type has + been checked. + */ +static void SecPolicyCheckEAPTrustedServerNames(SecPVCRef pvc, + CFStringRef key) { + SecPolicyRef policy = SecPVCGetPolicy(pvc); + CFArrayRef trustedServerNames = (CFArrayRef) + CFDictionaryGetValue(policy->_options, key); + /* No names specified means we accept any name. */ + if (!trustedServerNames) + return; + if (!isArray(trustedServerNames)) { + /* @@@ We can't return an error here and making the evaluation fail + won't help much either. */ + return; + } + + CFIndex tsnCount = CFArrayGetCount(trustedServerNames); + SecCertificateRef leaf = SecPVCGetCertificateAtIndex(pvc, 0); + bool dnsMatch = false; + CFArrayRef dnsNames = SecCertificateCopyDNSNames(leaf); + if (dnsNames) { + CFIndex ix, count = CFArrayGetCount(dnsNames); + // @@@ This is O(N^2) unfortunately we can't do better easily unless + // we don't do wildcard matching. */ + for (ix = 0; !dnsMatch && ix < count; ++ix) { + CFStringRef dns = (CFStringRef)CFArrayGetValueAtIndex(dnsNames, ix); + CFIndex tix; + for (tix = 0; tix < tsnCount; ++tix) { + CFStringRef serverName = + (CFStringRef)CFArrayGetValueAtIndex(trustedServerNames, tix); + if (!isString(serverName)) { + /* @@@ We can't return an error here and making the + evaluation fail won't help much either. */ + CFReleaseSafe(dnsNames); + return; + } + /* we purposefully reverse the arguments here such that dns names + from the cert are matched against a server name list, where + the server names list can contain wildcards and the dns name + cannot. References: http://support.microsoft.com/kb/941123 + It's easy to find occurrences where people tried to use + wildcard certificates and were told that those don't work + in this context. */ + if (SecDNSMatch(dns, serverName)) { + dnsMatch = true; + break; + } + } + } + CFRelease(dnsNames); + } + + if (!dnsMatch) { + /* Hostname mismatch or no hostnames found in certificate. */ + SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); + } +} + +static const unsigned char UTN_USERFirst_Hardware_Serial[][16] = { +{ 0xd8, 0xf3, 0x5f, 0x4e, 0xb7, 0x87, 0x2b, 0x2d, 0xab, 0x06, 0x92, 0xe3, 0x15, 0x38, 0x2f, 0xb0 }, +{ 0x92, 0x39, 0xd5, 0x34, 0x8f, 0x40, 0xd1, 0x69, 0x5a, 0x74, 0x54, 0x70, 0xe1, 0xf2, 0x3f, 0x43 }, +{ 0xb0, 0xb7, 0x13, 0x3e, 0xd0, 0x96, 0xf9, 0xb5, 0x6f, 0xae, 0x91, 0xc8, 0x74, 0xbd, 0x3a, 0xc0 }, +{ 0xe9, 0x02, 0x8b, 0x95, 0x78, 0xe4, 0x15, 0xdc, 0x1a, 0x71, 0x0a, 0x2b, 0x88, 0x15, 0x44, 0x47 }, +{ 0x39, 0x2a, 0x43, 0x4f, 0x0e, 0x07, 0xdf, 0x1f, 0x8a, 0xa3, 0x05, 0xde, 0x34, 0xe0, 0xc2, 0x29 }, +{ 0x3e, 0x75, 0xce, 0xd4, 0x6b, 0x69, 0x30, 0x21, 0x21, 0x88, 0x30, 0xae, 0x86, 0xa8, 0x2a, 0x71 }, +{ 0xd7, 0x55, 0x8f, 0xda, 0xf5, 0xf1, 0x10, 0x5b, 0xb2, 0x13, 0x28, 0x2b, 0x70, 0x77, 0x29, 0xa3 }, +{ 0x04, 0x7e, 0xcb, 0xe9, 0xfc, 0xa5, 0x5f, 0x7b, 0xd0, 0x9e, 0xae, 0x36, 0xe1, 0x0c, 0xae, 0x1e }, +{ 0xf5, 0xc8, 0x6a, 0xf3, 0x61, 0x62, 0xf1, 0x3a, 0x64, 0xf5, 0x4f, 0x6d, 0xc9, 0x58, 0x7c, 0x06 } }; + +static const unsigned char UTN_USERFirst_Hardware_Normalized_Issuer[] = { + 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, + 0x53, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x02, + 0x55, 0x54, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, + 0x0e, 0x53, 0x41, 0x4c, 0x54, 0x20, 0x4c, 0x41, 0x4b, 0x45, 0x20, 0x43, + 0x49, 0x54, 0x59, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, 0x0a, + 0x13, 0x15, 0x54, 0x48, 0x45, 0x20, 0x55, 0x53, 0x45, 0x52, 0x54, 0x52, + 0x55, 0x53, 0x54, 0x20, 0x4e, 0x45, 0x54, 0x57, 0x4f, 0x52, 0x4b, 0x31, + 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x18, 0x48, 0x54, + 0x54, 0x50, 0x3a, 0x2f, 0x2f, 0x57, 0x57, 0x57, 0x2e, 0x55, 0x53, 0x45, + 0x52, 0x54, 0x52, 0x55, 0x53, 0x54, 0x2e, 0x43, 0x4f, 0x4d, 0x31, 0x1f, + 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x16, 0x55, 0x54, 0x4e, + 0x2d, 0x55, 0x53, 0x45, 0x52, 0x46, 0x49, 0x52, 0x53, 0x54, 0x2d, 0x48, + 0x41, 0x52, 0x44, 0x57, 0x41, 0x52, 0x45 +}; +static const unsigned int UTN_USERFirst_Hardware_Normalized_Issuer_len = 151; + + +static void SecPolicyCheckBlackListedLeaf(SecPVCRef pvc, + CFStringRef key) { + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, 0); + CFDataRef issuer = cert ? SecCertificateGetNormalizedIssuerContent(cert) : NULL; + + if (issuer && (CFDataGetLength(issuer) == (CFIndex)UTN_USERFirst_Hardware_Normalized_Issuer_len) && + (0 == memcmp(UTN_USERFirst_Hardware_Normalized_Issuer, CFDataGetBytePtr(issuer), + UTN_USERFirst_Hardware_Normalized_Issuer_len))) + { + #if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) + CFDataRef serial = SecCertificateCopySerialNumber(cert, NULL); + #else + CFDataRef serial = SecCertificateCopySerialNumber(cert); + #endif + + if (serial) { + CFIndex serial_length = CFDataGetLength(serial); + const uint8_t *serial_ptr = CFDataGetBytePtr(serial); + + while ((serial_length > 0) && (*serial_ptr == 0)) { + serial_ptr++; + serial_length--; + } + + if (serial_length == (CFIndex)sizeof(*UTN_USERFirst_Hardware_Serial)) { + unsigned int i; + for (i = 0; i < array_size(UTN_USERFirst_Hardware_Serial); i++) + { + if (0 == memcmp(UTN_USERFirst_Hardware_Serial[i], + serial_ptr, sizeof(*UTN_USERFirst_Hardware_Serial))) + { + SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); + CFReleaseSafe(serial); + return; + } + } + } + CFRelease(serial); + } + } + + SecOTAPKIRef otapkiRef = SecOTAPKICopyCurrentOTAPKIRef(); + if (NULL != otapkiRef) + { + CFSetRef blackListedKeys = SecOTAPKICopyBlackListSet(otapkiRef); + CFRelease(otapkiRef); + if (NULL != blackListedKeys) + { + /* Check for blacklisted intermediates keys. */ + CFDataRef dgst = SecCertificateCopyPublicKeySHA1Digest(cert); + if (dgst) + { + /* Check dgst against blacklist. */ + if (CFSetContainsValue(blackListedKeys, dgst)) + { + SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); + } + CFRelease(dgst); + } + CFRelease(blackListedKeys); + } + } +} + +static void SecPolicyCheckGrayListedLeaf(SecPVCRef pvc, CFStringRef key) +{ + SecOTAPKIRef otapkiRef = SecOTAPKICopyCurrentOTAPKIRef(); + if (NULL != otapkiRef) + { + CFSetRef grayListedKeys = SecOTAPKICopyGrayList(otapkiRef); + CFRelease(otapkiRef); + if (NULL != grayListedKeys) + { + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, 0); + + CFDataRef dgst = SecCertificateCopyPublicKeySHA1Digest(cert); + if (dgst) + { + /* Check dgst against gray. */ + if (CFSetContainsValue(grayListedKeys, dgst)) + { + SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); + } + CFRelease(dgst); + } + CFRelease(grayListedKeys); + } + } + } + +static void SecPolicyCheckLeafMarkerOid(SecPVCRef pvc, CFStringRef key) +{ + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, 0); + SecPolicyRef policy = SecPVCGetPolicy(pvc); + CFTypeRef value = CFDictionaryGetValue(policy->_options, key); + + if (value && SecCertificateHasMarkerExtension(cert, value)) + return; + + SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); +} + +static void SecPolicyCheckIntermediateMarkerOid(SecPVCRef pvc, CFStringRef key) +{ + CFIndex ix, count = SecPVCGetCertificateCount(pvc); + SecPolicyRef policy = SecPVCGetPolicy(pvc); + CFTypeRef value = CFDictionaryGetValue(policy->_options, key); + + for (ix = 1; ix < count - 1; ix++) { + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, ix); + if (SecCertificateHasMarkerExtension(cert, value)) + return; + } + SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); +} + +/* Returns true if path is on the allow list, false otherwise */ +static bool SecPVCCheckCertificateAllowList(SecPVCRef pvc) +{ + bool result = false; + CFIndex ix = 0, count = SecPVCGetCertificateCount(pvc); + CFStringRef authKey = NULL; + SecOTAPKIRef otapkiRef = NULL; + + //get authKeyID from the last chain in the cert + if (count < 1) { + return result; + } + SecCertificateRef lastCert = SecPVCGetCertificateAtIndex(pvc, count - 1); + CFDataRef authKeyID = SecCertificateGetAuthorityKeyID(lastCert); + if (NULL == authKeyID) { + return result; + } + authKey = CFDataCopyHexString(authKeyID); + + //if allowList && key is in allowList, this would have chained up to a now-removed anchor + otapkiRef = SecOTAPKICopyCurrentOTAPKIRef(); + if (NULL == otapkiRef) { + goto errout; + } + CFDictionaryRef allowList = SecOTAPKICopyAllowList(otapkiRef); + if (NULL == allowList) { + goto errout; + } + + CFArrayRef allowedCerts = CFDictionaryGetValue(allowList, authKey); + if (!allowedCerts || !CFArrayGetCount(allowedCerts)) { + goto errout; + } + + //search sorted array for the SHA256 hash of a cert in the chain + CFRange range = CFRangeMake(0, CFArrayGetCount(allowedCerts)); + for (ix = 0; ix < count; ix++) { + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, ix); + if (!cert) { + goto errout; + } + + CFDataRef certHash = SecCertificateCopySHA256Digest(cert); + if (!certHash) { + goto errout; + } + + CFIndex position = CFArrayBSearchValues(allowedCerts, range, certHash, + (CFComparatorFunction)CFDataCompare, NULL); + if (position < CFArrayGetCount(allowedCerts)) { + CFDataRef possibleMatch = CFArrayGetValueAtIndex(allowedCerts, position); + if (!CFDataCompare(certHash, possibleMatch)) { + //this cert is in the allowlist + result = true; + } + } + + CFRelease(certHash); + } + +errout: + CFRelease(authKey); + CFReleaseNull(otapkiRef); + CFReleaseNull(allowList); + return result; +} + + +/**************************************************************************** + *********************** New rfc5280 Chain Validation *********************** + ****************************************************************************/ + +#if 0 +typedef struct cert_path *cert_path_t; +struct cert_path { + int length; +}; + +typedef struct x500_name *x500_name_t; +struct x500_name { +}; + +typedef struct algorithm_id *algorithm_id_t; +struct algorithm_id { + oid_t algorithm_oid; + der_t parameters; +}; + +typedef struct trust_anchor *trust_anchor_t; +struct trust_anchor { + x500_name_t issuer_name; + algorithm_id_t public_key_algorithm; /* includes optional params */ + SecKeyRef public_key; +}; + +typedef struct certificate_policy *certificate_policy_t; +struct certificate_policy { + policy_qualifier_t qualifiers; + oid_t oid; + SLIST_ENTRY(certificate_policy) policies; +}; + +typedef struct policy_mapping *policy_mapping_t; +struct policy_mapping { + SLIST_ENTRY(policy_mapping) mappings; + oid_t issuer_domain_policy; + oid_t subject_domain_policy; +}; + +typedef struct root_name *root_name_t; +struct root_name { +}; +#endif + +struct policy_tree_add_ctx { + oid_t p_oid; + policy_qualifier_t p_q; +}; + +/* For each node of depth i-1 in the valid_policy_tree where P-OID is in the expected_policy_set, create a child node as follows: set the valid_policy to P-OID, set the qualifier_set to P-Q, and set the expected_policy_set to {P-OID}. */ +static bool policy_tree_add_if_match(policy_tree_t node, void *ctx) { + struct policy_tree_add_ctx *info = (struct policy_tree_add_ctx *)ctx; + policy_set_t policy_set; + for (policy_set = node->expected_policy_set; + policy_set; + policy_set = policy_set->oid_next) { + if (oid_equal(policy_set->oid, info->p_oid)) { + policy_tree_add_child(node, &info->p_oid, info->p_q); + return true; + } + } + return false; +} + +/* If the valid_policy_tree includes a node of depth i-1 with the valid_policy anyPolicy, generate a child node with the following values: set the valid_policy to P-OID, set the qualifier_set to P-Q, and set the expected_policy_set to {P-OID}. */ +static bool policy_tree_add_if_any(policy_tree_t node, void *ctx) { + struct policy_tree_add_ctx *info = (struct policy_tree_add_ctx *)ctx; + if (oid_equal(node->valid_policy, oidAnyPolicy)) { + policy_tree_add_child(node, &info->p_oid, info->p_q); + return true; + } + return false; +} + +/* Return true iff node has a child with a valid_policy equal to oid. */ +static bool policy_tree_has_child_with_oid(policy_tree_t node, + const oid_t *oid) { + policy_tree_t child; + for (child = node->children; child; child = child->siblings) { + if (oid_equal(child->valid_policy, (*oid))) { + return true; + } + } + return false; +} + +/* For each node in the valid_policy_tree of depth i-1, for each value in the expected_policy_set (including anyPolicy) that does not appear in a child node, create a child node with the following values: set the valid_policy to the value from the expected_policy_set in the parent node, set the qualifier_set to AP-Q, and set the expected_policy_set to the value in the valid_policy from this node. */ +static bool policy_tree_add_expected(policy_tree_t node, void *ctx) { + policy_qualifier_t p_q = (policy_qualifier_t)ctx; + policy_set_t policy_set; + bool added_node = false; + for (policy_set = node->expected_policy_set; + policy_set; + policy_set = policy_set->oid_next) { + if (!policy_tree_has_child_with_oid(node, &policy_set->oid)) { + policy_tree_add_child(node, &policy_set->oid, p_q); + added_node = true; + } + } + return added_node; +} + +#if 0 +/* For each node where ID-P is the valid_policy, set expected_policy_set to the set of subjectDomainPolicy values that are specified as equivalent to ID-P by the policy mappings extension. */ +static bool policy_tree_map(policy_tree_t node, void *ctx) { + /* Can't map oidAnyPolicy. */ + if (oid_equal(node->valid_policy, oidAnyPolicy)) + return false; + + const SecCEPolicyMappings *pm = (const SecCEPolicyMappings *)ctx; + uint32_t mapping_ix, mapping_count = pm->numMappings; + policy_set_t policy_set = NULL; + /* First count how many mappings match this nodes valid_policy. */ + for (mapping_ix = 0; mapping_ix < mapping_count; ++mapping_ix) { + const SecCEPolicyMapping *mapping = &pm->mappings[mapping_ix]; + if (oid_equal(node->valid_policy, mapping->issuerDomainPolicy)) { + policy_set_t p_node = (policy_set_t)malloc(sizeof(*policy_set)); + p_node->oid = mapping->subjectDomainPolicy; + p_node->oid_next = policy_set ? policy_set : NULL; + policy_set = p_node; + } + } + if (policy_set) { + policy_tree_set_expected_policy(node, policy_set); + return true; + } + return false; +} +#endif + +#define POLICY_MAPPING 0 +#define POLICY_SUBTREES 1 + +/* rfc5280 basic cert processing. */ +static void SecPolicyCheckBasicCertificateProcessing(SecPVCRef pvc, + CFStringRef key) { + /* Inputs */ + //cert_path_t path; + CFIndex count = SecPVCGetCertificateCount(pvc); + /* 64 bits cast: worst case here is we truncate the number of cert, and the validation may fail */ + assert((unsigned long)count<=UINT32_MAX); /* Debug check. Correct as long as CFIndex is long */ + uint32_t n = (uint32_t)count; + bool is_anchored = SecPVCIsAnchored(pvc); + if (is_anchored) { + /* If the anchor is trusted we don't procces the last cert in the + chain (root). */ + n--; + } else { + /* trust may be restored for a path with an untrusted root that matches the allow list */ + if (!SecPVCCheckCertificateAllowList(pvc)) { + /* Add a detail for the root not being trusted. */ + if (SecPVCSetResultForced(pvc, kSecPolicyCheckAnchorTrusted, + n - 1, kCFBooleanFalse, true)) + return; + } + } + + CFAbsoluteTime verify_time = SecPVCGetVerifyTime(pvc); + //policy_set_t user_initial_policy_set = NULL; + //trust_anchor_t anchor; + bool initial_policy_mapping_inhibit = false; + bool initial_explicit_policy = false; + bool initial_any_policy_inhibit = false; + + /* Initialization */ + pvc->valid_policy_tree = policy_tree_create(&oidAnyPolicy, NULL); +#if POLICY_SUBTREES + CFMutableArrayRef permitted_subtrees = NULL; + CFMutableArrayRef excluded_subtrees = NULL; + permitted_subtrees = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + excluded_subtrees = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + assert(permitted_subtrees != NULL); + assert(excluded_subtrees != NULL); +#endif + uint32_t explicit_policy = initial_explicit_policy ? 0 : n + 1; + uint32_t inhibit_any_policy = initial_any_policy_inhibit ? 0 : n + 1; + uint32_t policy_mapping = initial_policy_mapping_inhibit ? 0 : n + 1; + +#if 0 + /* Path builder ensures we only get cert chains with proper issuer + chaining with valid signatures along the way. */ + algorithm_id_t working_public_key_algorithm = anchor->public_key_algorithm; + SecKeyRef working_public_key = anchor->public_key; + x500_name_t working_issuer_name = anchor->issuer_name; +#endif + uint32_t i, max_path_length = n; + SecCertificateRef cert = NULL; + for (i = 1; i <= n; ++i) { + /* Process Cert */ + cert = SecPVCGetCertificateAtIndex(pvc, n - i); + bool is_self_issued = SecPVCIsCertificateAtIndexSelfSigned(pvc, n - i); + + /* (a) Verify the basic certificate information. */ + /* @@@ Ensure that cert was signed with working_public_key_algorithm + using the working_public_key and the working_public_key_parameters. */ +#if 1 + /* Already done by chain builder. */ + if (!SecCertificateIsValid(cert, verify_time)) { + CFStringRef fail_key = i == n ? kSecPolicyCheckValidLeaf : kSecPolicyCheckValidIntermediates; + if (!SecPVCSetResult(pvc, fail_key, n - i, kCFBooleanFalse)) + return; + } +#endif +#if 0 + /* Check revocation status if the certificate asks for it. */ + CFArrayRef ocspResponders = SecCertificateGetOCSPResponders(cert); + if (ocspResponders) { + SecPVCSetCheckRevocation(pvc); + } +#endif + /* @@@ cert.issuer == working_issuer_name. */ + +#if POLICY_SUBTREES + /* (b) (c) */ + if (!is_self_issued || i == n) { + bool found = false; + /* Verify certificate Subject Name and SubjectAltNames are not within any of the excluded_subtrees */ + if(excluded_subtrees && CFArrayGetCount(excluded_subtrees)) { + if ((errSecSuccess != SecNameContraintsMatchSubtrees(cert, excluded_subtrees, &found)) || found) { + if(!SecPVCSetResultForced(pvc, key, n - i, kCFBooleanFalse, true)) return; + } + } + /* Verify certificate Subject Name and SubjectAltNames are within the permitted_subtrees */ + if(permitted_subtrees && CFArrayGetCount(permitted_subtrees)) { + if ((errSecSuccess != SecNameContraintsMatchSubtrees(cert, permitted_subtrees, &found)) || !found) { + if(!SecPVCSetResultForced(pvc, key, n - i, kCFBooleanFalse, true)) return; + } + } + } +#endif + /* (d) */ + if (pvc->valid_policy_tree) { + const SecCECertificatePolicies *cp = + SecCertificateGetCertificatePolicies(cert); + size_t policy_ix, policy_count = cp ? cp->numPolicies : 0; + for (policy_ix = 0; policy_ix < policy_count; ++policy_ix) { + const SecCEPolicyInformation *policy = &cp->policies[policy_ix]; + oid_t p_oid = policy->policyIdentifier; + policy_qualifier_t p_q = &policy->policyQualifiers; + struct policy_tree_add_ctx ctx = { p_oid, p_q }; + if (!oid_equal(p_oid, oidAnyPolicy)) { + if (!policy_tree_walk_depth(pvc->valid_policy_tree, i - 1, + policy_tree_add_if_match, &ctx)) { + policy_tree_walk_depth(pvc->valid_policy_tree, i - 1, + policy_tree_add_if_any, &ctx); + } + } + } + /* The certificate policies extension includes the policy + anyPolicy with the qualifier set AP-Q and either + (a) inhibit_anyPolicy is greater than 0 or + (b) i < n and the certificate is self-issued. */ + if (inhibit_any_policy > 0 || (i < n && is_self_issued)) { + for (policy_ix = 0; policy_ix < policy_count; ++policy_ix) { + const SecCEPolicyInformation *policy = &cp->policies[policy_ix]; + oid_t p_oid = policy->policyIdentifier; + policy_qualifier_t p_q = &policy->policyQualifiers; + if (oid_equal(p_oid, oidAnyPolicy)) { + policy_tree_walk_depth(pvc->valid_policy_tree, i - 1, + policy_tree_add_expected, (void *)p_q); + } + } + } + policy_tree_prune_childless(&pvc->valid_policy_tree, i - 1); + /* (e) */ + if (!cp) { + if (pvc->valid_policy_tree) + policy_tree_prune(&pvc->valid_policy_tree); + } + } + /* (f) Verify that either explicit_policy is greater than 0 or the + valid_policy_tree is not equal to NULL. */ + if (!pvc->valid_policy_tree && explicit_policy == 0) { + /* valid_policy_tree is empty and explicit policy is 0, illegal. */ + if (!SecPVCSetResultForced(pvc, key /* @@@ Need custom key */, n - i, kCFBooleanFalse, true)) + return; + } + /* If Last Cert in Path */ + if (i == n) + break; + + /* Prepare for Next Cert */ +#if POLICY_MAPPING + /* (a) verify that anyPolicy does not appear as an + issuerDomainPolicy or a subjectDomainPolicy */ + CFDictionaryRef pm = SecCertificateGetPolicyMappings(cert); + if (pm) { + uint32_t mapping_ix, mapping_count = pm->numMappings; + for (mapping_ix = 0; mapping_ix < mapping_count; ++mapping_ix) { + const SecCEPolicyMapping *mapping = &pm->mappings[mapping_ix]; + if (oid_equal(mapping->issuerDomainPolicy, oidAnyPolicy) + || oid_equal(mapping->subjectDomainPolicy, oidAnyPolicy)) { + /* Policy mapping uses anyPolicy, illegal. */ + if (!SecPVCSetResultForced(pvc, key /* @@@ Need custom key */, n - i, kCFBooleanFalse)) + return; + } + } + /* (b) */ + /* (1) If the policy_mapping variable is greater than 0 */ + if (policy_mapping > 0) { + if (!policy_tree_walk_depth(pvc->valid_policy_tree, i, + policy_tree_map, (void *)pm)) { + /* If no node of depth i in the valid_policy_tree has a valid_policy of ID-P but there is a node of depth i with a valid_policy of anyPolicy, then generate a child node of the node of depth i-1 that has a valid_policy of anyPolicy as follows: + + (i) set the valid_policy to ID-P; + + (ii) set the qualifier_set to the qualifier set of the + policy anyPolicy in the certificate policies + extension of certificate i; and + (iii) set the expected_policy_set to the set of subjectDomainPolicy values that are specified as equivalent to ID-P by the policy mappings extension. */ + } + } else { + #if 0 + /* (i) delete each node of depth i in the valid_policy_tree + where ID-P is the valid_policy. */ + struct policy_tree_map_ctx ctx = { idp_oid, sdp_oid }; + policy_tree_walk_depth(pvc->valid_policy_tree, i, + policy_tree_delete_if_match, &ctx); + #endif + /* (ii) If there is a node in the valid_policy_tree of depth + i-1 or less without any child nodes, delete that + node. Repeat this step until there are no nodes of + depth i-1 or less without children. */ + policy_tree_prune_childless(&pvc->valid_policy_tree, i - 1); + } + } +#endif /* POLICY_MAPPING */ + /* (c)(d)(e)(f) */ + //working_issuer_name = SecCertificateGetNormalizedSubjectContent(cert); + //working_public_key = SecCertificateCopyPublicKey(cert); + //working_public_key_parameters = SecCertificateCopyPublicKeyParameters(cert); + //working_public_key_algorithm = SecCertificateCopyPublicKeyAlgorithm(cert); +#if POLICY_SUBTREES + /* (g) If a name constraints extension is included in the certificate, modify the permitted_subtrees and excluded_subtrees state variables. + */ + CFArrayRef permitted_subtrees_in_cert = SecCertificateGetPermittedSubtrees(cert); + if (permitted_subtrees_in_cert) { + SecNameConstraintsIntersectSubtrees(permitted_subtrees, permitted_subtrees_in_cert); + } + + // could do something smart here to avoid inserting the exact same constraint + CFArrayRef excluded_subtrees_in_cert = SecCertificateGetExcludedSubtrees(cert); + if (excluded_subtrees_in_cert) { + CFIndex num_trees = CFArrayGetCount(excluded_subtrees_in_cert); + CFRange range = { 0, num_trees }; + CFArrayAppendArray(excluded_subtrees, excluded_subtrees_in_cert, range); + } +#endif + /* (h) */ + if (!is_self_issued) { + if (explicit_policy) + explicit_policy--; + if (policy_mapping) + policy_mapping--; + if (inhibit_any_policy) + inhibit_any_policy--; + } + /* (i) */ + const SecCEPolicyConstraints *pc = + SecCertificateGetPolicyConstraints(cert); + if (pc) { + if (pc->requireExplicitPolicyPresent + && pc->requireExplicitPolicy < explicit_policy) { + explicit_policy = pc->requireExplicitPolicy; + } + if (pc->inhibitPolicyMappingPresent + && pc->inhibitPolicyMapping < policy_mapping) { + policy_mapping = pc->inhibitPolicyMapping; + } + } + /* (j) */ + uint32_t iap = SecCertificateGetInhibitAnyPolicySkipCerts(cert); + if (iap < inhibit_any_policy) { + inhibit_any_policy = iap; + } + /* (k) */ + const SecCEBasicConstraints *bc = + SecCertificateGetBasicConstraints(cert); +#if 0 /* Checked in chain builder pre signature verify already. */ + if (!bc || !bc->isCA) { + /* Basic constraints not present or not marked as isCA, illegal. */ + if (!SecPVCSetResult(pvc, kSecPolicyCheckBasicContraints, + n - i, kCFBooleanFalse)) + return; + } +#endif + /* (l) */ + if (!is_self_issued) { + if (max_path_length > 0) { + max_path_length--; + } else { + /* max_path_len exceeded, illegal. */ + if (!SecPVCSetResult(pvc, kSecPolicyCheckBasicContraints, + n - i, kCFBooleanFalse)) + return; + } + } + /* (m) */ + if (bc && bc->pathLenConstraintPresent + && bc->pathLenConstraint < max_path_length) { + max_path_length = bc->pathLenConstraint; + } +#if 0 /* Checked in chain builder pre signature verify already. */ + /* (n) If a key usage extension is present, verify that the keyCertSign bit is set. */ + SecKeyUsage keyUsage = SecCertificateGetKeyUsage(cert); + if (keyUsage && !(keyUsage & kSecKeyUsageKeyCertSign)) { + if (!SecPVCSetResultForced(pvc, kSecPolicyCheckKeyUsage, + n - i, kCFBooleanFalse, true)) + return; + } +#endif + /* (o) Recognize and process any other critical extension present in the certificate. Process any other recognized non-critical extension present in the certificate that is relevant to path processing. */ + if (SecCertificateHasUnknownCriticalExtension(cert)) { + /* Certificate contains one or more unknown critical extensions. */ + if (!SecPVCSetResult(pvc, kSecPolicyCheckCriticalExtensions, + n - i, kCFBooleanFalse)) + return; + } + } /* end loop over certs in path */ + /* Wrap up */ + cert = SecPVCGetCertificateAtIndex(pvc, 0); + /* (a) */ + if (explicit_policy) + explicit_policy--; + /* (b) */ + const SecCEPolicyConstraints *pc = SecCertificateGetPolicyConstraints(cert); + if (pc) { + if (pc->requireExplicitPolicyPresent + && pc->requireExplicitPolicy == 0) { + explicit_policy = 0; + } + } + /* (c) */ + //working_public_key = SecCertificateCopyPublicKey(cert); + /* (d) */ + /* If the subjectPublicKeyInfo field of the certificate contains an algorithm field with null parameters or parameters are omitted, compare the certificate subjectPublicKey algorithm to the working_public_key_algorithm. If the certificate subjectPublicKey algorithm and the +working_public_key_algorithm are different, set the working_public_key_parameters to null. */ + //working_public_key_parameters = SecCertificateCopyPublicKeyParameters(cert); + /* (e) */ + //working_public_key_algorithm = SecCertificateCopyPublicKeyAlgorithm(cert); + /* (f) Recognize and process any other critical extension present in the certificate n. Process any other recognized non-critical extension present in certificate n that is relevant to path processing. */ + if (SecCertificateHasUnknownCriticalExtension(cert)) { + /* Certificate contains one or more unknown critical extensions. */ + if (!SecPVCSetResult(pvc, kSecPolicyCheckCriticalExtensions, + 0, kCFBooleanFalse)) + return; + } + /* (g) Calculate the intersection of the valid_policy_tree and the user-initial-policy-set, as follows */ + + if (pvc->valid_policy_tree) { +#if !defined(NDEBUG) + policy_tree_dump(pvc->valid_policy_tree); +#endif + /* (g3c4) */ + //policy_tree_prune_childless(&pvc->valid_policy_tree, n - 1); + } + + /* If either (1) the value of explicit_policy variable is greater than + zero or (2) the valid_policy_tree is not NULL, then path processing + has succeeded. */ + if (!pvc->valid_policy_tree && explicit_policy == 0) { + /* valid_policy_tree is empty and explicit policy is 0, illegal. */ + if (!SecPVCSetResultForced(pvc, key /* @@@ Need custom key */, 0, kCFBooleanFalse, true)) + return; + } + + CFReleaseNull(permitted_subtrees); + CFReleaseNull(excluded_subtrees); +} + +static policy_set_t policies_for_cert(SecCertificateRef cert) { + policy_set_t policies = NULL; + const SecCECertificatePolicies *cp = + SecCertificateGetCertificatePolicies(cert); + size_t policy_ix, policy_count = cp ? cp->numPolicies : 0; + for (policy_ix = 0; policy_ix < policy_count; ++policy_ix) { + policy_set_add(&policies, &cp->policies[policy_ix].policyIdentifier); + } + return policies; +} + +static void SecPolicyCheckEV(SecPVCRef pvc, + CFStringRef key) { + CFIndex ix, count = SecPVCGetCertificateCount(pvc); + policy_set_t valid_policies = NULL; + + for (ix = 0; ix < count; ++ix) { + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, ix); + policy_set_t policies = policies_for_cert(cert); + if (ix == 0) { + /* Subscriber */ + /* anyPolicy in the leaf isn't allowed for EV, so only init + valid_policies if we have real policies. */ + if (!policy_set_contains(policies, &oidAnyPolicy)) { + valid_policies = policies; + policies = NULL; + } + } else if (ix < count - 1) { + /* Subordinate CA */ + if (!SecPolicySubordinateCACertificateCouldBeEV(cert)) { + secdebug("ev", "subordinate certificate is not ev"); + if (SecPVCSetResultForced(pvc, key, + ix, kCFBooleanFalse, true)) { + policy_set_free(valid_policies); + policy_set_free(policies); + return; + } + } + policy_set_intersect(&valid_policies, policies); + } else { + /* Root CA */ + if (!SecPolicyRootCACertificateIsEV(cert, valid_policies)) { + secdebug("ev", "anchor certificate is not ev"); + if (SecPVCSetResultForced(pvc, key, + ix, kCFBooleanFalse, true)) { + policy_set_free(valid_policies); + policy_set_free(policies); + return; + } + } + } + policy_set_free(policies); + if (!valid_policies) { + secdebug("ev", "valid_policies set is empty: chain not ev"); + /* If we ever get into a state where no policies are valid anymore + this can't be an ev chain. */ + if (SecPVCSetResultForced(pvc, key, + ix, kCFBooleanFalse, true)) { + return; + } + } + } + + policy_set_free(valid_policies); + + /* (a) EV Subscriber Certificates Each EV Certificate issued by the CA to a +Subscriber MUST contain an OID defined by the CA in the certificate’s +certificatePolicies extension that: (i) indicates which CA policy statement relates +to that certificate, (ii) asserts the CA’s adherence to and compliance with these +Guidelines, and (iii), by pre-agreement with the Application Software Vendor, +marks the certificate as being an EV Certificate. +(b) EV Subordinate CA Certificates +(1) Certificates issued to Subordinate CAs that are not controlled by the issuing +CA MUST contain one or more OIDs defined by the issuing CA that +explicitly identify the EV Policies that are implemented by the Subordinate +CA; +(2) Certificates issued to Subordinate CAs that are controlled by the Root CA +MAY contain the special anyPolicy OID (2.5.29.32.0). +(c) Root CA Certificates Root CA Certificates SHOULD NOT contain the +certificatePolicies or extendedKeyUsage extensions. +*/ +} + + +/* + * MARK: Certificate Transparency support + */ + +/*** + +struct { + Version sct_version; // 1 byte + LogID id; // 32 bytes + uint64 timestamp; // 8 bytes + CtExtensions extensions; // 2 bytes len field, + n bytes data + digitally-signed struct { // 1 byte hash alg, 1 byte sig alg, n bytes signature + Version sct_version; + SignatureType signature_type = certificate_timestamp; + uint64 timestamp; + LogEntryType entry_type; + select(entry_type) { + case x509_entry: ASN.1Cert; + case precert_entry: PreCert; + } signed_entry; + CtExtensions extensions; + }; +} SignedCertificateTimestamp; + +***/ + +#include + +static const +SecAsn1Oid *oidForSigAlg(SSL_HashAlgorithm hash, SSL_SignatureAlgorithm alg) +{ + switch(alg) { + case SSL_SignatureAlgorithmRSA: + switch (hash) { + case SSL_HashAlgorithmSHA1: + return &CSSMOID_SHA1WithRSA; + case SSL_HashAlgorithmSHA256: + return &CSSMOID_SHA256WithRSA; + case SSL_HashAlgorithmSHA384: + return &CSSMOID_SHA384WithRSA; + default: + break; + } + case SSL_SignatureAlgorithmECDSA: + switch (hash) { + case SSL_HashAlgorithmSHA1: + return &CSSMOID_ECDSA_WithSHA1; + case SSL_HashAlgorithmSHA256: + return &CSSMOID_ECDSA_WithSHA256; + case SSL_HashAlgorithmSHA384: + return &CSSMOID_ECDSA_WithSHA384; + default: + break; + } + default: + break; + } + + return NULL; +} + + +static size_t SSLDecodeUint16(const uint8_t *p) +{ + return (p[0]<<8 | p[1]); +} + +static uint8_t *SSLEncodeUint16(uint8_t *p, size_t len) +{ + p[0] = (len >> 8)&0xff; + p[1] = (len & 0xff); + return p+2; +} + +static uint8_t *SSLEncodeUint24(uint8_t *p, size_t len) +{ + p[0] = (len >> 16)&0xff; + p[1] = (len >> 8)&0xff; + p[2] = (len & 0xff); + return p+3; +} + + +static +uint64_t SSLDecodeUint64(const uint8_t *p) +{ + uint64_t u = 0; + for(int i=0; i<8; i++) { + u=(u<<8)|p[0]; + p++; + } + return u; +} + +#include +#include +#include + + +static CFDataRef copy_x509_entry_from_chain(SecPVCRef pvc) +{ + SecCertificateRef leafCert = SecPVCGetCertificateAtIndex(pvc, 0); + + CFMutableDataRef data = CFDataCreateMutable(kCFAllocatorDefault, 3+SecCertificateGetLength(leafCert)); + + CFDataSetLength(data, 3+SecCertificateGetLength(leafCert)); + + uint8_t *q = CFDataGetMutableBytePtr(data); + q = SSLEncodeUint24(q, SecCertificateGetLength(leafCert)); + memcpy(q, SecCertificateGetBytePtr(leafCert), SecCertificateGetLength(leafCert)); + + return data; +} + + +static CFDataRef copy_precert_entry_from_chain(SecPVCRef pvc) +{ + SecCertificateRef leafCert = NULL; + SecCertificateRef issuer = NULL; + CFDataRef issuerKeyHash = NULL; + CFDataRef tbs_precert = NULL; + CFMutableDataRef data= NULL; + + require_quiet(SecPVCGetCertificateCount(pvc)>=2, out); //we need the issuer key for precerts. + leafCert = SecPVCGetCertificateAtIndex(pvc, 0); + issuer = SecPVCGetCertificateAtIndex(pvc, 1); + + require(leafCert, out); + require(issuer, out); // Those two would likely indicate an internal error, since we already checked the chain length above. + issuerKeyHash = SecCertificateCopySubjectPublicKeyInfoSHA256Digest(issuer); + tbs_precert = SecCertificateCopyPrecertTBS(leafCert); + + require(issuerKeyHash, out); + require(tbs_precert, out); + data = CFDataCreateMutable(kCFAllocatorDefault, CFDataGetLength(issuerKeyHash) + 3 + CFDataGetLength(tbs_precert)); + CFDataSetLength(data, CFDataGetLength(issuerKeyHash) + 3 + CFDataGetLength(tbs_precert)); + + uint8_t *q = CFDataGetMutableBytePtr(data); + memcpy(q, CFDataGetBytePtr(issuerKeyHash), CFDataGetLength(issuerKeyHash)); q += CFDataGetLength(issuerKeyHash); // issuer key hash + q = SSLEncodeUint24(q, CFDataGetLength(tbs_precert)); + memcpy(q, CFDataGetBytePtr(tbs_precert), CFDataGetLength(tbs_precert)); + +out: + CFReleaseSafe(issuerKeyHash); + CFReleaseSafe(tbs_precert); + return data; +} + +/* If the 'sct' is valid, return the operator ID of the log that signed this sct. + + The SCT is valid if: + - It decodes properly. + - Its timestamp is less than 'verifyTime'. + - It is signed by a log in 'trustedLogs'. + - The signing log expiryTime (if any) is less than 'verifyTime' (entry_type==0) or 'issuanceTime' (entry_type==1). + + If the SCT is valid, the returned CFStringRef is the identifier for the log operator. That value is not retained. + If the SCT is valid, '*validLogAtVerifyTime' is set to true if the log is not expired at 'verifyTime' + + If the SCT is not valid this function return NULL. + */ +static CFStringRef get_valid_sct_operator(CFDataRef sct, int entry_type, CFDataRef entry, CFAbsoluteTime verifyTime, CFAbsoluteTime issuanceTime, CFArrayRef trustedLogs, bool *validLogAtVerifyTime) +{ + uint8_t version; + const uint8_t *logID; + const uint8_t *timestampData; + uint64_t timestamp; + size_t extensionsLen; + const uint8_t *extensionsData; + uint8_t hashAlg; + uint8_t sigAlg; + size_t signatureLen; + const uint8_t *signatureData; + CFStringRef result = NULL; + SecKeyRef pubKey = NULL; + uint8_t *signed_data = NULL; + const SecAsn1Oid *oid = NULL; + SecAsn1AlgId algId; + + const uint8_t *p = CFDataGetBytePtr(sct); + size_t len = CFDataGetLength(sct); + uint64_t vt =(uint64_t)( verifyTime + kCFAbsoluteTimeIntervalSince1970) * 1000; + + require(len>=43, out); + + version = p[0]; p++; len--; + logID = p; p+=32; len-=32; + timestampData = p; p+=8; len-=8; + extensionsLen = SSLDecodeUint16(p); p+=2; len-=2; + + require(len>=extensionsLen, out); + extensionsData = p; p+=extensionsLen; len-=extensionsLen; + + require(len>=4, out); + hashAlg=p[0]; p++; len--; + sigAlg=p[0]; p++; len--; + signatureLen = SSLDecodeUint16(p); p+=2; len-=2; + require(len==signatureLen, out); /* We do not tolerate any extra data after the signature */ + signatureData = p; + + /* verify version: only v1(0) is supported */ + if(version!=0) { + secerror("SCT version unsupported: %d\n", version); + goto out; + } + + /* verify timestamp not in the future */ + timestamp = SSLDecodeUint64(timestampData); + if(timestamp > vt) { + secerror("SCT is in the future: %llu > %llu\n", timestamp, vt); + goto out; + } + + uint8_t *q; + + /* signed entry */ + size_t signed_data_len = 12 + CFDataGetLength(entry) + 2 + extensionsLen ; + signed_data = malloc(signed_data_len); + require(signed_data, out); + q = signed_data; + *q++ = version; + *q++ = 0; // certificate_timestamp + memcpy(q, timestampData, 8); q+=8; + q = SSLEncodeUint16(q, entry_type); // logentry type: 0=cert 1=precert + memcpy(q, CFDataGetBytePtr(entry), CFDataGetLength(entry)); q += CFDataGetLength(entry); + q = SSLEncodeUint16(q, extensionsLen); + memcpy(q, extensionsData, extensionsLen); + + CFDataRef logIDData = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, logID, 32, NULL); + + CFDictionaryRef logData = CFArrayGetValueMatching(trustedLogs, ^bool(const void *dict) { + const void *key_data; + if(!isDictionary(dict)) return false; + if(!CFDictionaryGetValueIfPresent(dict, CFSTR("key"), &key_data)) return false; + if(!isData(key_data)) return false; + CFDataRef valueID = SecSHA256DigestCreateFromData(kCFAllocatorDefault, (CFDataRef)key_data); + bool result = (bool)(CFDataCompare(logIDData, valueID)==kCFCompareEqualTo); + CFReleaseSafe(valueID); + return result; + }); + require(logData, out); + + /* If an expiry date is specified, and is a valid CFDate, then we check it against issuanceTime or verifyTime */ + const void *expiry_date; + if(CFDictionaryGetValueIfPresent(logData, CFSTR("expiry"), &expiry_date) && isDate(expiry_date)) { + CFAbsoluteTime expiryTime = CFDateGetAbsoluteTime(expiry_date); + if(entry_type == 1) {/* pre-cert: check the validity of the log at issuanceTime */ + require(issuanceTime<=expiryTime, out); + } else { + require(verifyTime<=expiryTime, out); + } + *validLogAtVerifyTime = (verifyTime<=expiryTime); + } else { + *validLogAtVerifyTime = true; + } + + CFDataRef logKeyData = CFDictionaryGetValue(logData, CFSTR("key")); + require(logKeyData, out); // This failing would be an internal logic error + pubKey = SecKeyCreateFromSubjectPublicKeyInfoData(kCFAllocatorDefault, logKeyData); + require(pubKey, out); + + oid = oidForSigAlg(hashAlg, sigAlg); + require(oid, out); + + algId.algorithm = *oid; + algId.parameters.Data = NULL; + algId.parameters.Length = 0; + + if(SecKeyDigestAndVerify(pubKey, &algId, signed_data, signed_data_len, signatureData, signatureLen)==0) { + result = CFDictionaryGetValue(logData, CFSTR("operator")); + } else { + secerror("SCT signature failed (log=%@)\n", logData); + } + +out: + CFReleaseSafe(pubKey); + free(signed_data); + return result; +} + +static CFArrayRef copy_ocsp_scts(SecPVCRef pvc) +{ + CFMutableArrayRef SCTs = NULL; + SecCertificateRef leafCert = NULL; + SecCertificateRef issuer = NULL; + CFArrayRef ocspResponsesData = NULL; + SecOCSPRequestRef ocspRequest = NULL; + + ocspResponsesData = SecPathBuilderCopyOCSPResponses(pvc->builder); + require_quiet(ocspResponsesData, out); + + require_quiet(SecPVCGetCertificateCount(pvc)>=2, out); //we need the issuer key for precerts. + leafCert = SecPVCGetCertificateAtIndex(pvc, 0); + issuer = SecPVCGetCertificateAtIndex(pvc, 1); + + require(leafCert, out); + require(issuer, out); // not quiet: Those two would likely indicate an internal error, since we already checked the chain length above. + ocspRequest = SecOCSPRequestCreate(leafCert, issuer); + + SCTs = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + require(SCTs, out); + + CFArrayForEach(ocspResponsesData, ^(const void *value) { + /* TODO: Should the builder already have the appropriate SecOCSPResponseRef ? */ + SecOCSPResponseRef ocspResponse = SecOCSPResponseCreate(value); + if(ocspResponse && SecOCSPGetResponseStatus(ocspResponse)==kSecOCSPSuccess) { + SecOCSPSingleResponseRef ocspSingleResponse = SecOCSPResponseCopySingleResponse(ocspResponse, ocspRequest); + if(ocspSingleResponse) { + CFArrayRef singleResponseSCTs = SecOCSPSingleResponseCopySCTs(ocspSingleResponse); + if(singleResponseSCTs) { + CFArrayAppendArray(SCTs, singleResponseSCTs, CFRangeMake(0, CFArrayGetCount(singleResponseSCTs))); + CFRelease(singleResponseSCTs); + } + SecOCSPSingleResponseDestroy(ocspSingleResponse); + } + SecOCSPResponseFinalize(ocspResponse); + } + }); + + if(CFArrayGetCount(SCTs)==0) { + CFReleaseNull(SCTs); + } + +out: + CFReleaseSafe(ocspResponsesData); + if(ocspRequest) + SecOCSPRequestFinalize(ocspRequest); + + return SCTs; +} + +static void SecPolicyCheckCT(SecPVCRef pvc, CFStringRef key) +{ + SecCertificateRef leafCert = SecPVCGetCertificateAtIndex(pvc, 0); + CFArrayRef embeddedScts = SecCertificateCopySignedCertificateTimestamps(leafCert); + CFArrayRef builderScts = SecPathBuilderCopySignedCertificateTimestamps(pvc->builder); + CFArrayRef trustedLogs = SecPathBuilderCopyTrustedLogs(pvc->builder); + CFArrayRef ocspScts = copy_ocsp_scts(pvc); + CFDataRef precertEntry = copy_precert_entry_from_chain(pvc); + CFDataRef x509Entry = copy_x509_entry_from_chain(pvc); + + // This eventually contain the list of operators who validated the SCT. + CFMutableSetRef operatorsValidatingEmbeddedScts = CFSetCreateMutable(kCFAllocatorDefault, 0, &kCFTypeSetCallBacks); + CFMutableSetRef operatorsValidatingExternalScts = CFSetCreateMutable(kCFAllocatorDefault, 0, &kCFTypeSetCallBacks); + + __block bool atLeastOneValidAtVerifyTime = false; + __block int lifetime; // in Months + + require(operatorsValidatingEmbeddedScts, out); + require(operatorsValidatingExternalScts, out); + + if(trustedLogs) { // Don't bother trying to validate SCTs if we don't have any trusted logs. + if(embeddedScts) { + CFArrayForEach(embeddedScts, ^(const void *value){ + bool validLogAtVerifyTime = false; + CFStringRef operator = get_valid_sct_operator(value, 1, precertEntry, pvc->verifyTime, SecCertificateNotValidBefore(leafCert), trustedLogs, &validLogAtVerifyTime); + if(operator) CFSetAddValue(operatorsValidatingEmbeddedScts, operator); + if(validLogAtVerifyTime) atLeastOneValidAtVerifyTime = true; + }); + } + + if(builderScts) { + CFArrayForEach(builderScts, ^(const void *value){ + bool validLogAtVerifyTime = false; + CFStringRef operator = get_valid_sct_operator(value, 0, x509Entry, pvc->verifyTime, SecCertificateNotValidBefore(leafCert), trustedLogs, &validLogAtVerifyTime); + if(operator) CFSetAddValue(operatorsValidatingExternalScts, operator); + if(validLogAtVerifyTime) atLeastOneValidAtVerifyTime = true; + }); + } + + if(ocspScts) { + CFArrayForEach(ocspScts, ^(const void *value){ + bool validLogAtVerifyTime = false; + CFStringRef operator = get_valid_sct_operator(value, 0, x509Entry, pvc->verifyTime, SecCertificateNotValidBefore(leafCert), trustedLogs, &validLogAtVerifyTime); + if(operator) CFSetAddValue(operatorsValidatingExternalScts, operator); + if(validLogAtVerifyTime) atLeastOneValidAtVerifyTime = true; + }); + } + } + + /* We now have 2 sets of operators that validated those SCTS, count them and make a final decision. + Current Policy: + is_ct = (A1 OR A2) AND B. + + A1: 2+ to 5+ SCTs from the cert from independent logs valid at issuance time + (operatorsValidatingEmbeddedScts) + A2: 2+ SCTs from external sources (OCSP stapled response and TLS extension) + from independent logs valid at verify time. (operatorsValidatingExternalScts) + B: All least one SCTs from a log valid at verify time. + + Policy is based on: https://docs.google.com/viewer?a=v&pid=sites&srcid=ZGVmYXVsdGRvbWFpbnxjZXJ0aWZpY2F0ZXRyYW5zcGFyZW5jeXxneDo0ODhjNGRlOTIyMzYwNTcz + with one difference: we consider SCTs from OCSP and TLS extensions as a whole. + It sounds like this is what Google will eventually do, per: + https://groups.google.com/forum/?fromgroups#!topic/certificate-transparency/VdXuzA3TLWY + + */ + + SecCFCalendarDoWithZuluCalendar(^(CFCalendarRef zuluCalendar) { + int _lifetime; + CFCalendarGetComponentDifference(zuluCalendar, + SecCertificateNotValidBefore(leafCert), + SecCertificateNotValidAfter(leafCert), + 0, "M", &_lifetime); + lifetime = _lifetime; + }); + + CFIndex requiredEmbeddedSctsCount; + + if (lifetime < 15) { + requiredEmbeddedSctsCount = 2; + } else if (lifetime <= 27) { + requiredEmbeddedSctsCount = 3; + } else if (lifetime <= 39) { + requiredEmbeddedSctsCount = 4; + } else { + requiredEmbeddedSctsCount = 5; + } + + pvc->is_ct = ((CFSetGetCount(operatorsValidatingEmbeddedScts) >= requiredEmbeddedSctsCount) || + (CFSetGetCount(operatorsValidatingExternalScts) >= 2) + ) && atLeastOneValidAtVerifyTime; + +out: + + CFReleaseSafe(operatorsValidatingEmbeddedScts); + CFReleaseSafe(operatorsValidatingExternalScts); + CFReleaseSafe(builderScts); + CFReleaseSafe(embeddedScts); + CFReleaseSafe(ocspScts); + CFReleaseSafe(precertEntry); + CFReleaseSafe(trustedLogs); + CFReleaseSafe(x509Entry); +} + +static void SecPolicyCheckCertificatePolicyOid(SecPVCRef pvc, CFStringRef key) +{ + CFIndex ix, count = SecPVCGetCertificateCount(pvc); + SecPolicyRef policy = SecPVCGetPolicy(pvc); + CFTypeRef value = CFDictionaryGetValue(policy->_options, key); + DERItem key_value; + key_value.data = NULL; + key_value.length = 0; + + if (CFGetTypeID(value) == CFDataGetTypeID()) + { + CFDataRef key_data = (CFDataRef)value; + key_value.data = (DERByte *)CFDataGetBytePtr(key_data); + key_value.length = (DERSize)CFDataGetLength(key_data); + + for (ix = 0; ix < count; ix++) { + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, ix); + policy_set_t policies = policies_for_cert(cert); + + if (policy_set_contains(policies, &key_value)) { + return; + } + } + SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); + } +} + + +static void SecPolicyCheckRevocation(SecPVCRef pvc, + CFStringRef key) { + SecPVCSetCheckRevocation(pvc); +} + +static void SecPolicyCheckRevocationResponseRequired(SecPVCRef pvc, + CFStringRef key) { + SecPVCSetCheckRevocationResponseRequired(pvc); +} + +static void SecPolicyCheckNoNetworkAccess(SecPVCRef pvc, + CFStringRef key) { + SecPathBuilderSetCanAccessNetwork(pvc->builder, false); +} + +// MARK: - +// MARK: SecRVCRef +/******************************************************** + ****************** SecRVCRef Functions ***************** + ********************************************************/ + +const CFAbsoluteTime kSecDefaultOCSPResponseTTL = 24.0 * 60.0 * 60.0; + +/* Revocation verification context. */ +struct OpaqueSecRVC { + /* Will contain the response data. */ + asynchttp_t http; + + /* Pointer to the pvc for this revocation check. */ + SecPVCRef pvc; + + /* The ocsp request we send to each responder. */ + SecOCSPRequestRef ocspRequest; + + /* The freshest response we received so far, from stapling or cache or responder. */ + SecOCSPResponseRef ocspResponse; + + /* The best validated candidate single response we received so far, from stapling or cache or responder. */ + SecOCSPSingleResponseRef ocspSingleResponse; + + /* Index of cert in pvc that this RVC is for 0 = leaf, etc. */ + CFIndex certIX; + + /* Index in array returned by SecCertificateGetOCSPResponders() for current + responder. */ + CFIndex responderIX; + + /* URL of current responder. */ + CFURLRef responder; + + /* Date until which this revocation status is valid. */ + CFAbsoluteTime nextUpdate; + + bool done; +}; +typedef struct OpaqueSecRVC *SecRVCRef; + +static void SecRVCDelete(SecRVCRef rvc) { + secdebug("alloc", "%p", rvc); + asynchttp_free(&rvc->http); + SecOCSPRequestFinalize(rvc->ocspRequest); + if (rvc->ocspResponse) { + SecOCSPResponseFinalize(rvc->ocspResponse); + rvc->ocspResponse = NULL; + if (rvc->ocspSingleResponse) { + SecOCSPSingleResponseDestroy(rvc->ocspSingleResponse); + rvc->ocspSingleResponse = NULL; + } + } +} + +/* Return the next responder we should contact for this rvc or NULL if we + exhausted them all. */ +static CFURLRef SecRVCGetNextResponder(SecRVCRef rvc) { + SecCertificateRef cert = SecPVCGetCertificateAtIndex(rvc->pvc, rvc->certIX); + CFArrayRef ocspResponders = SecCertificateGetOCSPResponders(cert); + if (ocspResponders) { + CFIndex responderCount = CFArrayGetCount(ocspResponders); + while (rvc->responderIX < responderCount) { + CFURLRef responder = CFArrayGetValueAtIndex(ocspResponders, rvc->responderIX); + rvc->responderIX++; + CFStringRef scheme = CFURLCopyScheme(responder); + if (scheme) { + /* We only support http and https responders currently. */ + bool valid_responder = (CFEqual(CFSTR("http"), scheme) || + CFEqual(CFSTR("https"), scheme)); + CFRelease(scheme); + if (valid_responder) + return responder; + } + } + } + return NULL; +} + +/* Fire off an async http request for this certs revocation status, return + false if request was queued, true if we're done. */ +static bool SecRVCFetchNext(SecRVCRef rvc) { + while ((rvc->responder = SecRVCGetNextResponder(rvc))) { + CFDataRef request = SecOCSPRequestGetDER(rvc->ocspRequest); + if (!request) + goto errOut; + + secdebug("ocsp", "Sending http ocsp request for cert %ld", rvc->certIX); + if (!asyncHttpPost(rvc->responder, request, &rvc->http)) { + /* Async request was posted, wait for reply. */ + return false; + } + } + +errOut: + rvc->done = true; + return true; +} + +/* Process a verified ocsp response for a given cert. Return true if the + certificate status was obtained. */ +static bool SecOCSPSingleResponseProcess(SecOCSPSingleResponseRef this, + SecRVCRef rvc) { + bool processed; + switch (this->certStatus) { + case CS_Good: + secdebug("ocsp", "CS_Good for cert %" PRIdCFIndex, rvc->certIX); + /* @@@ Mark cert as valid until a given date (nextUpdate if we have one) + in the info dictionary. */ + //cert.revokeCheckGood(true); + rvc->nextUpdate = this->nextUpdate == NULL_TIME ? this->thisUpdate + kSecDefaultOCSPResponseTTL : this->nextUpdate; + processed = true; + break; + case CS_Revoked: + secdebug("ocsp", "CS_Revoked for cert %" PRIdCFIndex, rvc->certIX); + /* @@@ Mark cert as revoked (with reason) at revocation date in + the info dictionary, or perhaps we should use a different key per + reason? That way a client using exceptions can ignore some but + not all reasons. */ + SInt32 reason = this->crlReason; + CFNumberRef cfreason = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &reason); + SecPVCSetResultForced(rvc->pvc, kSecPolicyCheckRevocation, rvc->certIX, + cfreason, true); + if (rvc->pvc && rvc->pvc->info) { + /* make the revocation reason available in the trust result */ + CFDictionarySetValue(rvc->pvc->info, kSecTrustRevocationReason, cfreason); + } + CFRelease(cfreason); + processed = true; + break; + case CS_Unknown: + /* not an error, no per-cert status, nothing here */ + secdebug("ocsp", "CS_Unknown for cert %" PRIdCFIndex, rvc->certIX); + processed = false; + break; + default: + secdebug("ocsp", "BAD certStatus (%d) for cert %" PRIdCFIndex, + (int)this->certStatus, rvc->certIX); + processed = false; + break; + } + + return processed; +} + +static void SecRVCUpdatePVC(SecRVCRef rvc) { + if (rvc->ocspSingleResponse) { + SecOCSPSingleResponseProcess(rvc->ocspSingleResponse, rvc); + } + if (rvc->ocspResponse) { + rvc->nextUpdate = SecOCSPResponseGetExpirationTime(rvc->ocspResponse); + } +} + +static bool SecOCSPResponseVerify(SecOCSPResponseRef ocspResponse, SecRVCRef rvc, CFAbsoluteTime verifyTime) { + bool trusted; + SecCertificatePathRef issuer = SecCertificatePathCopyFromParent(rvc->pvc->path, rvc->certIX + 1); + SecCertificatePathRef signer = SecOCSPResponseCopySigner(ocspResponse, issuer); + CFRelease(issuer); + + if (signer) { + if (signer == issuer) { + /* We already know we trust issuer since it's the path we are + trying to verify minus the leaf. */ + secdebug("ocsp", "ocsp responder: %@ response signed by issuer", + rvc->responder); + trusted = true; + } else { + secdebug("ocsp", + "ocsp responder: %@ response signed by cert issued by issuer", + rvc->responder); + /* @@@ Now check that we trust signer. */ + const void *ocspSigner = SecPolicyCreateOCSPSigner(); + CFArrayRef policies = CFArrayCreate(kCFAllocatorDefault, + &ocspSigner, 1, &kCFTypeArrayCallBacks); + CFRelease(ocspSigner); + struct OpaqueSecPVC ospvc; + SecPVCInit(&ospvc, rvc->pvc->builder, policies, verifyTime); + CFRelease(policies); + SecPVCSetPath(&ospvc, signer, NULL); + SecPVCLeafChecks(&ospvc); + if (ospvc.result) { + bool completed = SecPVCPathChecks(&ospvc); + /* If completed is false we are waiting for a callback, this + shouldn't happen since we aren't asking for details, no + revocation checking is done. */ + if (!completed) { + ocspdErrorLog("SecPVCPathChecks unexpectedly started " + "background job!"); + /* @@@ assert() or abort here perhaps? */ + } + } + if (ospvc.result) { + secdebug("ocsp", "response satisfies ocspSigner policy (%@)", + rvc->responder); + trusted = true; + } else { + /* @@@ We don't trust the cert so don't use this response. */ + ocspdErrorLog("ocsp response signed by certificate which " + "does not satisfy ocspSigner policy"); + trusted = false; + } + SecPVCDelete(&ospvc); + } + + CFRelease(signer); + } else { + /* @@@ No signer found for this ocsp response, discard it. */ + secdebug("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 + + return trusted; +} + +static void SecRVCConsumeOCSPResponse(SecRVCRef rvc, SecOCSPResponseRef ocspResponse /*CF_CONSUMED*/, CFTimeInterval maxAge, bool updateCache) { + SecOCSPSingleResponseRef sr = NULL; + require_quiet(ocspResponse, errOut); + SecOCSPResponseStatus orStatus = SecOCSPGetResponseStatus(ocspResponse); + require_action_quiet(orStatus == kSecOCSPSuccess, errOut, + secdebug("ocsp", "responder: %@ returned status: %d", rvc->responder, orStatus)); + require_action_quiet(sr = SecOCSPResponseCopySingleResponse(ocspResponse, rvc->ocspRequest), errOut, + secdebug("ocsp", "ocsp responder: %@ did not include status of requested cert", rvc->responder)); + // Check if this response is fresher than any (cached) response we might still have in the rvc. + require_quiet(!rvc->ocspSingleResponse || rvc->ocspSingleResponse->thisUpdate < sr->thisUpdate, errOut); + + CFAbsoluteTime verifyTime = CFAbsoluteTimeGetCurrent(); + /* TODO: If the responder doesn't have the ocsp-nocheck extension we should + check whether the leaf was revoked (we are already checking the rest of + the chain). */ + /* Check the OCSP response signature and verify the response. */ + require_quiet(SecOCSPResponseVerify(ocspResponse, rvc, + sr->certStatus == CS_Revoked ? SecOCSPResponseProducedAt(ocspResponse) : verifyTime), errOut); + + // If we get here, we have a properly signed ocsp response + // but we haven't checked dates yet. + + bool sr_valid = SecOCSPSingleResponseCalculateValidity(sr, kSecDefaultOCSPResponseTTL, verifyTime); + if (sr->certStatus == CS_Good) { + // Side effect of SecOCSPResponseCalculateValidity sets ocspResponse->expireTime + require_quiet(sr_valid && SecOCSPResponseCalculateValidity(ocspResponse, maxAge, kSecDefaultOCSPResponseTTL, verifyTime), errOut); + } else if (sr->certStatus == CS_Revoked) { + // Expire revoked responses when the subject certificate itself expires. + ocspResponse->expireTime = SecCertificateNotValidAfter(SecPVCGetCertificateAtIndex(rvc->pvc, rvc->certIX)); + } + + // Ok we like the new response, let's toss the old one. + if (updateCache) + SecOCSPCacheReplaceResponse(rvc->ocspResponse, ocspResponse, rvc->responder, verifyTime); + + if (rvc->ocspResponse) SecOCSPResponseFinalize(rvc->ocspResponse); + rvc->ocspResponse = ocspResponse; + ocspResponse = NULL; + + if (rvc->ocspSingleResponse) SecOCSPSingleResponseDestroy(rvc->ocspSingleResponse); + rvc->ocspSingleResponse = sr; + sr = NULL; + + rvc->done = sr_valid; + +errOut: + if (sr) SecOCSPSingleResponseDestroy(sr); + if (ocspResponse) SecOCSPResponseFinalize(ocspResponse); +} + +/* Callback from async http code after an ocsp response has been received. */ +static void SecOCSPFetchCompleted(asynchttp_t *http, CFTimeInterval maxAge) { + SecRVCRef rvc = (SecRVCRef)http->info; + SecPVCRef pvc = rvc->pvc; + SecOCSPResponseRef ocspResponse = NULL; + if (http->response) { + CFDataRef data = CFHTTPMessageCopyBody(http->response); + if (data) { + /* Parse the returned data as if it's an ocspResponse. */ + ocspResponse = SecOCSPResponseCreate(data); + CFRelease(data); + } + } + + SecRVCConsumeOCSPResponse(rvc, ocspResponse, maxAge, true); + // TODO: maybe we should set the cache-control: false in the http header and try again if the response is stale + + if (!rvc->done) { + /* Clear the data for the next response. */ + asynchttp_free(http); + SecRVCFetchNext(rvc); + } + + if (rvc->done) { + SecRVCUpdatePVC(rvc); + SecRVCDelete(rvc); + if (!--pvc->asyncJobCount) { + SecPathBuilderStep(pvc->builder); + } + } +} + +static void SecRVCInit(SecRVCRef rvc, SecPVCRef pvc, CFIndex certIX) { + secdebug("alloc", "%p", rvc); + rvc->pvc = pvc; + rvc->certIX = certIX; + rvc->http.queue = SecPathBuilderGetQueue(pvc->builder); + rvc->http.token = SecPathBuilderCopyClientAuditToken(pvc->builder); + rvc->http.completed = SecOCSPFetchCompleted; + rvc->http.info = rvc; + rvc->ocspRequest = NULL; + rvc->responderIX = 0; + rvc->responder = NULL; + rvc->nextUpdate = NULL_TIME; + rvc->ocspResponse = NULL; + rvc->ocspSingleResponse = NULL; + rvc->done = false; +} + + +static bool SecPVCCheckRevocation(SecPVCRef pvc) { + secdebug("ocsp", "checking revocation"); + CFIndex certIX, certCount = SecPVCGetCertificateCount(pvc); + bool completed = true; + if (certCount <= 1) { + /* Can't verify without an issuer; we're done */ + return completed; + } + if (!SecPVCIsAnchored(pvc)) { + /* We can't check revocation for chains without a trusted anchor. */ + return completed; + } + certCount--; + +#if 0 + /* TODO: Implement getting this value from the client. + Optional responder passed in though policy. */ + CFURLRef localResponder = NULL; + /* Generate a nonce in outgoing request if true. */ + bool genNonce = false; + /* Require a nonce in response if true. */ + bool requireRespNonce = false; + bool cacheReadDisable = false; + bool cacheWriteDisable = false; +#endif + + if (pvc->rvcs) { + /* We have done revocation checking already, we're done. */ + secdebug("ocsp", "Not rechecking revocation"); + return completed; + } + + /* Setup things so we check revocation status of all certs except the + anchor. */ + pvc->rvcs = calloc(sizeof(struct OpaqueSecRVC), certCount); + +#if 0 + /* Lookup cached revocation data for each certificate. */ + for (certIX = 0; certIX < certCount; ++certIX) { + SecCertificateRef cert = SecPVCGetCertificateAtIndex(rvc->pvc, rvc->certIX); + CFArrayRef ocspResponders = SecCertificateGetOCSPResponders(cert); + if (ocspResponders) { + /* First look though passed in ocsp responses. */ + //SecPVCGetOCSPResponseForCertificateAtIndex(pvc, ix, singleResponse); + + /* Then look though shared cache (we don't care which responder + something came from here). */ + CFDataRef ocspResponse = SecOCSPCacheCopyMatching(SecCertIDRef certID, NULL); + + /* Now let's parse the response. */ + if (decodeOCSPResponse(ocspResp)) { + secdebug("ocsp", "response ok: %@", ocspResp); + } else { + secdebug("ocsp", "response bad: %@", ocspResp); + /* ocsp response not ok. */ + if (!SecPVCSetResultForced(pvc, key, ix, kCFBooleanFalse, true)) + return completed; + } + CFReleaseSafe(ocspResp); + } else { + /* Check if certificate has any crl distributionPoints. */ + CFArrayRef distributionPoints = SecCertificateGetCRLDistributionPoints(cert); + if (distributionPoints) { + /* Look for a cached CRL and potentially delta CRL for this certificate. */ + } + } + } +#endif + + /* Note that if we are multi threaded and a job completes after it + is started but before we return from this function, we don't want + a callback to decrement asyncJobCount to zero before we finish issuing + all the jobs. To avoid this we pretend we issued certCount async jobs, + and decrement pvc->asyncJobCount for each cert that we don't start a + background fetch for. */ + pvc->asyncJobCount = (unsigned int) certCount; + + /* Loop though certificates again and issue an ocsp fetch if the + revocation status checking isn't done yet. */ + for (certIX = 0; certIX < certCount; ++certIX) { + secdebug("ocsp", "checking revocation for cert: %ld", certIX); + SecRVCRef rvc = &((SecRVCRef)pvc->rvcs)[certIX]; + SecRVCInit(rvc, pvc, certIX); + if (rvc->done) + continue; + + SecCertificateRef cert = SecPVCGetCertificateAtIndex(rvc->pvc, + rvc->certIX); + /* The certIX + 1 is ok here since certCount is always at least 1 + less than the actual number of certs. */ + SecCertificateRef issuer = SecPVCGetCertificateAtIndex(rvc->pvc, + rvc->certIX + 1); + + rvc->ocspRequest = SecOCSPRequestCreate(cert, issuer); + + /* Get stapled OCSP responses */ + CFArrayRef ocspResponsesData = SecPathBuilderCopyOCSPResponses(pvc->builder); + + /* If we have any OCSP stapled responses, check those first */ + if(ocspResponsesData) { + secdebug("ocsp", "Checking stapled responses for cert %ld", certIX); + CFArrayForEach(ocspResponsesData, ^(const void *value) { + /* TODO: Should the builder already have the appropriate SecOCSPResponseRef ? */ + SecOCSPResponseRef ocspResponse = SecOCSPResponseCreate(value); + SecRVCConsumeOCSPResponse(rvc, ocspResponse, NULL_TIME, false); + }); + CFRelease(ocspResponsesData); + } + + /* Then check the cached response */ + secdebug("ocsp", "Checking cached responses for cert %ld", certIX); + SecRVCConsumeOCSPResponse(rvc, SecOCSPCacheCopyMatching(rvc->ocspRequest, NULL), NULL_TIME, false); + + /* Unless we successfully checked the revocation status of this cert + based on the cache or stapled responses, attempt to fire off an async http request + for this cert's revocation status. */ + bool fetch_done = true; + if (rvc->done || !SecPathBuilderCanAccessNetwork(pvc->builder) || + (fetch_done = SecRVCFetchNext(rvc))) { + /* We got a cache hit or we aren't allowed to access the network, + or the async http post failed. */ + SecRVCUpdatePVC(rvc); + SecRVCDelete(rvc); + /* We didn't really start a background job for this cert. */ + pvc->asyncJobCount--; + } else if (!fetch_done) { + /* We started at least one background fetch. */ + completed = false; + } + } + + /* Return false if we started any background jobs. */ + /* We can't just return !pvc->asyncJobCount here, since if we started any + jobs the completion callback will be called eventually and it will call + SecPathBuilderStep(). If for some reason everything completed before we + get here we still want the outer SecPathBuilderStep() to terminate so we + keep track of whether we started any jobs and return false if so. */ + return completed; +} + + +void SecPolicyServerInitalize(void) { + gSecPolicyLeafCallbacks = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, + &kCFTypeDictionaryKeyCallBacks, NULL); + gSecPolicyPathCallbacks = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, + &kCFTypeDictionaryKeyCallBacks, NULL); + + CFDictionaryAddValue(gSecPolicyPathCallbacks, + kSecPolicyCheckBasicCertificateProcessing, + SecPolicyCheckBasicCertificateProcessing); + CFDictionaryAddValue(gSecPolicyPathCallbacks, + kSecPolicyCheckCriticalExtensions, + SecPolicyCheckCriticalExtensions); + CFDictionaryAddValue(gSecPolicyPathCallbacks, + kSecPolicyCheckIdLinkage, + SecPolicyCheckIdLinkage); + CFDictionaryAddValue(gSecPolicyLeafCallbacks, + kSecPolicyCheckKeyUsage, + SecPolicyCheckKeyUsage); + CFDictionaryAddValue(gSecPolicyLeafCallbacks, + kSecPolicyCheckExtendedKeyUsage, + SecPolicyCheckExtendedKeyUsage); + CFDictionaryAddValue(gSecPolicyLeafCallbacks, + kSecPolicyCheckBasicContraints, + SecPolicyCheckBasicContraints); + CFDictionaryAddValue(gSecPolicyLeafCallbacks, + kSecPolicyCheckNonEmptySubject, + SecPolicyCheckNonEmptySubject); + CFDictionaryAddValue(gSecPolicyLeafCallbacks, + kSecPolicyCheckQualifiedCertStatements, + SecPolicyCheckQualifiedCertStatements); + CFDictionaryAddValue(gSecPolicyLeafCallbacks, + kSecPolicyCheckSSLHostname, + SecPolicyCheckSSLHostname); + CFDictionaryAddValue(gSecPolicyLeafCallbacks, + kSecPolicyCheckEmail, + SecPolicyCheckEmail); + CFDictionaryAddValue(gSecPolicyPathCallbacks, + kSecPolicyCheckValidIntermediates, + SecPolicyCheckValidIntermediates); + CFDictionaryAddValue(gSecPolicyLeafCallbacks, + kSecPolicyCheckValidLeaf, + SecPolicyCheckValidLeaf); + CFDictionaryAddValue(gSecPolicyPathCallbacks, + kSecPolicyCheckValidRoot, + SecPolicyCheckValidRoot); + CFDictionaryAddValue(gSecPolicyPathCallbacks, + kSecPolicyCheckIssuerCommonName, + SecPolicyCheckIssuerCommonName); + CFDictionaryAddValue(gSecPolicyLeafCallbacks, + kSecPolicyCheckSubjectCommonNamePrefix, + SecPolicyCheckSubjectCommonNamePrefix); + CFDictionaryAddValue(gSecPolicyLeafCallbacks, + kSecPolicyCheckSubjectCommonName, + SecPolicyCheckSubjectCommonName); + CFDictionaryAddValue(gSecPolicyLeafCallbacks, + kSecPolicyCheckNotValidBefore, + SecPolicyCheckNotValidBefore); + CFDictionaryAddValue(gSecPolicyPathCallbacks, + kSecPolicyCheckChainLength, + SecPolicyCheckChainLength); + CFDictionaryAddValue(gSecPolicyPathCallbacks, + kSecPolicyCheckAnchorSHA1, + SecPolicyCheckAnchorSHA1); + CFDictionaryAddValue(gSecPolicyPathCallbacks, + kSecPolicyCheckIntermediateSPKISHA256, + SecPolicyCheckIntermediateSPKISHA256); + CFDictionaryAddValue(gSecPolicyPathCallbacks, + kSecPolicyCheckAnchorApple, + SecPolicyCheckAnchorApple); + CFDictionaryAddValue(gSecPolicyLeafCallbacks, + kSecPolicyCheckSubjectOrganization, + SecPolicyCheckSubjectOrganization); + CFDictionaryAddValue(gSecPolicyLeafCallbacks, + kSecPolicyCheckSubjectOrganizationalUnit, + SecPolicyCheckSubjectOrganizationalUnit); + CFDictionaryAddValue(gSecPolicyLeafCallbacks, + kSecPolicyCheckEAPTrustedServerNames, + SecPolicyCheckEAPTrustedServerNames); + CFDictionaryAddValue(gSecPolicyLeafCallbacks, + kSecPolicyCheckSubjectCommonNameTEST, + SecPolicyCheckSubjectCommonNameTEST); + CFDictionaryAddValue(gSecPolicyLeafCallbacks, + kSecPolicyCheckRevocation, + SecPolicyCheckRevocation); + CFDictionaryAddValue(gSecPolicyLeafCallbacks, + kSecPolicyCheckRevocationResponseRequired, + SecPolicyCheckRevocationResponseRequired); + CFDictionaryAddValue(gSecPolicyLeafCallbacks, + kSecPolicyCheckNoNetworkAccess, + SecPolicyCheckNoNetworkAccess); + CFDictionaryAddValue(gSecPolicyLeafCallbacks, + kSecPolicyCheckBlackListedLeaf, + SecPolicyCheckBlackListedLeaf); + CFDictionaryAddValue(gSecPolicyLeafCallbacks, + kSecPolicyCheckGrayListedLeaf, + SecPolicyCheckGrayListedLeaf); + CFDictionaryAddValue(gSecPolicyLeafCallbacks, + kSecPolicyCheckLeafMarkerOid, + SecPolicyCheckLeafMarkerOid); + CFDictionaryAddValue(gSecPolicyPathCallbacks, + kSecPolicyCheckIntermediateMarkerOid, + SecPolicyCheckIntermediateMarkerOid); + CFDictionaryAddValue(gSecPolicyLeafCallbacks, + kSecPolicyCheckCertificatePolicy, + SecPolicyCheckCertificatePolicyOid); +} + +/* AUDIT[securityd](done): + array (ok) is a caller provided array, only its cf type has + been checked. + The options (ok) field ends up in policy->_options unchecked, so every access + of policy->_options needs to be validated. + */ +static SecPolicyRef SecPolicyCreateWithArray(CFArrayRef array) { + SecPolicyRef policy = NULL; + require_quiet(array && CFArrayGetCount(array) == 2, errOut); + CFStringRef oid = (CFStringRef)CFArrayGetValueAtIndex(array, 0); + require_quiet(isString(oid), errOut); + CFDictionaryRef options = (CFDictionaryRef)CFArrayGetValueAtIndex(array, 1); + require_quiet(isDictionary(options), errOut); + policy = SecPolicyCreate(oid, options); +errOut: + return policy; +} + +/* AUDIT[securityd](done): + value (ok) is an element in a caller provided array. + */ +static void deserializePolicy(const void *value, void *context) { + CFArrayRef policyArray = (CFArrayRef)value; + if (isArray(policyArray)) { + CFTypeRef deserializedPolicy = SecPolicyCreateWithArray(policyArray); + if (deserializedPolicy) { + CFArrayAppendValue((CFMutableArrayRef)context, deserializedPolicy); + CFRelease(deserializedPolicy); + } + } +} + +/* AUDIT[securityd](done): + serializedPolicies (ok) is a caller provided array, only its cf type has + been checked. + */ +CFArrayRef SecPolicyArrayDeserialize(CFArrayRef serializedPolicies) { + CFMutableArrayRef result = NULL; + require_quiet(isArray(serializedPolicies), errOut); + CFIndex count = CFArrayGetCount(serializedPolicies); + result = CFArrayCreateMutable(kCFAllocatorDefault, count, &kCFTypeArrayCallBacks); + CFRange all_policies = { 0, count }; + CFArrayApplyFunction(serializedPolicies, all_policies, deserializePolicy, result); +errOut: + return result; +} + +// MARK: - +// MARK: SecPVCRef +/******************************************************** + ****************** SecPVCRef Functions ***************** + ********************************************************/ + +void SecPVCInit(SecPVCRef pvc, SecPathBuilderRef builder, CFArrayRef policies, + CFAbsoluteTime verifyTime) { + secdebug("alloc", "%p", pvc); + // Weird logging policies crashes. + //secdebug("policy", "%@", policies); + pvc->builder = builder; + pvc->policies = policies; + if (policies) + CFRetain(policies); + pvc->verifyTime = verifyTime; + pvc->path = NULL; + pvc->details = NULL; + pvc->info = NULL; + pvc->valid_policy_tree = NULL; + pvc->callbacks = NULL; + pvc->policyIX = 0; + pvc->rvcs = NULL; + pvc->asyncJobCount = 0; + pvc->check_revocation = false; + pvc->response_required = false; + pvc->optionally_ev = false; + pvc->is_ev = false; + pvc->result = true; +} + +static void SecPVCDeleteRVCs(SecPVCRef pvc) { + secdebug("alloc", "%p", pvc); + if (pvc->rvcs) { + free(pvc->rvcs); + pvc->rvcs = NULL; + } +} + +void SecPVCDelete(SecPVCRef pvc) { + secdebug("alloc", "%p", pvc); + CFReleaseNull(pvc->policies); + CFReleaseNull(pvc->details); + CFReleaseNull(pvc->info); + if (pvc->valid_policy_tree) { + policy_tree_prune(&pvc->valid_policy_tree); + } + SecPVCDeleteRVCs(pvc); +} + +void SecPVCSetPath(SecPVCRef pvc, SecCertificatePathRef path, + CF_CONSUMED CFArrayRef details) { + secdebug("policy", "%@", path); + if (pvc->path != path) { + /* Changing path makes us clear the Revocation Verification Contexts */ + SecPVCDeleteRVCs(pvc); + pvc->path = path; + } + pvc->details = details; + CFReleaseNull(pvc->info); + if (pvc->valid_policy_tree) { + policy_tree_prune(&pvc->valid_policy_tree); + } + pvc->policyIX = 0; + pvc->result = true; +} + +SecPolicyRef SecPVCGetPolicy(SecPVCRef pvc) { + return (SecPolicyRef)CFArrayGetValueAtIndex(pvc->policies, pvc->policyIX); +} + +CFIndex SecPVCGetCertificateCount(SecPVCRef pvc) { + return SecCertificatePathGetCount(pvc->path); +} + +SecCertificateRef SecPVCGetCertificateAtIndex(SecPVCRef pvc, CFIndex ix) { + return SecCertificatePathGetCertificateAtIndex(pvc->path, ix); +} + +bool SecPVCIsCertificateAtIndexSelfSigned(SecPVCRef pvc, CFIndex ix) { + return SecCertificatePathSelfSignedIndex(pvc->path) == ix; +} + +void SecPVCSetCheckRevocation(SecPVCRef pvc) { + pvc->check_revocation = true; + secdebug("ocsp", "deferred revocation checking enabled"); +} + +void SecPVCSetCheckRevocationResponseRequired(SecPVCRef pvc) { + pvc->response_required = true; + secdebug("ocsp", "revocation response required"); +} + +bool SecPVCIsAnchored(SecPVCRef pvc) { + return SecCertificatePathIsAnchored(pvc->path); +} + +CFAbsoluteTime SecPVCGetVerifyTime(SecPVCRef pvc) { + return pvc->verifyTime; +} + +/* AUDIT[securityd](done): + policy->_options is a caller provided dictionary, only its cf type has + been checked. + */ +bool SecPVCSetResultForced(SecPVCRef pvc, + CFStringRef key, CFIndex ix, CFTypeRef result, bool force) { + + secdebug("policy", "cert[%d]: %@ =(%s)[%s]> %@", (int) ix, key, + (pvc->callbacks == gSecPolicyLeafCallbacks ? "leaf" + : (pvc->callbacks == gSecPolicyPathCallbacks ? "path" + : "custom")), + (force ? "force" : ""), result); + + /* If this is not something the current policy cares about ignore + this error and return true so our caller continues evaluation. */ + if (!force) { + /* @@@ The right long term fix might be to check if none of the passed + in policies contain this key, since not all checks are run for all + policies. */ + SecPolicyRef policy = SecPVCGetPolicy(pvc); + if (policy && !CFDictionaryContainsKey(policy->_options, key)) + return true; + } + + /* @@@ Check to see if the SecTrustSettings for the certificate in question + tell us to ignore this error. */ + pvc->result = false; + if (!pvc->details) + return false; + + CFMutableDictionaryRef detail = + (CFMutableDictionaryRef)CFArrayGetValueAtIndex(pvc->details, ix); + + /* Perhaps detail should have an array of results per key? As it stands + in the case of multiple policy failures the last failure stands. */ + CFDictionarySetValue(detail, key, result); + + return true; +} + +bool SecPVCSetResult(SecPVCRef pvc, + CFStringRef key, CFIndex ix, CFTypeRef result) { + return SecPVCSetResultForced(pvc, key, ix, result, false); +} + +/* AUDIT[securityd](done): + key(ok) is a caller provided. + value(ok, unused) is a caller provided. + */ +static void SecPVCValidateKey(const void *key, const void *value, + void *context) { + SecPVCRef pvc = (SecPVCRef)context; + + /* If our caller doesn't want full details and we failed earlier there is + no point in doing additional checks. */ + if (!pvc->result && !pvc->details) + return; + + SecPolicyCheckFunction fcn = (SecPolicyCheckFunction) + CFDictionaryGetValue(pvc->callbacks, key); + + if (!fcn) { +#if 0 + /* Why not to have optional policy checks rant: + Not all keys are in all dictionaries anymore, so why not make checks + optional? This way a client can ask for something and the server will + do a best effort based on the supported flags. It works since they are + synchronized now, but we need some debug checking here for now. */ + pvc->result = false; +#endif + if (pvc->callbacks == gSecPolicyLeafCallbacks) { + if (!CFDictionaryContainsKey(gSecPolicyPathCallbacks, key)) { + pvc->result = false; + } + } else if (pvc->callbacks == gSecPolicyPathCallbacks) { + if (!CFDictionaryContainsKey(gSecPolicyLeafCallbacks, key)) { + pvc->result = false; + } + } else { + /* Non standard validation phase, nothing is optional. */ + pvc->result = false; + } + return; + } + + fcn(pvc, (CFStringRef)key); +} + +/* AUDIT[securityd](done): + policy->_options is a caller provided dictionary, only its cf type has + been checked. + */ +bool SecPVCLeafChecks(SecPVCRef pvc) { + pvc->result = true; + CFArrayRef policies = pvc->policies; + CFIndex ix, count = CFArrayGetCount(policies); + for (ix = 0; ix < count; ++ix) { + SecPolicyRef policy = (SecPolicyRef)CFArrayGetValueAtIndex(policies, ix); + pvc->policyIX = ix; + /* Validate all keys for all policies. */ + pvc->callbacks = gSecPolicyLeafCallbacks; + CFDictionaryApplyFunction(policy->_options, SecPVCValidateKey, pvc); + if (!pvc->result && !pvc->details) + return pvc->result; + } + + return pvc->result; +} + +bool SecPVCParentCertificateChecks(SecPVCRef pvc, CFIndex ix) { + /* Check stuff common to intermediate and anchors. */ + CFAbsoluteTime verifyTime = SecPVCGetVerifyTime(pvc); + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, ix); + bool is_anchor = (ix == SecPVCGetCertificateCount(pvc) - 1 + && SecPVCIsAnchored(pvc)); + if (!SecCertificateIsValid(cert, verifyTime)) { + /* Certificate has expired. */ + if (!SecPVCSetResult(pvc, is_anchor ? kSecPolicyCheckValidRoot + : kSecPolicyCheckValidIntermediates, ix, kCFBooleanFalse)) + goto errOut; + } + + if (is_anchor) { + /* Perform anchor specific checks. */ + /* Don't think we have any of these. */ + } else { + /* Perform intermediate specific checks. */ + + /* (k) */ + const SecCEBasicConstraints *bc = + SecCertificateGetBasicConstraints(cert); + if (!bc || !bc->isCA) { + /* Basic constraints not present or not marked as isCA, illegal. */ + if (!SecPVCSetResultForced(pvc, kSecPolicyCheckBasicContraints, + ix, kCFBooleanFalse, true)) + goto errOut; + } + /* Consider adding (l) max_path_length checking here. */ + + /* (n) If a key usage extension is present, verify that the keyCertSign bit is set. */ + SecKeyUsage keyUsage = SecCertificateGetKeyUsage(cert); + if (keyUsage && !(keyUsage & kSecKeyUsageKeyCertSign)) { + if (!SecPVCSetResultForced(pvc, kSecPolicyCheckKeyUsage, + ix, kCFBooleanFalse, true)) + goto errOut; + } + } + +errOut: + return pvc->result; +} + +bool SecPVCBlackListedKeyChecks(SecPVCRef pvc, CFIndex ix) { + /* Check stuff common to intermediate and anchors. */ + + SecOTAPKIRef otapkiRef = SecOTAPKICopyCurrentOTAPKIRef(); + if (NULL != otapkiRef) + { + CFSetRef blackListedKeys = SecOTAPKICopyBlackListSet(otapkiRef); + CFRelease(otapkiRef); + if (NULL != blackListedKeys) + { + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, ix); + bool is_anchor = (ix == SecPVCGetCertificateCount(pvc) - 1 + && SecPVCIsAnchored(pvc)); + if (!is_anchor) { + /* Check for blacklisted intermediates keys. */ + CFDataRef dgst = SecCertificateCopyPublicKeySHA1Digest(cert); + if (dgst) { + /* Check dgst against blacklist. */ + if (CFSetContainsValue(blackListedKeys, dgst)) { + SecPVCSetResultForced(pvc, kSecPolicyCheckBlackListedKey, + ix, kCFBooleanFalse, true); + } + CFRelease(dgst); + } + } + CFRelease(blackListedKeys); + return pvc->result; + } + } + // Assume OK + return true; +} + +bool SecPVCGrayListedKeyChecks(SecPVCRef pvc, CFIndex ix) +{ + /* Check stuff common to intermediate and anchors. */ + SecOTAPKIRef otapkiRef = SecOTAPKICopyCurrentOTAPKIRef(); + if (NULL != otapkiRef) + { + CFSetRef grayListKeys = SecOTAPKICopyGrayList(otapkiRef); + CFRelease(otapkiRef); + if (NULL != grayListKeys) + { + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, ix); + bool is_anchor = (ix == SecPVCGetCertificateCount(pvc) - 1 + && SecPVCIsAnchored(pvc)); + if (!is_anchor) { + /* Check for gray listed intermediates keys. */ + CFDataRef dgst = SecCertificateCopyPublicKeySHA1Digest(cert); + if (dgst) { + /* Check dgst against gray list. */ + if (CFSetContainsValue(grayListKeys, dgst)) { + SecPVCSetResultForced(pvc, kSecPolicyCheckGrayListedKey, + ix, kCFBooleanFalse, true); + } + CFRelease(dgst); + } + } + CFRelease(grayListKeys); + return pvc->result; + } + } + // Assume ok + return true; +} + +/* AUDIT[securityd](done): + policy->_options is a caller provided dictionary, only its cf type has + been checked. + */ +bool SecPVCPathChecks(SecPVCRef pvc) { + secdebug("policy", "begin path: %@", pvc->path); + bool completed = true; + /* This needs to be initialized before we call any function that might call + SecPVCSetResultForced(). */ + pvc->policyIX = 0; + SecPolicyCheckIdLinkage(pvc, kSecPolicyCheckIdLinkage); + if (pvc->result || pvc->details) { + SecPolicyCheckBasicCertificateProcessing(pvc, + kSecPolicyCheckBasicCertificateProcessing); + } + + CFArrayRef policies = pvc->policies; + CFIndex count = CFArrayGetCount(policies); + for (; pvc->policyIX < count; ++pvc->policyIX) { + /* Validate all keys for all policies. */ + pvc->callbacks = gSecPolicyPathCallbacks; + SecPolicyRef policy = SecPVCGetPolicy(pvc); + CFDictionaryApplyFunction(policy->_options, SecPVCValidateKey, pvc); + if (!pvc->result && !pvc->details) + return completed; + } + + /* Check the things we can't check statically for the certificate path. */ + /* Critical Extensions, chainLength. */ + + /* Policy tests. */ + pvc->is_ev = false; + if ((pvc->result || pvc->details) && pvc->optionally_ev) { + bool pre_ev_check_result = pvc->result; + SecPolicyCheckEV(pvc, kSecPolicyCheckExtendedValidation); + pvc->is_ev = pvc->result; + /* If ev checking failed, we still want to accept this chain + as a non EV one, if it was valid as such. */ + pvc->result = pre_ev_check_result; + } + /* Check revocation only if the chain is valid so far. Then only check + revocation if the client asked for it explicitly or is_ev is + true. */ + if (pvc->result && (pvc->is_ev || pvc->check_revocation)) { + completed = SecPVCCheckRevocation(pvc); + } + + /* Check for CT */ + if (pvc->result || pvc->details) { + /* This call will set the value of pvc->is_ct, but won't change the result (pvc->result) */ + SecPolicyCheckCT(pvc, kSecPolicyCheckCertificateTransparency); + } + + +//errOut: + secdebug("policy", "end %strusted completed: %d path: %@", + (pvc->result ? "" : "not "), completed, pvc->path); + return completed; +} + +/* This function returns 0 to indicate revocation checking was not completed + for this certificate chain, otherwise return to date at which the first + piece of revocation checking info we used expires. */ +CFAbsoluteTime SecPVCGetEarliestNextUpdate(SecPVCRef pvc) { + CFIndex certIX, certCount = SecPVCGetCertificateCount(pvc); + CFAbsoluteTime enu = 0; + if (certCount <= 1 || !pvc->rvcs) { + return enu; + } + certCount--; + + for (certIX = 0; certIX < certCount; ++certIX) { + SecRVCRef rvc = &((SecRVCRef)pvc->rvcs)[certIX]; + if (rvc->nextUpdate == 0) { + if (certIX > 0) { + /* We allow for CA certs to not be revocation checked if they + have no ocspResponders to check against, but the leaf + must be checked in order for us to claim we did revocation + checking. */ + SecCertificateRef cert = + SecPVCGetCertificateAtIndex(rvc->pvc, rvc->certIX); + CFArrayRef ocspResponders = SecCertificateGetOCSPResponders(cert); + if (!ocspResponders || CFArrayGetCount(ocspResponders) == 0) { + /* We can't check this cert so we don't consider it a soft + failure that we didn't. Ideally we should support crl + checking and remove this workaround, since that more + strict. */ + continue; + } + } + secdebug("ocsp", "revocation checking soft failure for cert: %ld", + certIX); + enu = rvc->nextUpdate; + break; + } + if (enu == 0 || rvc->nextUpdate < enu) { + enu = rvc->nextUpdate; + } +#if 0 + /* Perhaps we don't want to do this since some policies might + ignore the certificate expiration but still use revocation + checking. */ + + /* Earliest certificate expiration date. */ + SecCertificateRef cert = SecPVCGetCertificateAtIndex(rvc->pvc, rvc->certIX); + CFAbsoluteTime nva = SecCertificateNotValidAfter(cert); + if (nva && (enu == 0 || nva < enu) + enu = nva; +#endif + } + + secdebug("ocsp", "revocation valid until: %lg", enu); + return enu; +}