X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/641423b6670d8656d5daeaf988e7d307fb6c1ebc..866f8763175ff60e4fa455b92b5eb660a12fe6c7:/OSX/sec/securityd/SecPolicyServer.c diff --git a/OSX/sec/securityd/SecPolicyServer.c b/OSX/sec/securityd/SecPolicyServer.c index 9bad034c..56d212ce 100644 --- a/OSX/sec/securityd/SecPolicyServer.c +++ b/OSX/sec/securityd/SecPolicyServer.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008-2015 Apple Inc. All Rights Reserved. + * Copyright (c) 2008-2017 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * @@ -22,13 +22,13 @@ */ /* - * SecPolicyServer.c - Trust policies dealing with certificate revocation. + * SecPolicyServer.c - Engine for evaluating certificate paths against trust policies. */ #include #include #include -#include +#include #include #include #include @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -47,23 +48,31 @@ #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" +#include "personalization.h" +#include -#define ocspdErrorLog(args...) asl_log(NULL, NULL, ASL_LEVEL_ERR, ## args) +#if !TARGET_OS_IPHONE +#include +#endif /* Set this to 1 to dump the ocsp responses received in DER form in /tmp. */ #ifndef DUMP_OCSPRESPONSES @@ -88,6 +97,10 @@ static void secdumpdata(CFDataRef data, const char *name) { ****************** SecPolicy object ******************** ********************************************************/ +static SecCertificateRef SecPVCGetCertificateAtIndex(SecPVCRef pvc, CFIndex ix); +static CFIndex SecPVCGetCertificateCount(SecPVCRef pvc); +static CFAbsoluteTime SecPVCGetVerifyTime(SecPVCRef pvc); + static CFMutableDictionaryRef gSecPolicyLeafCallbacks = NULL; static CFMutableDictionaryRef gSecPolicyPathCallbacks = NULL; @@ -115,7 +128,7 @@ static CFArrayRef SecPolicyAnchorDigestsForEVPolicy(const DERItem *policyOID) result = (CFArrayRef)CFDictionaryGetValue(evToPolicyAnchorDigest, oid); if (roots && CFGetTypeID(result) != CFArrayGetTypeID()) { - ocspdErrorLog("EVRoot.plist has non array value"); + secerror("EVRoot.plist has non array value"); result = NULL; } CFRelease(oid); @@ -125,12 +138,15 @@ static CFArrayRef SecPolicyAnchorDigestsForEVPolicy(const DERItem *policyOID) } -static bool SecPolicyIsEVPolicy(const DERItem *policyOID) { +bool SecPolicyIsEVPolicy(const DERItem *policyOID) { return SecPolicyAnchorDigestsForEVPolicy(policyOID); } static bool SecPolicyRootCACertificateIsEV(SecCertificateRef certificate, policy_set_t valid_policies) { + CFDictionaryRef keySizes = NULL; + CFNumberRef rsaSize = NULL, ecSize = NULL; + bool isEV = false; /* Ensure that this certificate is a valid anchor for one of the certificate policy oids specified in the leaf. */ CFDataRef digest = SecCertificateGetSHA1Digest(certificate); @@ -145,124 +161,101 @@ static bool SecPolicyRootCACertificateIsEV(SecCertificateRef certificate, break; } } - require_quiet(good_ev_anchor, notEV); + require_action_quiet(good_ev_anchor, notEV, secnotice("ev", "anchor not in plist")); CFAbsoluteTime october2006 = 178761600; + if (SecCertificateNotValidBefore(certificate) >= october2006) { + require_action_quiet(SecCertificateVersion(certificate) >= 3, notEV, + secnotice("ev", "Anchor issued after October 2006 and is not v3")); + } if (SecCertificateVersion(certificate) >= 3 && SecCertificateNotValidBefore(certificate) >= october2006) { const SecCEBasicConstraints *bc = SecCertificateGetBasicConstraints(certificate); - require_quiet(bc && bc->isCA == true, notEV); + require_action_quiet(bc && bc->isCA == true, notEV, + secnotice("ev", "Anchor has invalid basic constraints")); SecKeyUsage ku = SecCertificateGetKeyUsage(certificate); - require_quiet((ku & (kSecKeyUsageKeyCertSign | kSecKeyUsageCRLSign)) - == (kSecKeyUsageKeyCertSign | kSecKeyUsageCRLSign), notEV); + require_action_quiet((ku & (kSecKeyUsageKeyCertSign | kSecKeyUsageCRLSign)) + == (kSecKeyUsageKeyCertSign | kSecKeyUsageCRLSign), notEV, + secnotice("ev", "Anchor has invalid key usage %u", ku)); } - 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. */ - } + /* At least RSA 2048 or ECC NIST P-256. */ + require_quiet(rsaSize = CFNumberCreateWithCFIndex(NULL, 2048), notEV); + require_quiet(ecSize = CFNumberCreateWithCFIndex(NULL, 256), notEV); + const void *keys[] = { kSecAttrKeyTypeRSA, kSecAttrKeyTypeEC }; + const void *values[] = { rsaSize, ecSize }; + require_quiet(keySizes = CFDictionaryCreate(NULL, keys, values, 2, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks), notEV); + require_action_quiet(SecCertificateIsAtLeastMinKeySize(certificate, keySizes), notEV, + secnotice("ev", "Anchor's public key is too weak for EV")); + + isEV = true; - return true; notEV: - return false; + CFReleaseNull(rsaSize); + CFReleaseNull(ecSize); + CFReleaseNull(keySizes); + return isEV; } static bool SecPolicySubordinateCACertificateCouldBeEV(SecCertificateRef certificate) { + CFMutableDictionaryRef keySizes = NULL; + CFNumberRef rsaSize = NULL, ecSize = NULL; + bool isEV = false; + const SecCECertificatePolicies *cp; cp = SecCertificateGetCertificatePolicies(certificate); - require_quiet(cp && cp->numPolicies > 0, notEV); - /* SecCertificateGetCRLDistributionPoints() is a noop right now */ -#if 0 + require_action_quiet(cp && cp->numPolicies > 0, notEV, + secnotice("ev", "SubCA missing certificate policies")); CFArrayRef cdp = SecCertificateGetCRLDistributionPoints(certificate); - require_quiet(cdp && CFArrayGetCount(cdp) > 0, notEV); -#endif + require_action_quiet(cdp && CFArrayGetCount(cdp) > 0, notEV, + secnotice("ev", "SubCA missing CRLDP")); const SecCEBasicConstraints *bc = SecCertificateGetBasicConstraints(certificate); - require_quiet(bc && bc->isCA == true, notEV); + require_action_quiet(bc && bc->isCA == true, notEV, + secnotice("ev", "SubCA has invalid basic constraints")); 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. */ + require_action_quiet((ku & (kSecKeyUsageKeyCertSign | kSecKeyUsageCRLSign)) + == (kSecKeyUsageKeyCertSign | kSecKeyUsageCRLSign), notEV, + secnotice("ev", "SubCA has invalid key usage %u", ku)); - /* (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); + /* 6.1.5 Key Sizes */ + CFAbsoluteTime jan2011 = 315532800; + CFAbsoluteTime jan2014 = 410227200; + require_quiet(ecSize = CFNumberCreateWithCFIndex(NULL, 256), notEV); + require_quiet(keySizes = CFDictionaryCreateMutable(NULL, 2, &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks), notEV); + CFDictionaryAddValue(keySizes, kSecAttrKeyTypeEC, ecSize); + if (SecCertificateNotValidBefore(certificate) < jan2011 || + SecCertificateNotValidAfter(certificate) < jan2014) { + /* At least RSA 1024 or ECC NIST P-256. */ + require_quiet(rsaSize = CFNumberCreateWithCFIndex(NULL, 1024), notEV); + CFDictionaryAddValue(keySizes, kSecAttrKeyTypeRSA, rsaSize); + require_action_quiet(SecCertificateIsAtLeastMinKeySize(certificate, keySizes), notEV, + secnotice("ev", "SubCA's public key is too small for issuance before 2011 or expiration before 2014")); } 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); + /* At least RSA 2028 or ECC NIST P-256. */ + require_quiet(rsaSize = CFNumberCreateWithCFIndex(NULL, 2048), notEV); + CFDictionaryAddValue(keySizes, kSecAttrKeyTypeRSA, rsaSize); + require_action_quiet(SecCertificateIsAtLeastMinKeySize(certificate, keySizes), notEV, + secnotice("ev", "SubCA's public key is too small for issuance after 2010 or expiration after 2013")); } - /* (e) keyUsage. */ - SecKeyUsage ku = SecCertificateGetKeyUsage(certificate); - if (ku) { - require_quiet((ku & (kSecKeyUsageKeyCertSign | kSecKeyUsageCRLSign)) == 0, notEV); + /* 7.1.3 Algorithm Object Identifiers */ + CFAbsoluteTime jan2016 = 473299200; + if (SecCertificateNotValidBefore(certificate) > jan2016) { + /* SHA-2 only */ + require_action_quiet(SecCertificateGetSignatureHashAlgorithm(certificate) > kSecSignatureHashAlgorithmSHA1, + notEV, secnotice("ev", "SubCA was issued with SHA-1 after 2015")); } -#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. */ - } + isEV = true; - return true; notEV: - return false; + CFReleaseNull(rsaSize); + CFReleaseNull(ecSize); + CFReleaseNull(keySizes); + return isEV; } /******************************************************** @@ -298,78 +291,25 @@ static void SecPolicyCheckIdLinkage(SecPVCRef pvc, } } -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) { + if (!SecPolicyCheckCertKeyUsage(leaf, xku)) { 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) { + if (!SecPolicyCheckCertExtendedKeyUsage(leaf, xeku)){ SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); } } @@ -437,7 +377,7 @@ static void SecPolicyCheckBasicContraintsCommon(SecPVCRef pvc, } #endif -static void SecPolicyCheckBasicContraints(SecPVCRef pvc, +static void SecPolicyCheckBasicConstraints(SecPVCRef pvc, CFStringRef key) { //SecPolicyCheckBasicContraintsCommon(pvc, key, false); } @@ -445,25 +385,14 @@ static void SecPolicyCheckBasicContraints(SecPVCRef pvc, static void SecPolicyCheckNonEmptySubject(SecPVCRef pvc, CFStringRef key) { CFIndex ix, count = SecPVCGetCertificateCount(pvc); + SecPolicyRef policy = SecPVCGetPolicy(pvc); + CFTypeRef pvcValue = CFDictionaryGetValue(policy->_options, key); 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; - } - } + if (!SecPolicyCheckCertNonEmptySubject(cert, pvcValue)) { + if (!SecPVCSetResult(pvc, key, ix, kCFBooleanFalse)) + return; + } } } @@ -471,231 +400,6 @@ 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. - - RFC6125: - */ -bool SecDNSMatch(CFStringRef hostname, CFStringRef servername) { - CFStringInlineBuffer hbuf, sbuf; - CFIndex hix, six, tix, - hlength = CFStringGetLength(hostname), - slength = CFStringGetLength(servername); - CFRange hrange = { 0, hlength }, srange = { 0, slength }; - CFStringInitInlineBuffer(hostname, &hbuf, hrange); - CFStringInitInlineBuffer(servername, &sbuf, srange); - bool prevLabel=false; - - for (hix = six = 0; six < slength; ++six) { - UniChar tch, hch, sch = CFStringGetCharacterFromInlineBuffer(&sbuf, six); - if (sch == '*') { - if (prevLabel) { - /* RFC6125: No wildcard after a Previous Label */ - /* INVALID: Means we have something like foo.*. */ - return false; - } - - 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. */ - if (prevLabel==false) { /* RFC6125: Check if *. */ - tix=six+1; - do { /* Loop to end of servername */ - if (tix > slength) - return false; /* Means we have something like *.com */ - tch = CFStringGetCharacterFromInlineBuffer(&sbuf, tix++); - } while (tch != '.'); - if (tix > slength) - return false; /* In case we have *.com. */ - } - - 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 (sch == '.') - prevLabel=true; /* Set if a confirmed previous component */ - } - } - - 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. @@ -714,61 +418,13 @@ static void SecPolicyCheckSSLHostname(SecPVCRef pvc, } 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); - } - } + bool dnsMatch = SecPolicyCheckCertSSLHostname(leaf, hostName); 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): @@ -778,7 +434,6 @@ static void SecPolicyCheckSSLHostname(SecPVCRef pvc, 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. */ @@ -786,20 +441,8 @@ static void SecPolicyCheckEmail(SecPVCRef pvc, CFStringRef key) { } 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) { + if (!SecPolicyCheckCertEmail(leaf, email)) { /* Hostname mismatch or no hostnames found in certificate. */ SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); } @@ -865,13 +508,9 @@ static void SecPolicyCheckIssuerCommonName(SecPVCRef pvc, 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); + if (!SecPolicyCheckCertSubjectCommonName(cert, commonName)) { + SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); + } } /* AUDIT[securityd](done): @@ -889,13 +528,9 @@ static void SecPolicyCheckSubjectCommonName(SecPVCRef pvc, won't help much either. */ return; } - CFArrayRef commonNames = SecCertificateCopyCommonNames(cert); - if (!commonNames || CFArrayGetCount(commonNames) != 1 || - !CFEqual(common_name, CFArrayGetValueAtIndex(commonNames, 0))) { - /* Common Name mismatch. */ + if (!SecPolicyCheckCertSubjectCommonName(cert, common_name)) { SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); - } - CFReleaseSafe(commonNames); + } } /* AUDIT[securityd](done): @@ -913,13 +548,9 @@ static void SecPolicyCheckSubjectCommonNamePrefix(SecPVCRef pvc, 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); + if (!SecPolicyCheckCertSubjectCommonNamePrefix(cert, prefix)) { + SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); + } } /* AUDIT[securityd](done): @@ -937,20 +568,9 @@ static void SecPolicyCheckSubjectCommonNameTEST(SecPVCRef pvc, 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); + if (!SecPolicyCheckCertSubjectCommonNameTEST(cert, common_name)) { + SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); + } } /* AUDIT[securityd](done): @@ -967,9 +587,7 @@ static void SecPolicyCheckNotValidBefore(SecPVCRef pvc, 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 (!SecPolicyCheckCertNotValidBefore(cert, date)) { if (!SecPVCSetResult(pvc, key, 0, kCFBooleanFalse)) return; } @@ -999,32 +617,51 @@ static void SecPolicyCheckChainLength(SecPVCRef pvc, } } -/* 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); +static bool isDigestInPolicy(SecPVCRef pvc, CFStringRef key, CFDataRef digest) { + SecPolicyRef policy = SecPVCGetPolicy(pvc); CFTypeRef value = CFDictionaryGetValue(policy->_options, key); - CFDataRef anchorSHA1 = SecCertificateGetSHA1Digest(cert); bool foundMatch = false; - if (isData(value)) - foundMatch = CFEqual(anchorSHA1, value); + foundMatch = CFEqual(digest, value); else if (isArray(value)) - foundMatch = CFArrayContainsValue((CFArrayRef) value, CFRangeMake(0, CFArrayGetCount((CFArrayRef) value)), anchorSHA1); + 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. */ + we let the evaluation fail (not much help) and assert in debug. */ assert(false); } - if (!foundMatch) - if (!SecPVCSetResult(pvc, kSecPolicyCheckAnchorSHA1, 0, kCFBooleanFalse)) + return foundMatch; +} + +static void SecPolicyCheckAnchorSHA256(SecPVCRef pvc, CFStringRef key) { + CFIndex count = SecPVCGetCertificateCount(pvc); + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, count - 1); + CFDataRef anchorSHA256 = NULL; + anchorSHA256 = SecCertificateCopySHA256Digest(cert); + + if (!isDigestInPolicy(pvc, key, anchorSHA256)) { + SecPVCSetResult(pvc, kSecPolicyCheckAnchorSHA256, count-1, kCFBooleanFalse); + } + + CFReleaseNull(anchorSHA256); + 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); + CFDataRef anchorSHA1 = SecCertificateGetSHA1Digest(cert); + + if (!isDigestInPolicy(pvc, key, anchorSHA1)) + if (!SecPVCSetResult(pvc, kSecPolicyCheckAnchorSHA1, count-1, kCFBooleanFalse)) return; return; @@ -1037,11 +674,8 @@ static void SecPolicyCheckAnchorSHA1(SecPVCRef pvc, */ 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); @@ -1051,21 +685,10 @@ static void SecPolicyCheckIntermediateSPKISHA256(SecPVCRef pvc, 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); + if (!isDigestInPolicy(pvc, key, digest)) { + SecPVCSetResult(pvc, kSecPolicyCheckIntermediateSPKISHA256, 1, kCFBooleanFalse); } - CFReleaseNull(digest); - - if (!foundMatch) { - SecPVCSetResult(pvc, kSecPolicyCheckIntermediateSPKISHA256, 0, kCFBooleanFalse); - } } /* @@ -1076,16 +699,8 @@ 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; - if (CFDictionaryGetValue(value, kSecPolicyAppleAnchorAllowTestRootsOnProduction)) - flags |= kSecAppleTrustAnchorFlagsAllowNonProduction; - } bool foundMatch = SecIsAppleTrustAnchor(cert, flags); @@ -1112,13 +727,10 @@ static void SecPolicyCheckSubjectOrganization(SecPVCRef pvc, won't help much either. */ return; } - CFArrayRef organization = SecCertificateCopyOrganization(cert); - if (!organization || CFArrayGetCount(organization) != 1 || - !CFEqual(org, CFArrayGetValueAtIndex(organization, 0))) { + if (!SecPolicyCheckCertSubjectOrganization(cert, org)) { /* Leaf Subject Organization mismatch. */ SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); } - CFReleaseSafe(organization); } static void SecPolicyCheckSubjectOrganizationalUnit(SecPVCRef pvc, @@ -1132,13 +744,10 @@ static void SecPolicyCheckSubjectOrganizationalUnit(SecPVCRef pvc, 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); + if (!SecPolicyCheckCertSubjectOrganizationalUnit(cert, orgUnit)) { + /* Leaf Subject Organization mismatch. */ + SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); + } } /* AUDIT[securityd](done): @@ -1159,43 +768,8 @@ static void SecPolicyCheckEAPTrustedServerNames(SecPVCRef pvc, 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) { + if (!SecPolicyCheckCertEAPTrustedServerNames(leaf, trustedServerNames)) { /* Hostname mismatch or no hostnames found in certificate. */ SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); } @@ -1262,6 +836,7 @@ static void SecPolicyCheckBlackListedLeaf(SecPVCRef pvc, serial_ptr, sizeof(*UTN_USERFirst_Hardware_Serial))) { SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); + pvc->result = kSecTrustResultFatalTrustFailure; CFReleaseSafe(serial); return; } @@ -1286,6 +861,7 @@ static void SecPolicyCheckBlackListedLeaf(SecPVCRef pvc, if (CFSetContainsValue(blackListedKeys, dgst)) { SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); + pvc->result = kSecTrustResultFatalTrustFailure; } CFRelease(dgst); } @@ -1318,7 +894,7 @@ static void SecPolicyCheckGrayListedLeaf(SecPVCRef pvc, CFStringRef key) CFRelease(grayListedKeys); } } - } +} static void SecPolicyCheckLeafMarkerOid(SecPVCRef pvc, CFStringRef key) { @@ -1326,227 +902,103 @@ static void SecPolicyCheckLeafMarkerOid(SecPVCRef pvc, CFStringRef key) SecPolicyRef policy = SecPVCGetPolicy(pvc); CFTypeRef value = CFDictionaryGetValue(policy->_options, key); - if (value && SecCertificateHasMarkerExtension(cert, value)) - return; - - SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); + if (!SecPolicyCheckCertLeafMarkerOid(cert, value)) { + SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); + } } -static void SecPolicyCheckIntermediateMarkerOid(SecPVCRef pvc, CFStringRef key) +static void SecPolicyCheckLeafMarkerOidWithoutValueCheck(SecPVCRef pvc, CFStringRef key) { - CFIndex ix, count = SecPVCGetCertificateCount(pvc); + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, 0); 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; + if (!SecPolicyCheckCertLeafMarkerOidWithoutValueCheck(cert, value)) { + SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); } - SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); } -/* Returns true if path is on the allow list, false otherwise */ -static bool SecPVCCheckCertificateAllowList(SecPVCRef pvc) +/* + * The value is a dictionary. The dictionary contains keys indicating + * whether the value is for Prod or QA. The values are the same as + * in the options dictionary for SecPolicyCheckLeafMarkerOid. + */ +static void SecPolicyCheckLeafMarkersProdAndQA(SecPVCRef pvc, CFStringRef key) { - 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); + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, 0); + SecPolicyRef policy = SecPVCGetPolicy(pvc); + CFDictionaryRef value = CFDictionaryGetValue(policy->_options, key); + CFTypeRef prodValue = CFDictionaryGetValue(value, kSecPolicyLeafMarkerProd); - //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; + if (!SecPolicyCheckCertLeafMarkerOid(cert, prodValue)) { + bool result = false; + if (!result) { + SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); + } } +} - CFArrayRef allowedCerts = CFDictionaryGetValue(allowList, authKey); - if (!allowedCerts || !CFArrayGetCount(allowedCerts)) { - goto errout; - } +static void SecPolicyCheckIntermediateMarkerOid(SecPVCRef pvc, CFStringRef key) +{ + CFIndex ix, count = SecPVCGetCertificateCount(pvc); + SecPolicyRef policy = SecPVCGetPolicy(pvc); + CFTypeRef value = CFDictionaryGetValue(policy->_options, key); - //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++) { + for (ix = 1; ix < count - 1; 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); + if (SecCertificateHasMarkerExtension(cert, value)) + return; } - -errout: - CFRelease(authKey); - CFReleaseNull(otapkiRef); - CFReleaseNull(allowList); - return result; + SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); } +static void SecPolicyCheckIntermediateEKU(SecPVCRef pvc, CFStringRef key) +{ + CFIndex ix, count = SecPVCGetCertificateCount(pvc); + SecPolicyRef policy = SecPVCGetPolicy(pvc); + CFTypeRef peku = CFDictionaryGetValue(policy->_options, key); -/**************************************************************************** - *********************** 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; + for (ix = 1; ix < count - 1; ix++) { + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, ix); + if (!SecPolicyCheckCertExtendedKeyUsage(cert, peku)) { + SecPVCSetResult(pvc, key, ix, kCFBooleanFalse); + } + } } -/* 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; -} +static void SecPolicyCheckIntermediateOrganization(SecPVCRef pvc, CFStringRef key) +{ + CFIndex ix, count = SecPVCGetCertificateCount(pvc); + SecPolicyRef policy = SecPVCGetPolicy(pvc); + CFTypeRef organization = CFDictionaryGetValue(policy->_options, key); -/* 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; + for (ix = 1; ix < count - 1; ix++) { + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, ix); + if (!SecPolicyCheckCertSubjectOrganization(cert, organization)) { + SecPVCSetResult(pvc, key, ix, kCFBooleanFalse); } } - 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; +static void SecPolicyCheckIntermediateCountry(SecPVCRef pvc, CFStringRef key) +{ + CFIndex ix, count = SecPVCGetCertificateCount(pvc); + SecPolicyRef policy = SecPVCGetPolicy(pvc); + CFTypeRef country = CFDictionaryGetValue(policy->_options, key); + + for (ix = 1; ix < count - 1; ix++) { + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, ix); + if (!SecPolicyCheckCertSubjectCountry(cert, country)) { + SecPVCSetResult(pvc, key, ix, kCFBooleanFalse); } } - 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 +/**************************************************************************** + *********************** New rfc5280 Chain Validation *********************** + ****************************************************************************/ -#define POLICY_MAPPING 0 +#define POLICY_MAPPING 1 #define POLICY_SUBTREES 1 /* rfc5280 basic cert processing. */ @@ -1555,44 +1007,66 @@ static void SecPolicyCheckBasicCertificateProcessing(SecPVCRef pvc, /* Inputs */ //cert_path_t path; CFIndex count = SecPVCGetCertificateCount(pvc); + SecCertificatePathVCRef path = SecPathBuilderGetPath(pvc->builder); /* 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); + + bool is_anchored = SecPathBuilderIsAnchored(pvc->builder); + bool is_anchor_trusted = false; if (is_anchored) { - /* If the anchor is trusted we don't procces the last cert in the + CFArrayRef constraints = SecCertificatePathVCGetUsageConstraintsAtIndex(path, n - 1); + if (CFArrayGetCount(constraints) == 0) { + /* Given that the path builder has already indicated the last cert in this chain has + * trust set on it, empty constraints means trusted. */ + is_anchor_trusted = true; + } else { + /* Determine whether constraints say to trust this cert for this PVC. */ + SecTrustSettingsResult tsResult = SecPVCGetTrustSettingsResult(pvc, SecCertificatePathVCGetCertificateAtIndex(path, n - 1), + constraints); + if (tsResult == kSecTrustSettingsResultTrustRoot || tsResult == kSecTrustSettingsResultTrustAsRoot) { + is_anchor_trusted = true; + } + } + } + + if (is_anchor_trusted) { + /* If the anchor is trusted we don't process 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)) { + /* trust may be restored for a path with an untrusted root that matches the allow list. + (isAllowlisted is set by revocation check, which is performed prior to path checks) */ + if (!SecCertificatePathVCIsAllowlisted(path)) { /* Add a detail for the root not being trusted. */ - if (SecPVCSetResultForced(pvc, kSecPolicyCheckAnchorTrusted, - n - 1, kCFBooleanFalse, true)) + 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); + require_action_quiet(permitted_subtrees != NULL, errOut, + SecPVCSetResultForced(pvc, key, 0, kCFBooleanFalse, true)); + require_action_quiet(excluded_subtrees != NULL, errOut, + SecPVCSetResultForced(pvc, key, 0, kCFBooleanFalse, true)); #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 (!SecCertificatePathVCVerifyPolicyTree(path, is_anchor_trusted)) { + if (!SecPVCSetResultForced(pvc, key, 0, kCFBooleanFalse, true)) { + goto errOut; + } + } #if 0 /* Path builder ensures we only get cert chains with proper issuer @@ -1606,7 +1080,7 @@ static void SecPolicyCheckBasicCertificateProcessing(SecPVCRef pvc, for (i = 1; i <= n; ++i) { /* Process Cert */ cert = SecPVCGetCertificateAtIndex(pvc, n - i); - bool is_self_issued = SecPVCIsCertificateAtIndexSelfSigned(pvc, n - i); + bool is_self_issued = SecCertificatePathVCIsCertificateAtIndexSelfIssued(SecPathBuilderGetPath(pvc->builder), n - i); /* (a) Verify the basic certificate information. */ /* @@@ Ensure that cert was signed with working_public_key_algorithm @@ -1615,20 +1089,16 @@ static void SecPolicyCheckBasicCertificateProcessing(SecPVCRef pvc, /* 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; + if (!SecPVCSetResult(pvc, fail_key, n - i, kCFBooleanFalse)) { + goto errOut; + } } - if (SecCertificateIsWeak(cert)) { + if (SecCertificateIsWeakKey(cert)) { CFStringRef fail_key = i == n ? kSecPolicyCheckWeakLeaf : kSecPolicyCheckWeakIntermediates; - 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); + if (!SecPVCSetResult(pvc, fail_key, n - i, kCFBooleanFalse)) { + goto errOut; + } + pvc->result = kSecTrustResultFatalTrustFailure; } #endif /* @@@ cert.issuer == working_issuer_name. */ @@ -1640,115 +1110,28 @@ static void SecPolicyCheckBasicCertificateProcessing(SecPVCRef pvc, /* 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, false)) || found) { - if(!SecPVCSetResultForced(pvc, key, n - i, kCFBooleanFalse, true)) return; + secnotice("policy", "name in excluded subtrees"); + if(!SecPVCSetResultForced(pvc, key, n - i, kCFBooleanFalse, true)) { goto errOut; } } } /* Verify certificate Subject Name and SubjectAltNames are within the permitted_subtrees */ if(permitted_subtrees && CFArrayGetCount(permitted_subtrees)) { if ((errSecSuccess != SecNameContraintsMatchSubtrees(cert, permitted_subtrees, &found, true)) || !found) { - if(!SecPVCSetResultForced(pvc, key, n - i, kCFBooleanFalse, true)) return; + secnotice("policy", "name not in permitted subtrees"); + if(!SecPVCSetResultForced(pvc, key, n - i, kCFBooleanFalse, true)) { goto errOut; } } } } #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; - } + /* (d) (e) (f) handled by SecCertificatePathVCVerifyPolicyTree */ + /* 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) */ + /* (a) (b) Done by SecCertificatePathVCVerifyPolicyTree */ + /* (c)(d)(e)(f) Done by SecPathBuilderGetNext and SecCertificatePathVCVerify */ //working_issuer_name = SecCertificateGetNormalizedSubjectContent(cert); //working_public_key = SecCertificateCopyPublicKey(cert); //working_public_key_parameters = SecCertificateCopyPublicKeyParameters(cert); @@ -1769,42 +1152,18 @@ static void SecPolicyCheckBasicCertificateProcessing(SecPVCRef pvc, 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; - } + /* (h), (i), (j) done by SecCertificatePathVCVerifyPolicyTree */ + /* (k) */ const SecCEBasicConstraints *bc = SecCertificateGetBasicConstraints(cert); -#if 0 /* Checked in chain builder pre signature verify already. */ +#if 0 /* Checked in chain builder pre signature verify already. SecPVCParentCertificateChecks */ if (!bc || !bc->isCA) { /* Basic constraints not present or not marked as isCA, illegal. */ - if (!SecPVCSetResult(pvc, kSecPolicyCheckBasicContraints, - n - i, kCFBooleanFalse)) - return; + if (!SecPVCSetResult(pvc, kSecPolicyCheckBasicConstraints, + n - i, kCFBooleanFalse)) { + goto errOut; + } } #endif /* (l) */ @@ -1813,9 +1172,10 @@ static void SecPolicyCheckBasicCertificateProcessing(SecPVCRef pvc, max_path_length--; } else { /* max_path_len exceeded, illegal. */ - if (!SecPVCSetResult(pvc, kSecPolicyCheckBasicContraints, - n - i, kCFBooleanFalse)) - return; + if (!SecPVCSetResult(pvc, kSecPolicyCheckBasicConstraints, + n - i, kCFBooleanFalse)) { + goto errOut; + } } } /* (m) */ @@ -1823,36 +1183,27 @@ static void SecPolicyCheckBasicCertificateProcessing(SecPVCRef pvc, && bc->pathLenConstraint < max_path_length) { max_path_length = bc->pathLenConstraint; } -#if 0 /* Checked in chain builder pre signature verify already. */ +#if 0 /* Checked in chain builder pre signature verify already. SecPVCParentCertificateChecks */ /* (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; + n - i, kCFBooleanFalse, true)) { + goto errOut; + } } #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; + n - i, kCFBooleanFalse)) { + goto errOut; + } } } /* 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; - } - } + /* (a) (b) done by SecCertificatePathVCVerifyPolicyTree */ /* (c) */ //working_public_key = SecCertificateCopyPublicKey(cert); /* (d) */ @@ -1865,28 +1216,13 @@ working_public_key_algorithm are different, set the working_public_key_parameter 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; + 0, kCFBooleanFalse)) { + goto errOut; + } } + /* (g) done by SecCertificatePathVCVerifyPolicyTree */ +errOut: CFReleaseNull(permitted_subtrees); CFReleaseNull(excluded_subtrees); } @@ -1907,6 +1243,19 @@ static void SecPolicyCheckEV(SecPVCRef pvc, CFIndex ix, count = SecPVCGetCertificateCount(pvc); policy_set_t valid_policies = NULL; + /* 6.1.7. Key Usage Purposes */ + if (count) { + CFAbsoluteTime jul2016 = 489024000; + SecCertificateRef leaf = SecPVCGetCertificateAtIndex(pvc, 0); + if (SecCertificateNotValidBefore(leaf) > jul2016 && count < 3) { + /* Root CAs may not sign subscriber certificates after 30 June 2016. */ + if (SecPVCSetResultForced(pvc, key, + 0, kCFBooleanFalse, true)) { + return; + } + } + } + for (ix = 0; ix < count; ++ix) { SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, ix); policy_set_t policies = policies_for_cert(cert); @@ -1921,7 +1270,7 @@ static void SecPolicyCheckEV(SecPVCRef pvc, } else if (ix < count - 1) { /* Subordinate CA */ if (!SecPolicySubordinateCACertificateCouldBeEV(cert)) { - secdebug("ev", "subordinate certificate is not ev"); + secnotice("ev", "subordinate certificate is not ev"); if (SecPVCSetResultForced(pvc, key, ix, kCFBooleanFalse, true)) { policy_set_free(valid_policies); @@ -1933,7 +1282,7 @@ static void SecPolicyCheckEV(SecPVCRef pvc, } else { /* Root CA */ if (!SecPolicyRootCACertificateIsEV(cert, valid_policies)) { - secdebug("ev", "anchor certificate is not ev"); + secnotice("ev", "anchor certificate is not ev"); if (SecPVCSetResultForced(pvc, key, ix, kCFBooleanFalse, true)) { policy_set_free(valid_policies); @@ -1944,7 +1293,7 @@ static void SecPolicyCheckEV(SecPVCRef pvc, } policy_set_free(policies); if (!valid_policies) { - secdebug("ev", "valid_policies set is empty: chain not ev"); + secnotice("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, @@ -2123,20 +1472,46 @@ out: return data; } -/* If the 'sct' is valid, return the operator ID of the log that signed this sct. +static +CFAbsoluteTime TimestampToCFAbsoluteTime(uint64_t ts) +{ + return (ts / 1000) - kCFAbsoluteTimeIntervalSince1970; +} + +static +uint64_t TimestampFromCFAbsoluteTime(CFAbsoluteTime at) +{ + return (uint64_t)(at + kCFAbsoluteTimeIntervalSince1970) * 1000; +} + + + + +/* + If the 'sct' is valid, add it to the validatingLogs dictionary. + + Inputs: + - validatingLogs: mutable dictionary to which to add the log that validate this SCT. + - sct: the SCT date + - entry_type: 0 for x509 cert, 1 for precert. + - entry: the cert or precert data. + - vt: verification time timestamp (as used in SCTs: ms since 1970 Epoch) + - trustedLog: Dictionary contain the Trusted Logs. 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 entry_type = 0, the log must be currently qualified. + - If entry_type = 1, the log may be expired. - 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 valid, it's added to the validatinLogs dictionary using the log dictionary as the key, and the timestamp as value. + If an entry for the same log already existing in the dictionary, the entry is replaced only if the timestamp of this SCT is earlier. - 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) + + +static CFDictionaryRef getSCTValidatingLog(CFDataRef sct, int entry_type, CFDataRef entry, uint64_t vt, CFArrayRef trustedLogs, CFAbsoluteTime *sct_at) { uint8_t version; const uint8_t *logID; @@ -2148,15 +1523,15 @@ static CFStringRef get_valid_sct_operator(CFDataRef sct, int entry_type, CFDataR 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; + CFDataRef logIDData = NULL; + CFDictionaryRef result = 0; const uint8_t *p = CFDataGetBytePtr(sct); size_t len = CFDataGetLength(sct); - uint64_t vt =(uint64_t)( verifyTime + kCFAbsoluteTimeIntervalSince1970) * 1000; require(len>=43, out); @@ -2203,7 +1578,7 @@ static CFStringRef get_valid_sct_operator(CFDataRef sct, int entry_type, CFDataR q = SSLEncodeUint16(q, extensionsLen); memcpy(q, extensionsData, extensionsLen); - CFDataRef logIDData = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, logID, 32, kCFAllocatorNull); + logIDData = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, logID, 32, kCFAllocatorNull); CFDictionaryRef logData = CFArrayGetValueMatching(trustedLogs, ^bool(const void *dict) { const void *key_data; @@ -2215,21 +1590,11 @@ static CFStringRef get_valid_sct_operator(CFDataRef sct, int entry_type, CFDataR CFReleaseSafe(valueID); return result; }); - CFReleaseSafe(logIDData); 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; + if(entry_type==0) { + // For external SCTs, only keep SCTs from currently valid logs. + require(!CFDictionaryContainsKey(logData, CFSTR("expiry")), out); } CFDataRef logKeyData = CFDictionaryGetValue(logData, CFSTR("key")); @@ -2245,17 +1610,31 @@ static CFStringRef get_valid_sct_operator(CFDataRef sct, int entry_type, CFDataR algId.parameters.Length = 0; if(SecKeyDigestAndVerify(pubKey, &algId, signed_data, signed_data_len, signatureData, signatureLen)==0) { - result = CFDictionaryGetValue(logData, CFSTR("operator")); + *sct_at = TimestampToCFAbsoluteTime(timestamp); + result = logData; } else { secerror("SCT signature failed (log=%@)\n", logData); } out: + CFReleaseSafe(logIDData); CFReleaseSafe(pubKey); free(signed_data); return result; } + +static void addValidatingLog(CFMutableDictionaryRef validatingLogs, CFDictionaryRef log, CFAbsoluteTime sct_at) +{ + CFDateRef validated_time = CFDictionaryGetValue(validatingLogs, log); + + if(validated_time==NULL || (sct_at < CFDateGetAbsoluteTime(validated_time))) { + CFDateRef sct_time = CFDateCreate(kCFAllocatorDefault, sct_at); + CFDictionarySetValue(validatingLogs, log, sct_time); + CFReleaseSafe(sct_time); + } +} + static CFArrayRef copy_ocsp_scts(SecPVCRef pvc) { CFMutableArrayRef SCTs = NULL; @@ -2317,91 +1696,131 @@ static void SecPolicyCheckCT(SecPVCRef pvc, CFStringRef key) 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); + // This eventually contain list of logs who validated the SCT. + CFMutableDictionaryRef currentLogsValidatingScts = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFMutableDictionaryRef logsValidatingEmbeddedScts = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - __block bool atLeastOneValidAtVerifyTime = false; - __block int lifetime; // in Months + uint64_t vt = TimestampFromCFAbsoluteTime(SecPVCGetVerifyTime(pvc)); - require(operatorsValidatingEmbeddedScts, out); - require(operatorsValidatingExternalScts, out); + __block bool at_least_one_currently_valid_external = 0; + __block bool at_least_one_currently_valid_embedded = 0; + + require(logsValidatingEmbeddedScts, out); + require(currentLogsValidatingScts, out); if(trustedLogs) { // Don't bother trying to validate SCTs if we don't have any trusted logs. if(embeddedScts && precertEntry) { // Don't bother if we could not get the precert. 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; + CFAbsoluteTime sct_at; + CFDictionaryRef log = getSCTValidatingLog(value, 1, precertEntry, vt, trustedLogs, &sct_at); + if(log) { + addValidatingLog(logsValidatingEmbeddedScts, log, sct_at); + if(!CFDictionaryContainsKey(log, CFSTR("expiry"))) { + addValidatingLog(currentLogsValidatingScts, log, sct_at); + at_least_one_currently_valid_embedded = true; + } + } }); } if(builderScts && x509Entry) { // Don't bother if we could not get the cert. 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; + CFAbsoluteTime sct_at; + CFDictionaryRef log = getSCTValidatingLog(value, 0, x509Entry, vt, trustedLogs, &sct_at); + if(log) { + addValidatingLog(currentLogsValidatingScts, log, sct_at); + at_least_one_currently_valid_external = true; + } }); } if(ocspScts && x509Entry) { 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; + CFAbsoluteTime sct_at; + CFDictionaryRef log = getSCTValidatingLog(value, 0, x509Entry, vt, trustedLogs, &sct_at); + if(log) { + addValidatingLog(currentLogsValidatingScts, log, sct_at); + at_least_one_currently_valid_external = 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. + /* We now have 2 sets of logs that validated those SCTS, count them and make a final decision. - 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 + Current Policy: + is_ct = (A1 AND A2) OR (B1 AND B2). - */ + A1: embedded SCTs from 2+ to 5+ logs valid at issuance time + A2: At least one embedded SCT from a currently valid log. - SecCFCalendarDoWithZuluCalendar(^(CFCalendarRef zuluCalendar) { - int _lifetime; - CFCalendarGetComponentDifference(zuluCalendar, - SecCertificateNotValidBefore(leafCert), - SecCertificateNotValidAfter(leafCert), - 0, "M", &_lifetime); - lifetime = _lifetime; - }); + B1: SCTs from 2 currently valid logs (from any source) + B2: At least 1 external SCT from a currently valid log. - CFIndex requiredEmbeddedSctsCount; + */ - if (lifetime < 15) { - requiredEmbeddedSctsCount = 2; - } else if (lifetime <= 27) { - requiredEmbeddedSctsCount = 3; - } else if (lifetime <= 39) { - requiredEmbeddedSctsCount = 4; - } else { - requiredEmbeddedSctsCount = 5; - } + SecCertificatePathVCSetIsCT(SecPathBuilderGetPath(pvc->builder), false); + + if(at_least_one_currently_valid_external && CFDictionaryGetCount(currentLogsValidatingScts)>=2) { + SecCertificatePathVCSetIsCT(SecPathBuilderGetPath(pvc->builder), true); + } else if(at_least_one_currently_valid_embedded) { + __block CFAbsoluteTime issuanceTime = SecPVCGetVerifyTime(pvc); + __block int lifetime; // in Months + __block unsigned once_or_current_qualified_embedded = 0; + + /* Calculate issuance time base on timestamp of SCTs from current logs */ + CFDictionaryForEach(currentLogsValidatingScts, ^(const void *key, const void *value) { + CFDictionaryRef log = key; + if(!CFDictionaryContainsKey(log, CFSTR("expiry"))) { + // Log is still qualified + CFDateRef ts = (CFDateRef) value; + CFAbsoluteTime timestamp = CFDateGetAbsoluteTime(ts); + if(timestamp < issuanceTime) { + issuanceTime = timestamp; + } + } + }); + + /* Count Logs */ + CFDictionaryForEach(logsValidatingEmbeddedScts, ^(const void *key, const void *value) { + CFDictionaryRef log = key; + CFDateRef ts = value; + CFDateRef expiry = CFDictionaryGetValue(log, CFSTR("expiry")); + if(expiry == NULL || CFDateCompare(ts, expiry, NULL) == kCFCompareLessThan) { + once_or_current_qualified_embedded++; + } + }); + + SecCFCalendarDoWithZuluCalendar(^(CFCalendarRef zuluCalendar) { + int _lifetime; + CFCalendarGetComponentDifference(zuluCalendar, + SecCertificateNotValidBefore(leafCert), + SecCertificateNotValidAfter(leafCert), + 0, "M", &_lifetime); + lifetime = _lifetime; + }); + + unsigned 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; + if(once_or_current_qualified_embedded >= requiredEmbeddedSctsCount){ + SecCertificatePathVCSetIsCT(SecPathBuilderGetPath(pvc->builder), true); + } + } out: - - CFReleaseSafe(operatorsValidatingEmbeddedScts); - CFReleaseSafe(operatorsValidatingExternalScts); + CFReleaseSafe(logsValidatingEmbeddedScts); + CFReleaseSafe(currentLogsValidatingScts); CFReleaseSafe(builderScts); CFReleaseSafe(embeddedScts); CFReleaseSafe(ocspScts); @@ -2410,47 +1829,75 @@ out: CFReleaseSafe(x509Entry); } +static bool checkPolicyOidData(SecPVCRef pvc, CFDataRef oid) { + CFIndex ix, count = SecPVCGetCertificateCount(pvc); + DERItem key_value; + key_value.data = (DERByte *)CFDataGetBytePtr(oid); + key_value.length = (DERSize)CFDataGetLength(oid); + + 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)) { + policy_set_free(policies); + return true; + } + policy_set_free(policies); + } + return false; +} + 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; + bool result = false; 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; - } - } + result = checkPolicyOidData(pvc, value); + } else if (CFGetTypeID(value) == CFStringGetTypeID()) { + CFDataRef dataOid = SecCertificateCreateOidDataFromString(NULL, value); + if (dataOid) { + result = checkPolicyOidData(pvc, dataOid); + CFRelease(dataOid); + } + } + if(!result) { SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); - } + } } static void SecPolicyCheckRevocation(SecPVCRef pvc, CFStringRef key) { - SecPVCSetCheckRevocation(pvc); + SecPolicyRef policy = SecPVCGetPolicy(pvc); + CFTypeRef value = CFDictionaryGetValue(policy->_options, key); + if (isString(value)) { + SecPathBuilderSetRevocationMethod(pvc->builder, value); + } } static void SecPolicyCheckRevocationResponseRequired(SecPVCRef pvc, CFStringRef key) { - SecPVCSetCheckRevocationResponseRequired(pvc); + pvc->require_revocation_response = true; + secdebug("policy", "revocation response required"); +} + +static void SecPolicyCheckRevocationOnline(SecPVCRef pvc, CFStringRef key) { + SecPathBuilderSetCheckRevocationOnline(pvc->builder); } static void SecPolicyCheckNoNetworkAccess(SecPVCRef pvc, - CFStringRef key) { - SecPathBuilderSetCanAccessNetwork(pvc->builder, false); + CFStringRef key) { + SecPolicyRef policy = SecPVCGetPolicy(pvc); + CFTypeRef value = CFDictionaryGetValue(policy->_options, key); + if (value == kCFBooleanTrue) { + SecPathBuilderSetCanAccessNetwork(pvc->builder, false); + } else { + SecPathBuilderSetCanAccessNetwork(pvc->builder, true); + } } static void SecPolicyCheckWeakIntermediates(SecPVCRef pvc, @@ -2458,10 +1905,11 @@ static void SecPolicyCheckWeakIntermediates(SecPVCRef pvc, CFIndex ix, count = SecPVCGetCertificateCount(pvc); for (ix = 1; ix < count - 1; ++ix) { SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, ix); - if (cert && SecCertificateIsWeak(cert)) { + if (cert && SecCertificateIsWeakKey(cert)) { /* Intermediate certificate has a weak key. */ if (!SecPVCSetResult(pvc, key, ix, kCFBooleanFalse)) return; + pvc->result = kSecTrustResultFatalTrustFailure; } } } @@ -2469,10 +1917,11 @@ static void SecPolicyCheckWeakIntermediates(SecPVCRef pvc, static void SecPolicyCheckWeakLeaf(SecPVCRef pvc, CFStringRef key) { SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, 0); - if (cert && SecCertificateIsWeak(cert)) { + if (cert && SecCertificateIsWeakKey(cert)) { /* Leaf certificate has a weak key. */ if (!SecPVCSetResult(pvc, key, 0, kCFBooleanFalse)) return; + pvc->result = kSecTrustResultFatalTrustFailure; } } @@ -2481,480 +1930,178 @@ static void SecPolicyCheckWeakRoot(SecPVCRef pvc, CFIndex ix, count = SecPVCGetCertificateCount(pvc); ix = count - 1; SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, ix); - if (cert && SecCertificateIsWeak(cert)) { + if (cert && SecCertificateIsWeakKey(cert)) { /* Root certificate has a weak key. */ if (!SecPVCSetResult(pvc, key, ix, kCFBooleanFalse)) return; + pvc->result = kSecTrustResultFatalTrustFailure; } } -// 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; +static void SecPolicyCheckKeySize(SecPVCRef pvc, CFStringRef key) { + CFIndex ix, count = SecPVCGetCertificateCount(pvc); + SecPolicyRef policy = SecPVCGetPolicy(pvc); + CFDictionaryRef keySizes = CFDictionaryGetValue(policy->_options, key); + for (ix = 0; ix < count; ++ix) { + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, ix); + if (!SecCertificateIsAtLeastMinKeySize(cert, keySizes)) { + if (!SecPVCSetResult(pvc, key, ix, kCFBooleanFalse)) + return; + } + } +} - /* 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; - } +static void SecPolicyCheckSignatureHashAlgorithms(SecPVCRef pvc, + CFStringRef key) { + CFIndex ix, count = SecPVCGetCertificateCount(pvc); + SecPolicyRef policy = SecPVCGetPolicy(pvc); + CFSetRef disallowedHashAlgorithms = CFDictionaryGetValue(policy->_options, key); + for (ix = 0; ix < count; ++ix) { + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, ix); + if (!SecPolicyCheckCertSignatureHashAlgorithms(cert, disallowedHashAlgorithms)) { + if (!SecPVCSetResult(pvc, key, ix, kCFBooleanFalse)) + return; } } - 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; +static bool leaf_is_on_weak_hash_whitelist(SecPVCRef pvc) { + SecCertificateRef leaf = SecPVCGetCertificateAtIndex(pvc, 0); + require_quiet(leaf, out); - 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; - } - } + /* And now a special snowflake from our tests */ -errOut: - rvc->done = true; - return true; -} + /* subject:/C=AU/ST=NSW/L=St Leonards/O=VODAFONE HUTCHISON AUSTRALIA PTY LIMITED/OU=Technology Shared Services/CN=mybill.vodafone.com.au */ + /* issuer :/C=UK/O=Vodafone Group/CN=Vodafone (Corporate Services 2009) */ + /* Not After : May 26 09:37:50 2017 GMT */ + static const uint8_t vodafone[] = { + 0xde, 0x77, 0x63, 0x97, 0x79, 0x47, 0xee, 0x6e, 0xc1, 0x3a, + 0x7b, 0x3b, 0xad, 0x43, 0x88, 0xa9, 0x66, 0x59, 0xa8, 0x18 + }; -/* 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); - } + /* subject:/C=US/ST=Kansas/L=Overland Park/O=Sprint/CN=oma.ssprov.sprint.com */ + /* issuer :/C=US/O=Entrust, Inc./OU=www.entrust.net/rpa is incorporated by reference/OU=(c) 2009 Entrust, Inc./CN=Entrust Certification Authority - L1C */ + /* Not After : Aug 16 05:04:29 2017 GMT */ + static const uint8_t sprint[] = { + 0xa3, 0x18, 0x70, 0x4f, 0xf7, 0xbf, 0xfb, 0x2b, 0xe2, 0x64, + 0x3a, 0x2d, 0x2b, 0xb8, 0x10, 0x5f, 0x77, 0xd5, 0x01, 0xab + }; - 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; + CFDataRef leafFingerprint = SecCertificateGetSHA1Digest(leaf); + require_quiet(leafFingerprint, out); + const unsigned int len = 20; + const uint8_t *dp = CFDataGetBytePtr(leafFingerprint); + if (dp && (!memcmp(vodafone, dp, len) || !memcmp(sprint,dp,len))) { + return true; } -#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; +out: + return false; } -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); +static bool SecPVCKeyIsConstraintPolicyOption(SecPVCRef pvc, CFStringRef key); - 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); -} +static void SecPolicyCheckSystemTrustedWeakHash(SecPVCRef pvc, + CFStringRef key) { + CFIndex ix, count = SecPVCGetCertificateCount(pvc); -/* 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); + Boolean keyInPolicy = false; + CFArrayRef policies = pvc->policies; + CFIndex policyIX, policyCount = CFArrayGetCount(policies); + for (policyIX = 0; policyIX < policyCount; ++policyIX) { + SecPolicyRef policy = (SecPolicyRef)CFArrayGetValueAtIndex(policies, policyIX); + if (policy && CFDictionaryContainsKey(policy->_options, key)) { + keyInPolicy = true; } } - 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 + /* We only enforce this check when *both* of the following are true: + * 1. One of the certs in the path has this usage constraint, and + * 2. One of the policies in the PVC has this key + * (As compared to normal policy options which require only one to be true..) */ + require_quiet(SecPVCKeyIsConstraintPolicyOption(pvc, key) && + keyInPolicy, out); - if (!rvc->done) { - /* Clear the data for the next response. */ - asynchttp_free(http); - SecRVCFetchNext(rvc); + /* Ignore the anchor if it's trusted */ + if (SecPathBuilderIsAnchored(pvc->builder)) { + count--; } - - if (rvc->done) { - SecRVCUpdatePVC(rvc); - SecRVCDelete(rvc); - if (!--pvc->asyncJobCount) { - SecPathBuilderStep(pvc->builder); + for (ix = 0; ix < count; ++ix) { + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, ix); + if (SecCertificateIsWeakHash(cert)) { + if (!leaf_is_on_weak_hash_whitelist(pvc)) { + if (!SecPVCSetResult(pvc, key, ix, kCFBooleanFalse)) { + return; + } + } } } +out: + return; } -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 void SecPolicyCheckSystemTrustedWeakKey(SecPVCRef pvc, + CFStringRef key) { + CFIndex ix, count = SecPVCGetCertificateCount(pvc); -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; + Boolean keyInPolicy = false; + CFArrayRef policies = pvc->policies; + CFIndex policyIX, policyCount = CFArrayGetCount(policies); + for (policyIX = 0; policyIX < policyCount; ++policyIX) { + SecPolicyRef policy = (SecPolicyRef)CFArrayGetValueAtIndex(policies, policyIX); + if (policy && CFDictionaryContainsKey(policy->_options, key)) { + keyInPolicy = true; + } } - 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 + /* We only enforce this check when *both* of the following are true: + * 1. One of the certs in the path has this usage constraint, and + * 2. One of the policies in the PVC has this key + * (As compared to normal policy options which require only one to be true..) */ + require_quiet(SecPVCKeyIsConstraintPolicyOption(pvc, key) && + keyInPolicy, out); - if (pvc->rvcs) { - /* We have done revocation checking already, we're done. */ - secdebug("ocsp", "Not rechecking revocation"); - return completed; + /* Ignore the anchor if it's trusted */ + if (SecPathBuilderIsAnchored(pvc->builder)) { + count--; } - - /* 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. */ + for (ix = 0; ix < count; ++ix) { + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, ix); + if (!SecCertificateIsStrongKey(cert)) { + if (!SecPVCSetResult(pvc, key, ix, kCFBooleanFalse)) { + return; } } - } -#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; + } /* Cert loop */ +out: + return; +} - 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); +static void SecPolicyCheckPinningRequired(SecPVCRef pvc, CFStringRef key) { + /* Pinning is disabled on the system, skip. */ + if (SecIsInternalRelease()) { + if (CFPreferencesGetAppBooleanValue(CFSTR("AppleServerAuthenticationNoPinning"), + CFSTR("com.apple.security"), NULL)) { + return; } + } - /* Then check the cached response */ - secdebug("ocsp", "Checking cached responses for cert %ld", certIX); - SecRVCConsumeOCSPResponse(rvc, SecOCSPCacheCopyMatching(rvc->ocspRequest, NULL), NULL_TIME, false); - - /* If the cert is EV or if revocation checking was explicitly enabled, attempt to fire off an - async http request for this cert's revocation status, unless we already successfully checked - the revocation status of this cert based on the cache or stapled responses, */ - bool allow_fetch = SecPathBuilderCanAccessNetwork(pvc->builder) && (pvc->is_ev || pvc->check_revocation); - bool fetch_done = true; - if (rvc->done || !allow_fetch || - (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; + CFArrayRef policies = pvc->policies; + CFIndex policyIX, policyCount = CFArrayGetCount(policies); + for (policyIX = 0; policyIX < policyCount; ++policyIX) { + SecPolicyRef policy = (SecPolicyRef)CFArrayGetValueAtIndex(policies, policyIX); + CFStringRef policyName = SecPolicyGetName(policy); + if (CFEqualSafe(policyName, CFSTR("sslServer"))) { + /* policy required pinning, but we didn't use a pinning policy */ + if (!SecPVCSetResult(pvc, key, 0, kCFBooleanFalse)) { + return; + } } } - - /* 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) { +void SecPolicyServerInitialize(void) { gSecPolicyLeafCallbacks = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, NULL); gSecPolicyPathCallbacks = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, @@ -2976,9 +2123,9 @@ void SecPolicyServerInitalize(void) { kSecPolicyCheckExtendedKeyUsage, SecPolicyCheckExtendedKeyUsage); CFDictionaryAddValue(gSecPolicyLeafCallbacks, - kSecPolicyCheckBasicContraints, - SecPolicyCheckBasicContraints); - CFDictionaryAddValue(gSecPolicyLeafCallbacks, + kSecPolicyCheckBasicConstraints, + SecPolicyCheckBasicConstraints); + CFDictionaryAddValue(gSecPolicyPathCallbacks, kSecPolicyCheckNonEmptySubject, SecPolicyCheckNonEmptySubject); CFDictionaryAddValue(gSecPolicyLeafCallbacks, @@ -3017,9 +2164,9 @@ void SecPolicyServerInitalize(void) { CFDictionaryAddValue(gSecPolicyPathCallbacks, kSecPolicyCheckAnchorSHA1, SecPolicyCheckAnchorSHA1); - CFDictionaryAddValue(gSecPolicyPathCallbacks, - kSecPolicyCheckIntermediateSPKISHA256, - SecPolicyCheckIntermediateSPKISHA256); + CFDictionaryAddValue(gSecPolicyPathCallbacks, + kSecPolicyCheckAnchorSHA256, + SecPolicyCheckAnchorSHA256); CFDictionaryAddValue(gSecPolicyPathCallbacks, kSecPolicyCheckAnchorApple, SecPolicyCheckAnchorApple); @@ -3041,6 +2188,9 @@ void SecPolicyServerInitalize(void) { CFDictionaryAddValue(gSecPolicyLeafCallbacks, kSecPolicyCheckRevocationResponseRequired, SecPolicyCheckRevocationResponseRequired); + CFDictionaryAddValue(gSecPolicyLeafCallbacks, + kSecPolicyCheckRevocationOnline, + SecPolicyCheckRevocationOnline); CFDictionaryAddValue(gSecPolicyLeafCallbacks, kSecPolicyCheckNoNetworkAccess, SecPolicyCheckNoNetworkAccess); @@ -3053,10 +2203,22 @@ void SecPolicyServerInitalize(void) { CFDictionaryAddValue(gSecPolicyLeafCallbacks, kSecPolicyCheckLeafMarkerOid, SecPolicyCheckLeafMarkerOid); + CFDictionaryAddValue(gSecPolicyLeafCallbacks, + kSecPolicyCheckLeafMarkerOidWithoutValueCheck, + SecPolicyCheckLeafMarkerOidWithoutValueCheck); + CFDictionaryAddValue(gSecPolicyLeafCallbacks, + kSecPolicyCheckLeafMarkersProdAndQA, + SecPolicyCheckLeafMarkersProdAndQA); + CFDictionaryAddValue(gSecPolicyPathCallbacks, + kSecPolicyCheckIntermediateSPKISHA256, + SecPolicyCheckIntermediateSPKISHA256); + CFDictionaryAddValue(gSecPolicyPathCallbacks, + kSecPolicyCheckIntermediateEKU, + SecPolicyCheckIntermediateEKU); CFDictionaryAddValue(gSecPolicyPathCallbacks, kSecPolicyCheckIntermediateMarkerOid, SecPolicyCheckIntermediateMarkerOid); - CFDictionaryAddValue(gSecPolicyLeafCallbacks, + CFDictionaryAddValue(gSecPolicyPathCallbacks, kSecPolicyCheckCertificatePolicy, SecPolicyCheckCertificatePolicyOid); CFDictionaryAddValue(gSecPolicyPathCallbacks, @@ -3068,53 +2230,27 @@ void SecPolicyServerInitalize(void) { CFDictionaryAddValue(gSecPolicyPathCallbacks, kSecPolicyCheckWeakRoot, SecPolicyCheckWeakRoot); -} - -/* 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; + CFDictionaryAddValue(gSecPolicyPathCallbacks, + kSecPolicyCheckKeySize, + SecPolicyCheckKeySize); + CFDictionaryAddValue(gSecPolicyPathCallbacks, + kSecPolicyCheckSignatureHashAlgorithms, + SecPolicyCheckSignatureHashAlgorithms); + CFDictionaryAddValue(gSecPolicyPathCallbacks, + kSecPolicyCheckSystemTrustedWeakHash, + SecPolicyCheckSystemTrustedWeakHash); + CFDictionaryAddValue(gSecPolicyPathCallbacks, + kSecPolicyCheckSystemTrustedWeakKey, + SecPolicyCheckSystemTrustedWeakKey); + CFDictionaryAddValue(gSecPolicyPathCallbacks, + kSecPolicyCheckIntermediateOrganization, + SecPolicyCheckIntermediateOrganization); + CFDictionaryAddValue(gSecPolicyPathCallbacks, + kSecPolicyCheckIntermediateCountry, + SecPolicyCheckIntermediateCountry); + CFDictionaryAddValue(gSecPolicyLeafCallbacks, + kSecPolicyCheckPinningRequired, + SecPolicyCheckPinningRequired); } // MARK: - @@ -3123,99 +2259,203 @@ errOut: ****************** SecPVCRef Functions ***************** ********************************************************/ -void SecPVCInit(SecPVCRef pvc, SecPathBuilderRef builder, CFArrayRef policies, - CFAbsoluteTime verifyTime) { +void SecPVCInit(SecPVCRef pvc, SecPathBuilderRef builder, CFArrayRef policies) { secdebug("alloc", "%p", pvc); // Weird logging policies crashes. //secdebug("policy", "%@", policies); + + // Zero the pvc struct so only non-zero fields need to be explicitly set + memset(pvc, 0, sizeof(struct OpaqueSecPVC)); 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; -} + pvc->result = kSecTrustResultUnspecified; -static void SecPVCDeleteRVCs(SecPVCRef pvc) { - secdebug("alloc", "%p", pvc); - if (pvc->rvcs) { - free(pvc->rvcs); - pvc->rvcs = NULL; - } + CFMutableDictionaryRef certDetail = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + pvc->leafDetails = CFArrayCreate(kCFAllocatorDefault, (const void **)&certDetail, + 1, &kCFTypeArrayCallBacks); + CFRelease(certDetail); } 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); + CFReleaseNull(pvc->leafDetails); } -void SecPVCSetPath(SecPVCRef pvc, SecCertificatePathRef path, - CF_CONSUMED CFArrayRef details) { +void SecPVCSetPath(SecPVCRef pvc, SecCertificatePathVCRef path) { 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; + pvc->result = kSecTrustResultUnspecified; + CFReleaseNull(pvc->details); +} + +void SecPVCComputeDetails(SecPVCRef pvc, SecCertificatePathVCRef path) { + pvc->policyIX = 0; + + /* Since we don't run the LeafChecks again, we need to preserve the + * result the leaf had. */ + CFIndex ix, pathLength = SecCertificatePathVCGetCount(path); + CFMutableArrayRef details = CFArrayCreateMutableCopy(kCFAllocatorDefault, + pathLength, pvc->leafDetails); + for (ix = 1; ix < pathLength; ++ix) { + CFMutableDictionaryRef certDetail = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + CFArrayAppendValue(details, certDetail); + CFRelease(certDetail); + } + CFRetainAssign(pvc->details, details); + pvc->result = pvc->leafResult; + CFReleaseSafe(details); } SecPolicyRef SecPVCGetPolicy(SecPVCRef pvc) { return (SecPolicyRef)CFArrayGetValueAtIndex(pvc->policies, pvc->policyIX); } -CFIndex SecPVCGetCertificateCount(SecPVCRef pvc) { - return SecCertificatePathGetCount(pvc->path); +static CFIndex SecPVCGetCertificateCount(SecPVCRef pvc) { + return SecPathBuilderGetCertificateCount(pvc->builder); } -SecCertificateRef SecPVCGetCertificateAtIndex(SecPVCRef pvc, CFIndex ix) { - return SecCertificatePathGetCertificateAtIndex(pvc->path, ix); +static SecCertificateRef SecPVCGetCertificateAtIndex(SecPVCRef pvc, CFIndex ix) { + return SecPathBuilderGetCertificateAtIndex(pvc->builder, ix); } -bool SecPVCIsCertificateAtIndexSelfSigned(SecPVCRef pvc, CFIndex ix) { - return SecCertificatePathSelfSignedIndex(pvc->path) == ix; +static CFAbsoluteTime SecPVCGetVerifyTime(SecPVCRef pvc) { + return SecPathBuilderGetVerifyTime(pvc->builder); } -void SecPVCSetCheckRevocation(SecPVCRef pvc) { - pvc->check_revocation = true; - secdebug("ocsp", "deferred revocation checking enabled"); +static bool SecPVCIsExceptedError(SecPVCRef pvc, CFIndex ix, CFStringRef key, CFTypeRef value) { + CFArrayRef exceptions = SecPathBuilderGetExceptions(pvc->builder); + if (!exceptions) { return false; } + CFIndex exceptionsCount = CFArrayGetCount(exceptions); + + /* There are two types of exceptions: + * 1. Those that are built from SecTrustCopyExceptions, which are particular to the + * certs in the chain -- as indicated by the SHA1 digest in the exception dictionary. + * 2. On macOS, those built from SecTrustSetOptions, which are generic excepted errors. + */ +#if TARGET_OS_OSX + CFDictionaryRef options = CFArrayGetValueAtIndex(exceptions, 0); + /* Type 2 */ + if (exceptionsCount == 1 && (ix > 0 || !CFDictionaryContainsKey(options, kSecCertificateDetailSHA1Digest))) { + /* SHA1Digest not allowed */ + if (CFDictionaryContainsKey(options, kSecCertificateDetailSHA1Digest)) { return false; } + /* Key excepted */ + if (CFDictionaryContainsKey(options, key)) { + /* Special case -- AnchorTrusted only for self-signed certs */ + if (CFEqual(kSecPolicyCheckAnchorTrusted, key)) { + Boolean isSelfSigned = false; + SecCertificateRef cert = SecPathBuilderGetCertificateAtIndex(pvc->builder, ix); + if (!cert || (errSecSuccess != SecCertificateIsSelfSigned(cert, &isSelfSigned)) || !isSelfSigned) { + return false; + } + } + return true; + } + } +#endif + + /* Type 1 */ + if (ix >= exceptionsCount) { return false; } + CFDictionaryRef exception = CFArrayGetValueAtIndex(exceptions, ix); + + /* Compare the cert hash */ + if (!CFDictionaryContainsKey(exception, kSecCertificateDetailSHA1Digest)) { return false; } + SecCertificateRef cert = SecPathBuilderGetCertificateAtIndex(pvc->builder, ix); + if (!CFEqual(SecCertificateGetSHA1Digest(cert), CFDictionaryGetValue(exception, kSecCertificateDetailSHA1Digest))) { + return false; + } + + /* Key Excepted */ + CFTypeRef exceptionValue = CFDictionaryGetValue(exception, key); + if (exceptionValue && CFEqual(value, exceptionValue)) { + /* Only change result if PVC is already ok */ + if (SecPVCIsOkResult(pvc)) { + // Chains that pass due to exceptions get Proceed result. + pvc->result = kSecTrustResultProceed; + } + return true; + } + + return false; } -void SecPVCSetCheckRevocationResponseRequired(SecPVCRef pvc) { - pvc->response_required = true; - secdebug("ocsp", "revocation response required"); +static int32_t detailKeyToCssmErr(CFStringRef key) { + int32_t result = 0; + + if (CFEqual(key, kSecPolicyCheckSSLHostname)) { + result = -2147408896; // CSSMERR_APPLETP_HOSTNAME_MISMATCH + } + else if (CFEqual(key, kSecPolicyCheckEmail)) { + result = -2147408872; // CSSMERR_APPLETP_SMIME_EMAIL_ADDRS_NOT_FOUND + } + else if (CFEqual(key, kSecPolicyCheckValidLeaf) || + CFEqual(key, kSecPolicyCheckValidIntermediates) || + CFEqual(key, kSecPolicyCheckValidRoot)) { + result = -2147409654; // CSSMERR_TP_CERT_EXPIRED + } + + return result; } -bool SecPVCIsAnchored(SecPVCRef pvc) { - return SecCertificatePathIsAnchored(pvc->path); +static bool SecPVCMeetsConstraint(SecPVCRef pvc, SecCertificateRef certificate, CFDictionaryRef constraint); + +static bool SecPVCIsAllowedError(SecPVCRef pvc, CFIndex ix, CFStringRef key) { + bool result = false; + SecCertificatePathVCRef path = SecPathBuilderGetPath(pvc->builder); + CFArrayRef constraints = SecCertificatePathVCGetUsageConstraintsAtIndex(path, ix); + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, ix); + CFIndex constraintIX, constraintCount = CFArrayGetCount(constraints); + + for (constraintIX = 0; constraintIX < constraintCount; constraintIX++) { + CFDictionaryRef constraint = (CFDictionaryRef)CFArrayGetValueAtIndex(constraints, constraintIX); + CFNumberRef allowedErrorNumber = NULL; + if (!isDictionary(constraint)) { + continue; + } + allowedErrorNumber = (CFNumberRef)CFDictionaryGetValue(constraint, kSecTrustSettingsAllowedError); + int32_t allowedErrorValue = 0; + if (!isNumber(allowedErrorNumber) || !CFNumberGetValue(allowedErrorNumber, kCFNumberSInt32Type, &allowedErrorValue)) { + continue; + } + + if (SecPVCMeetsConstraint(pvc, cert, constraint)) { + if (allowedErrorValue == detailKeyToCssmErr(key)) { + result = true; + break; + } + } + } + return result; } -CFAbsoluteTime SecPVCGetVerifyTime(SecPVCRef pvc) { - return pvc->verifyTime; +static bool SecPVCKeyIsConstraintPolicyOption(SecPVCRef pvc, CFStringRef key) { + CFIndex certIX, certCount = SecPVCGetCertificateCount(pvc); + for (certIX = 0; certIX < certCount; certIX++) { + SecCertificatePathVCRef path = SecPathBuilderGetPath(pvc->builder); + CFArrayRef constraints = SecCertificatePathVCGetUsageConstraintsAtIndex(path, certIX); + CFIndex constraintIX, constraintCount = CFArrayGetCount(constraints); + for (constraintIX = 0; constraintIX < constraintCount; constraintIX++) { + CFDictionaryRef constraint = (CFDictionaryRef)CFArrayGetValueAtIndex(constraints, constraintIX); + if (!isDictionary(constraint)) { + continue; + } + + CFDictionaryRef policyOptions = NULL; + policyOptions = (CFDictionaryRef)CFDictionaryGetValue(constraint, kSecTrustSettingsPolicyOptions); + if (policyOptions && isDictionary(policyOptions) && + CFDictionaryContainsKey(policyOptions, key)) { + return true; + } + } + } + return false; } /* AUDIT[securityd](done): @@ -3225,7 +2465,7 @@ CFAbsoluteTime SecPVCGetVerifyTime(SecPVCRef pvc) { bool SecPVCSetResultForced(SecPVCRef pvc, CFStringRef key, CFIndex ix, CFTypeRef result, bool force) { - secdebug("policy", "cert[%d]: %@ =(%s)[%s]> %@", (int) ix, key, + secnotice("policy", "cert[%d]: %@ =(%s)[%s]> %@", (int) ix, key, (pvc->callbacks == gSecPolicyLeafCallbacks ? "leaf" : (pvc->callbacks == gSecPolicyPathCallbacks ? "path" : "custom")), @@ -3234,17 +2474,31 @@ bool SecPVCSetResultForced(SecPVCRef pvc, /* 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. */ + /* Either the policy or the usage constraints have to have this key */ SecPolicyRef policy = SecPVCGetPolicy(pvc); - if (policy && !CFDictionaryContainsKey(policy->_options, key)) + if (!(SecPVCKeyIsConstraintPolicyOption(pvc, key) || + (policy && CFDictionaryContainsKey(policy->_options, key)))) { return true; + } } - /* @@@ Check to see if the SecTrustSettings for the certificate in question + /* Check to see if the SecTrustSettings for the certificate in question tell us to ignore this error. */ - pvc->result = false; + if (SecPVCIsAllowedError(pvc, ix, key)) { + secinfo("policy", "cert[%d]: skipped allowed error %@", (int) ix, key); + return true; + } + + /* Check to see if exceptions tells us to ignore this error. */ + if (SecPVCIsExceptedError(pvc, ix, key, result)) { + secinfo("policy", "cert[%d]: skipped exception error %@", (int) ix, key); + return true; + } + + /* Check SecPVCIsOkResult to avoid resetting deny or fatal to recoverable */ + if (SecPVCIsOkResult(pvc)) { + pvc->result = kSecTrustResultRecoverableTrustFailure; + } if (!pvc->details) return false; @@ -3273,32 +2527,26 @@ static void SecPVCValidateKey(const void *key, const void *value, /* 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) + if (!SecPVCIsOkResult(pvc) && !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; + pvc->result = kSecTrustResultOtherError; } } else if (pvc->callbacks == gSecPolicyPathCallbacks) { if (!CFDictionaryContainsKey(gSecPolicyLeafCallbacks, key)) { - pvc->result = false; + pvc->result = kSecTrustResultOtherError; } } else { - /* Non standard validation phase, nothing is optional. */ - pvc->result = false; + /* Non standard validation phase. This may be a new key from the + * pinning DB which is not implemented in this OS version. Log + * a warning. */ + secwarning("policy: unknown policy key %@, skipping", key); } return; } @@ -3310,8 +2558,10 @@ static void SecPVCValidateKey(const void *key, const void *value, policy->_options is a caller provided dictionary, only its cf type has been checked. */ -bool SecPVCLeafChecks(SecPVCRef pvc) { - pvc->result = true; +SecTrustResultType SecPVCLeafChecks(SecPVCRef pvc) { + /* We need to compute details for the leaf. */ + CFRetainAssign(pvc->details, pvc->leafDetails); + CFArrayRef policies = pvc->policies; CFIndex ix, count = CFArrayGetCount(policies); for (ix = 0; ix < count; ++ix) { @@ -3320,31 +2570,45 @@ bool SecPVCLeafChecks(SecPVCRef pvc) { /* Validate all keys for all policies. */ pvc->callbacks = gSecPolicyLeafCallbacks; CFDictionaryApplyFunction(policy->_options, SecPVCValidateKey, pvc); - if (!pvc->result && !pvc->details) - return pvc->result; } + pvc->leafResult = pvc->result; + CFRetainAssign(pvc->leafDetails, pvc->details); + return pvc->result; } +bool SecPVCIsOkResult(SecPVCRef pvc) { + if (pvc->result == kSecTrustResultRecoverableTrustFailure || + pvc->result == kSecTrustResultDeny || + pvc->result == kSecTrustResultFatalTrustFailure || + pvc->result == kSecTrustResultOtherError) { + return false; + } + return true; +} + 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)) + CFAbsoluteTime verifyTime = SecPVCGetVerifyTime(pvc); + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, ix); + CFIndex anchor_ix = SecPVCGetCertificateCount(pvc) - 1; + bool is_anchor = (ix == anchor_ix && SecPathBuilderIsAnchored(pvc->builder)); + + if (!SecCertificateIsValid(cert, verifyTime)) { + /* Certificate has expired. */ + if (!SecPVCSetResult(pvc, is_anchor ? kSecPolicyCheckValidRoot + : kSecPolicyCheckValidIntermediates, ix, kCFBooleanFalse)) { goto errOut; - } + } + } - if (SecCertificateIsWeak(cert)) { + if (SecCertificateIsWeakKey(cert)) { /* Certificate uses weak key. */ if (!SecPVCSetResult(pvc, is_anchor ? kSecPolicyCheckWeakRoot - : kSecPolicyCheckWeakIntermediates, ix, kCFBooleanFalse)) + : kSecPolicyCheckWeakIntermediates, ix, kCFBooleanFalse)) { goto errOut; + } } if (is_anchor) { @@ -3353,31 +2617,47 @@ bool SecPVCParentCertificateChecks(SecPVCRef pvc, CFIndex ix) { } 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; + /* (k) Basic constraints only relevant for v3 and later. */ + if (SecCertificateVersion(cert) >= 3) { + const SecCEBasicConstraints *bc = + SecCertificateGetBasicConstraints(cert); + if (!bc || !bc->isCA) { + /* Basic constraints not present or not marked as isCA, illegal. */ + if (!SecPVCSetResultForced(pvc, kSecPolicyCheckBasicConstraints, + ix, kCFBooleanFalse, true)) { + goto errOut; + } + } + } + /* For a v1 or v2 certificate in an intermediate slot (not a leaf and + not an anchor), we additionally require that the certificate chain + does not end in a v3 or later anchor. [rdar://32204517] */ + else if (ix > 0 && ix < anchor_ix) { + SecCertificateRef anchor = SecPVCGetCertificateAtIndex(pvc, anchor_ix); + if (SecCertificateVersion(anchor) >= 3) { + if (!SecPVCSetResultForced(pvc, kSecPolicyCheckBasicConstraints, + ix, kCFBooleanFalse, true)) { + goto errOut; + } + } } - /* Consider adding (l) max_path_length checking here. */ + /* (l) max_path_length is checked elsewhere. */ /* (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)) + ix, kCFBooleanFalse, true)) { goto errOut; + } } } errOut: - return pvc->result; + return SecPVCIsOkResult(pvc); } -bool SecPVCBlackListedKeyChecks(SecPVCRef pvc, CFIndex ix) { +static bool SecPVCBlackListedKeyChecks(SecPVCRef pvc, CFIndex ix) { /* Check stuff common to intermediate and anchors. */ SecOTAPKIRef otapkiRef = SecOTAPKICopyCurrentOTAPKIRef(); @@ -3388,31 +2668,40 @@ bool SecPVCBlackListedKeyChecks(SecPVCRef pvc, CFIndex ix) { 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); - } - } + CFIndex count = SecPVCGetCertificateCount(pvc); + bool is_last = (ix == count - 1); + bool is_anchor = (is_last && SecPathBuilderIsAnchored(pvc->builder)); + if (!is_anchor) { + /* Check for blacklisted intermediate issuer keys. */ + CFDataRef dgst = SecCertificateCopyPublicKeySHA1Digest(cert); + if (dgst) { + /* Check dgst against blacklist. */ + if (CFSetContainsValue(blackListedKeys, dgst)) { + /* Check allow list for this blacklisted issuer key, + which is the authority key of the issued cert at ix-1. + */ + SecCertificatePathVCRef path = SecPathBuilderGetPath(pvc->builder); + bool allowed = path && SecCertificatePathVCIsAllowlisted(path); + if (!allowed) { + SecPVCSetResultForced(pvc, kSecPolicyCheckBlackListedKey, + ix, kCFBooleanFalse, true); + pvc->result = kSecTrustResultFatalTrustFailure; + } + } + CFRelease(dgst); + } + } CFRelease(blackListedKeys); - return pvc->result; + return SecPVCIsOkResult(pvc); } } // Assume OK return true; } -bool SecPVCGrayListedKeyChecks(SecPVCRef pvc, CFIndex ix) +static bool SecPVCGrayListedKeyChecks(SecPVCRef pvc, CFIndex ix) { - /* Check stuff common to intermediate and anchors. */ + /* Check stuff common to intermediate and anchors. */ SecOTAPKIRef otapkiRef = SecOTAPKICopyCurrentOTAPKIRef(); if (NULL != otapkiRef) { @@ -3421,139 +2710,546 @@ bool SecPVCGrayListedKeyChecks(SecPVCRef pvc, CFIndex ix) 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); - } - } + CFIndex count = SecPVCGetCertificateCount(pvc); + bool is_last = (ix == count - 1); + bool is_anchor = (is_last && SecPathBuilderIsAnchored(pvc->builder)); + if (!is_anchor) { + /* Check for gray listed intermediate issuer keys. */ + CFDataRef dgst = SecCertificateCopyPublicKeySHA1Digest(cert); + if (dgst) { + /* Check dgst against gray list. */ + if (CFSetContainsValue(grayListKeys, dgst)) { + /* Check allow list for this graylisted issuer key, + which is the authority key of the issued cert at ix-1. + */ + SecCertificatePathVCRef path = SecPathBuilderGetPath(pvc->builder); + bool allowed = path && SecCertificatePathVCIsAllowlisted(path); + if (!allowed) { + SecPVCSetResultForced(pvc, kSecPolicyCheckGrayListedKey, + ix, kCFBooleanFalse, true); + } + } + CFRelease(dgst); + } + } CFRelease(grayListKeys); - return pvc->result; + return SecPVCIsOkResult(pvc); } } // Assume ok return true; } +static bool SecPVCContainsPolicy(SecPVCRef pvc, CFStringRef searchOid, CFStringRef searchName, CFIndex *policyIX) { + if (!isString(searchName) && !isString(searchOid)) { + return false; + } + CFArrayRef policies = pvc->policies; + CFIndex ix, count = CFArrayGetCount(policies); + for (ix = 0; ix < count; ++ix) { + SecPolicyRef policy = (SecPolicyRef)CFArrayGetValueAtIndex(policies, ix); + CFStringRef policyName = SecPolicyGetName(policy); + CFStringRef policyOid = SecPolicyGetOidString(policy); + /* Prefer a match of both name and OID */ + if (searchOid && searchName && policyOid && policyName) { + if (CFEqual(searchOid, policyOid) && + CFEqual(searchName, policyName)) { + if (policyIX) { *policyIX = ix; } + return true; + } + } + /* Next best is just OID. */ + if (!searchName && searchOid && policyOid) { + if (CFEqual(searchOid, policyOid)) { + if (policyIX) { *policyIX = ix; } + return true; + } + } + if (!searchOid && searchName && policyName) { + if (CFEqual(searchName, policyName)) { + if (policyIX) { *policyIX = ix; } + return true; + } + } + } + return false; +} + +static bool SecPVCContainsString(SecPVCRef pvc, CFIndex policyIX, CFStringRef stringValue) { + if (!isString(stringValue)) { + return false; + } + bool result = false; + + CFStringRef tmpStringValue = NULL; + if (CFStringGetCharacterAtIndex(stringValue, CFStringGetLength(stringValue) -1) == (UniChar)0x0000) { + tmpStringValue = CFStringCreateTruncatedCopy(stringValue, CFStringGetLength(stringValue) - 1); + } else { + tmpStringValue = CFStringCreateCopy(NULL, stringValue); + } + if (policyIX >= 0 && policyIX < CFArrayGetCount(pvc->policies)) { + SecPolicyRef policy = (SecPolicyRef)CFArrayGetValueAtIndex(pvc->policies, policyIX); + /* Have to look for all the possible locations of name string */ + CFStringRef policyString = NULL; + policyString = CFDictionaryGetValue(policy->_options, kSecPolicyCheckSSLHostname); + if (!policyString) { + policyString = CFDictionaryGetValue(policy->_options, kSecPolicyCheckEmail); + } + if (policyString && (CFStringCompare(tmpStringValue, policyString, kCFCompareCaseInsensitive) == kCFCompareEqualTo)) { + result = true; + goto out; + } + + CFArrayRef policyStrings = NULL; + policyStrings = CFDictionaryGetValue(policy->_options, kSecPolicyCheckEAPTrustedServerNames); + if (policyStrings && CFArrayContainsValue(policyStrings, + CFRangeMake(0, CFArrayGetCount(policyStrings)), + tmpStringValue)) { + result = true; + goto out; + } + } + +out: + CFReleaseNull(tmpStringValue); + return result; +} + + +static uint32_t ts_key_usage_for_kuNumber(CFNumberRef keyUsageNumber) { + uint32_t ourTSKeyUsage = 0; + uint32_t keyUsage = 0; + if (keyUsageNumber && + CFNumberGetValue(keyUsageNumber, kCFNumberSInt32Type, &keyUsage)) { + if (keyUsage & kSecKeyUsageDigitalSignature) { + ourTSKeyUsage |= kSecTrustSettingsKeyUseSignature; + } + if (keyUsage & kSecKeyUsageDataEncipherment) { + ourTSKeyUsage |= kSecTrustSettingsKeyUseEnDecryptData; + } + if (keyUsage & kSecKeyUsageKeyEncipherment) { + ourTSKeyUsage |= kSecTrustSettingsKeyUseEnDecryptKey; + } + if (keyUsage & kSecKeyUsageKeyAgreement) { + ourTSKeyUsage |= kSecTrustSettingsKeyUseKeyExchange; + } + if (keyUsage == kSecKeyUsageAll) { + ourTSKeyUsage = kSecTrustSettingsKeyUseAny; + } + } + return ourTSKeyUsage; +} + +static uint32_t ts_key_usage_for_policy(SecPolicyRef policy) { + uint32_t ourTSKeyUsage = 0; + CFTypeRef policyKeyUsageType = NULL; + + policyKeyUsageType = (CFTypeRef)CFDictionaryGetValue(policy->_options, kSecPolicyCheckKeyUsage); + if (isArray(policyKeyUsageType)) { + CFIndex ix, count = CFArrayGetCount(policyKeyUsageType); + for (ix = 0; ix < count; ix++) { + CFNumberRef policyKeyUsageNumber = NULL; + policyKeyUsageNumber = (CFNumberRef)CFArrayGetValueAtIndex(policyKeyUsageType, ix); + ourTSKeyUsage |= ts_key_usage_for_kuNumber(policyKeyUsageNumber); + } + } else if (isNumber(policyKeyUsageType)) { + ourTSKeyUsage |= ts_key_usage_for_kuNumber(policyKeyUsageType); + } + + return ourTSKeyUsage; +} + +static bool SecPVCContainsTrustSettingsKeyUsage(SecPVCRef pvc, + SecCertificateRef certificate, CFIndex policyIX, CFNumberRef keyUsageNumber) { + int64_t keyUsageValue = 0; + uint32_t ourKeyUsage = 0; + + if (!isNumber(keyUsageNumber) || !CFNumberGetValue(keyUsageNumber, kCFNumberSInt64Type, &keyUsageValue)) { + return false; + } + + if (keyUsageValue == kSecTrustSettingsKeyUseAny) { + return true; + } + + /* We're using the key for revocation if we have the OCSPSigner policy. + * @@@ If we support CRLs, we'd need to check for that policy here too. + */ + if (SecPVCContainsPolicy(pvc, kSecPolicyAppleOCSPSigner, NULL, NULL)) { + ourKeyUsage |= kSecTrustSettingsKeyUseSignRevocation; + } + + /* We're using the key for verifying a cert if it's a root/intermediate + * in the chain. If the cert isn't in the path yet, we're about to add it, + * so it's a root/intermediate. If there is no path, this is the leaf. + */ + CFIndex pathIndex = -1; + SecCertificatePathVCRef path = SecPathBuilderGetPath(pvc->builder); + if (path) { + pathIndex = SecCertificatePathVCGetIndexOfCertificate(path, certificate); + } else { + pathIndex = 0; + } + if (pathIndex != 0) { + ourKeyUsage |= kSecTrustSettingsKeyUseSignCert; + } + + /* The rest of the key usages may be specified by the policy(ies). */ + if (policyIX >= 0 && policyIX < CFArrayGetCount(pvc->policies)) { + SecPolicyRef policy = (SecPolicyRef)CFArrayGetValueAtIndex(pvc->policies, policyIX); + ourKeyUsage |= ts_key_usage_for_policy(policy); + } else { + /* Get key usage from ALL policies */ + CFIndex ix, count = CFArrayGetCount(pvc->policies); + for (ix = 0; ix < count; ix++) { + SecPolicyRef policy = (SecPolicyRef)CFArrayGetValueAtIndex(pvc->policies, ix); + ourKeyUsage |= ts_key_usage_for_policy(policy); + } + } + + if (ourKeyUsage == (uint32_t)(keyUsageValue & 0x00ffffffff)) { + return true; + } + + return false; +} + +#if TARGET_OS_OSX +#include +#include +#include +#include +#include + +extern OSStatus SecTaskValidateForRequirement(SecTaskRef task, CFStringRef requirement); + +static bool SecPVCCallerIsApplication(CFDataRef clientAuditToken, CFTypeRef appRef) { + bool result = false; + audit_token_t auditToken = {}; + SecTaskRef task = NULL; + SecRequirementRef requirement = NULL; + CFStringRef stringRequirement = NULL; + + require(appRef && clientAuditToken, out); + require(CFGetTypeID(appRef) == SecTrustedApplicationGetTypeID(), out); + require_noerr(SecTrustedApplicationCopyRequirement((SecTrustedApplicationRef)appRef, &requirement), out); + require(requirement, out); + require_noerr(SecRequirementsCopyString(requirement, kSecCSDefaultFlags, &stringRequirement), out); + require(stringRequirement, out); + + require(sizeof(auditToken) == CFDataGetLength(clientAuditToken), out); + CFDataGetBytes(clientAuditToken, CFRangeMake(0, sizeof(auditToken)), (uint8_t *)&auditToken); + require(task = SecTaskCreateWithAuditToken(NULL, auditToken), out); + + if(errSecSuccess == SecTaskValidateForRequirement(task, stringRequirement)) { + result = true; + } + +out: + CFReleaseNull(task); + CFReleaseNull(requirement); + CFReleaseNull(stringRequirement); + return result; +} +#endif + +static bool SecPVCContainsTrustSettingsPolicyOption(SecPVCRef pvc, CFDictionaryRef options) { + if (!isDictionary(options)) { + return false; + } + + /* Push */ + CFDictionaryRef currentCallbacks = pvc->callbacks; + + /* We need to run the leaf and path checks using these options. */ + pvc->callbacks = gSecPolicyLeafCallbacks; + CFDictionaryApplyFunction(options, SecPVCValidateKey, pvc); + + pvc->callbacks = gSecPolicyPathCallbacks; + CFDictionaryApplyFunction(options, SecPVCValidateKey, pvc); + + /* Pop */ + pvc->callbacks = currentCallbacks; + + /* Our work here is done; no need to claim a match */ + return false; +} + +static bool SecPVCMeetsConstraint(SecPVCRef pvc, SecCertificateRef certificate, CFDictionaryRef constraint) { + CFStringRef policyOid = NULL, policyString = NULL, policyName = NULL; + CFNumberRef keyUsageNumber = NULL; + CFTypeRef trustedApplicationData = NULL; + CFDictionaryRef policyOptions = NULL; + + bool policyMatch = false, policyStringMatch = false, applicationMatch = false , + keyUsageMatch = false, policyOptionMatch = false; + bool result = false; + +#if TARGET_OS_MAC && !TARGET_OS_IPHONE + /* OS X returns a SecPolicyRef in the constraints. Convert to the oid string. */ + SecPolicyRef policy = NULL; + policy = (SecPolicyRef)CFDictionaryGetValue(constraint, kSecTrustSettingsPolicy); + policyOid = (policy) ? policy->_oid : NULL; +#else + policyOid = (CFStringRef)CFDictionaryGetValue(constraint, kSecTrustSettingsPolicy); +#endif + policyName = (CFStringRef)CFDictionaryGetValue(constraint, kSecTrustSettingsPolicyName); + policyString = (CFStringRef)CFDictionaryGetValue(constraint, kSecTrustSettingsPolicyString); + keyUsageNumber = (CFNumberRef)CFDictionaryGetValue(constraint, kSecTrustSettingsKeyUsage); + policyOptions = (CFDictionaryRef)CFDictionaryGetValue(constraint, kSecTrustSettingsPolicyOptions); + + CFIndex policyIX = -1; + policyMatch = SecPVCContainsPolicy(pvc, policyOid, policyName, &policyIX); + policyStringMatch = SecPVCContainsString(pvc, policyIX, policyString); + keyUsageMatch = SecPVCContainsTrustSettingsKeyUsage(pvc, certificate, policyIX, keyUsageNumber); + policyOptionMatch = SecPVCContainsTrustSettingsPolicyOption(pvc, policyOptions); + +#if TARGET_OS_MAC && !TARGET_OS_IPHONE + trustedApplicationData = CFDictionaryGetValue(constraint, kSecTrustSettingsApplication); + CFDataRef clientAuditToken = SecPathBuilderCopyClientAuditToken(pvc->builder); + applicationMatch = SecPVCCallerIsApplication(clientAuditToken, trustedApplicationData); + CFReleaseNull(clientAuditToken); +#else + if(CFDictionaryContainsKey(constraint, kSecTrustSettingsApplication)) { + secerror("kSecTrustSettingsApplication is not yet supported on this platform"); + } +#endif + + /* If we either didn't find the parameter in the dictionary or we got a match + * against that parameter, for all possible parameters in the dictionary, then + * this trust setting result applies to the output. */ + if (((!policyOid && !policyName) || policyMatch) && + (!policyString || policyStringMatch) && + (!trustedApplicationData || applicationMatch) && + (!keyUsageNumber || keyUsageMatch) && + (!policyOptions || policyOptionMatch)) { + result = true; + } + + return result; +} + +SecTrustSettingsResult SecPVCGetTrustSettingsResult(SecPVCRef pvc, SecCertificateRef certificate, CFArrayRef constraints) { + SecTrustSettingsResult result = kSecTrustSettingsResultInvalid; + CFIndex constraintIX, constraintCount = CFArrayGetCount(constraints); + for (constraintIX = 0; constraintIX < constraintCount; constraintIX++) { + CFDictionaryRef constraint = (CFDictionaryRef)CFArrayGetValueAtIndex(constraints, constraintIX); + if (!isDictionary(constraint)) { + continue; + } + + CFNumberRef resultNumber = NULL; + resultNumber = (CFNumberRef)CFDictionaryGetValue(constraint, kSecTrustSettingsResult); + uint32_t resultValue = kSecTrustSettingsResultInvalid; + if (!isNumber(resultNumber) || !CFNumberGetValue(resultNumber, kCFNumberSInt32Type, &resultValue)) { + /* no SecTrustSettingsResult entry defaults to TrustRoot*/ + resultValue = kSecTrustSettingsResultTrustRoot; + } + + if (SecPVCMeetsConstraint(pvc, certificate, constraint)) { + result = resultValue; + break; + } + } + return result; +} + +static void SecPVCCheckUsageConstraints(SecPVCRef pvc) { + CFIndex certIX, certCount = SecPVCGetCertificateCount(pvc); + for (certIX = 0; certIX < certCount; certIX++) { + SecCertificatePathVCRef path = SecPathBuilderGetPath(pvc->builder); + CFArrayRef constraints = SecCertificatePathVCGetUsageConstraintsAtIndex(path, certIX); + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, certIX); + SecTrustSettingsResult result = SecPVCGetTrustSettingsResult(pvc, cert, constraints); + + /* Set the pvc trust result based on the usage constraints and anchor source. */ + if (result == kSecTrustSettingsResultDeny) { + SecPVCSetResultForced(pvc, kSecPolicyCheckUsageConstraints, certIX, kCFBooleanFalse, true); + pvc->result = kSecTrustResultDeny; + } else if ((result == kSecTrustSettingsResultTrustRoot || result == kSecTrustSettingsResultTrustAsRoot || + result == kSecTrustSettingsResultInvalid) && SecPVCIsOkResult(pvc)) { + /* If we already think the PVC is ok and this cert is from one of the user/ + * admin anchor sources, trustRoot, trustAsRoot, and Invalid (no constraints), + * all mean we should use the special "Proceed" trust result. */ +#if TARGET_OS_IPHONE + if (SecPathBuilderIsAnchorSource(pvc->builder, kSecUserAnchorSource) && + SecCertificateSourceContains(kSecUserAnchorSource, cert)) { +#else + if (SecPathBuilderIsAnchorSource(pvc->builder, kSecLegacyAnchorSource) && + SecCertificateSourceContains(kSecLegacyAnchorSource, cert)) { +#endif + pvc->result = kSecTrustResultProceed; + } + } + } +} + +#define kSecPolicySHA256Size 32 +static const UInt8 kTestDateConstraintsRoot[kSecPolicySHA256Size] = { + 0x51,0xA0,0xF3,0x1F,0xC0,0x1D,0xEC,0x87,0x32,0xB6,0xFD,0x13,0x6A,0x43,0x4D,0x6C, + 0x87,0xCD,0x62,0xE0,0x38,0xB4,0xFB,0xD6,0x40,0xB0,0xFD,0x62,0x4D,0x1F,0xCF,0x6D +}; +static const UInt8 kWS_CA1_G2[kSecPolicySHA256Size] = { + 0xD4,0x87,0xA5,0x6F,0x83,0xB0,0x74,0x82,0xE8,0x5E,0x96,0x33,0x94,0xC1,0xEC,0xC2, + 0xC9,0xE5,0x1D,0x09,0x03,0xEE,0x94,0x6B,0x02,0xC3,0x01,0x58,0x1E,0xD9,0x9E,0x16 +}; +static const UInt8 kWS_CA1_NEW[kSecPolicySHA256Size] = { + 0x4B,0x22,0xD5,0xA6,0xAE,0xC9,0x9F,0x3C,0xDB,0x79,0xAA,0x5E,0xC0,0x68,0x38,0x47, + 0x9C,0xD5,0xEC,0xBA,0x71,0x64,0xF7,0xF2,0x2D,0xC1,0xD6,0x5F,0x63,0xD8,0x57,0x08 +}; +static const UInt8 kWS_CA2_NEW[kSecPolicySHA256Size] = { + 0xD6,0xF0,0x34,0xBD,0x94,0xAA,0x23,0x3F,0x02,0x97,0xEC,0xA4,0x24,0x5B,0x28,0x39, + 0x73,0xE4,0x47,0xAA,0x59,0x0F,0x31,0x0C,0x77,0xF4,0x8F,0xDF,0x83,0x11,0x22,0x54 +}; +static const UInt8 kWS_ECC[kSecPolicySHA256Size] = { + 0x8B,0x45,0xDA,0x1C,0x06,0xF7,0x91,0xEB,0x0C,0xAB,0xF2,0x6B,0xE5,0x88,0xF5,0xFB, + 0x23,0x16,0x5C,0x2E,0x61,0x4B,0xF8,0x85,0x56,0x2D,0x0D,0xCE,0x50,0xB2,0x9B,0x02 +}; +static const UInt8 kSC_SFSCA[kSecPolicySHA256Size] = { + 0xC7,0x66,0xA9,0xBE,0xF2,0xD4,0x07,0x1C,0x86,0x3A,0x31,0xAA,0x49,0x20,0xE8,0x13, + 0xB2,0xD1,0x98,0x60,0x8C,0xB7,0xB7,0xCF,0xE2,0x11,0x43,0xB8,0x36,0xDF,0x09,0xEA +}; +static const UInt8 kSC_SHA2[kSecPolicySHA256Size] = { + 0xE1,0x78,0x90,0xEE,0x09,0xA3,0xFB,0xF4,0xF4,0x8B,0x9C,0x41,0x4A,0x17,0xD6,0x37, + 0xB7,0xA5,0x06,0x47,0xE9,0xBC,0x75,0x23,0x22,0x72,0x7F,0xCC,0x17,0x42,0xA9,0x11 +}; +static const UInt8 kSC_G2[kSecPolicySHA256Size] = { + 0xC7,0xBA,0x65,0x67,0xDE,0x93,0xA7,0x98,0xAE,0x1F,0xAA,0x79,0x1E,0x71,0x2D,0x37, + 0x8F,0xAE,0x1F,0x93,0xC4,0x39,0x7F,0xEA,0x44,0x1B,0xB7,0xCB,0xE6,0xFD,0x59,0x95 +}; + +static void SecPVCCheckIssuerDateConstraints(SecPVCRef pvc) { + static CFSetRef sConstrainedRoots = NULL; + static dispatch_once_t _t; + dispatch_once(&_t, ^{ + const UInt8 *v_hashes[] = { + kWS_CA1_G2, kWS_CA1_NEW, kWS_CA2_NEW, kWS_ECC, + kSC_SFSCA, kSC_SHA2, kSC_G2, kTestDateConstraintsRoot + }; + CFMutableSetRef set = CFSetCreateMutable(NULL, 0, &kCFTypeSetCallBacks); + CFIndex ix, count = sizeof(v_hashes)/sizeof(*v_hashes); + for (ix=0; ix= 0 && !shouldDeny; certIX--) { + SecCertificateRef cert = SecPVCGetCertificateAtIndex(pvc, certIX); + CFDataRef sha256 = SecCertificateCopySHA256Digest(cert); + if (sha256 && CFSetContainsValue(sConstrainedRoots, sha256)) { + /* matched a constrained root; check notBefore dates on all its children. */ + CFIndex childIX = certIX; + while (--childIX >= 0) { + SecCertificateRef child = SecPVCGetCertificateAtIndex(pvc, childIX); + /* 1 Dec 2016 00:00:00 GMT */ + if (child && (CFAbsoluteTime)502243200.0 <= SecCertificateNotValidBefore(child)) { + SecPVCSetResultForced(pvc, kSecPolicyCheckBlackListedKey, certIX, kCFBooleanFalse, true); + pvc->result = kSecTrustResultFatalTrustFailure; + shouldDeny = true; + break; + } + } + } + CFReleaseNull(sha256); + } +} + /* 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; +void SecPVCPathChecks(SecPVCRef pvc) { + secdebug("policy", "begin path: %@", SecPathBuilderGetPath(pvc->builder)); + SecCertificatePathVCRef path = SecPathBuilderGetPath(pvc->builder); /* 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) { + if (SecPVCIsOkResult(pvc) || pvc->details) { + /* @@@ This theoretically only needs to be done once per path, but since + this function affects the pvc result, we'll run it every time. */ SecPolicyCheckBasicCertificateProcessing(pvc, kSecPolicyCheckBasicCertificateProcessing); } CFArrayRef policies = pvc->policies; - CFIndex count = CFArrayGetCount(policies); - for (; pvc->policyIX < count; ++pvc->policyIX) { + CFIndex count = CFArrayGetCount(policies); + for (; pvc->policyIX < count; ++pvc->policyIX) { /* Validate all keys for all policies. */ pvc->callbacks = gSecPolicyPathCallbacks; - SecPolicyRef policy = SecPVCGetPolicy(pvc); + SecPolicyRef policy = SecPVCGetPolicy(pvc); CFDictionaryApplyFunction(policy->_options, SecPVCValidateKey, pvc); - if (!pvc->result && !pvc->details) - return completed; - } + if (!SecPVCIsOkResult(pvc) && !pvc->details) + return; + } - /* Check the things we can't check statically for the certificate path. */ - /* Critical Extensions, chainLength. */ + // Reset + pvc->policyIX = 0; - /* 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. The revocation will - only fetch OCSP response over the network if the client asked for revocation - check explicitly or is_ev is true. */ - if (pvc->result) { - completed = SecPVCCheckRevocation(pvc); - } + /* Check whether the TrustSettings say to deny a cert in the path. */ + SecPVCCheckUsageConstraints(pvc); + + /* Check for Blocklisted certs */ + SecPVCCheckIssuerDateConstraints(pvc); + CFIndex ix; + count = SecCertificatePathVCGetCount(path); + for (ix = 1; ix < count; ix++) { + SecPVCGrayListedKeyChecks(pvc, ix); + SecPVCBlackListedKeyChecks(pvc, ix); + } + + /* Path-based check tests. */ + if (!SecCertificatePathVCIsPathValidated(path)) { + bool ev_check_ok = false; + if (SecCertificatePathVCIsOptionallyEV(path)) { + SecTrustResultType pre_ev_check_result = pvc->result; + SecPolicyCheckEV(pvc, kSecPolicyCheckExtendedValidation); + ev_check_ok = SecPVCIsOkResult(pvc); + /* 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 for CT */ - if (pvc->result || pvc->details) { + /* Check for CT */ /* This call will set the value of pvc->is_ct, but won't change the result (pvc->result) */ SecPolicyCheckCT(pvc, kSecPolicyCheckCertificateTransparency); - } + /* Certs are only EV if they are also CT verified */ + if (ev_check_ok && SecCertificatePathVCIsCT(path)) { + SecCertificatePathVCSetIsEV(path, true); + } + } //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("policy", "end %strusted path: %@", + (SecPVCIsOkResult(pvc) ? "" : "not "), SecPathBuilderGetPath(pvc->builder)); + + SecCertificatePathVCSetPathValidated(SecPathBuilderGetPath(pvc->builder)); + return; +} + +void SecPVCPathCheckRevocationRequired(SecPVCRef pvc) { + SecCertificatePathVCRef path = SecPathBuilderGetPath(pvc->builder); + CFIndex ix, certCount = SecCertificatePathVCGetCount(path); + for (ix = 0; ix < certCount; ix++) { + /* If we require revocation (for that cert per the SecCertificateVCRef or + * per the pvc) */ + if (SecCertificatePathVCIsRevocationRequiredForCertificateAtIndex(path, ix) || + ((ix == 0) && pvc->require_revocation_response)) { + /* Do we have a valid revocation response? */ + SecRVCRef rvc = SecCertificatePathVCGetRVCAtIndex(path, ix); + if (SecRVCGetEarliestNextUpdate(rvc) == NULL_TIME) { + SecPVCSetResultForced(pvc, kSecPolicyCheckRevocationResponseRequired, + ix, kCFBooleanFalse, true); } - 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; }