+/*
+ * Copyright (c) 2020 Apple Inc. All Rights Reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ *
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+#include <AssertMacros.h>
+#include <libDER/DER_CertCrl.h>
+#include <libDER/DER_Encode.h>
+#include <libDER/asn1Types.h>
+
+#include <security_asn1/SecAsn1Coder.h>
+#include <security_asn1/oidsalg.h>
+#include <utilities/SecCFWrappers.h>
+#include <Security/SecCertificatePriv.h>
+#include <Security/SecureTransportPriv.h>
+#include <Security/SecKeyPriv.h>
+#include <Security/SecPolicyPriv.h>
+
+#include "trust/trustd/SecTrustServer.h"
+#include "trust/trustd/SecPolicyServer.h"
+#include "trust/trustd/SecOCSPResponse.h"
+#include "trust/trustd/OTATrustUtilities.h"
+#include "trust/trustd/SecCertificateServer.h"
+#include "trust/trustd/CertificateTransparency.h"
+
+const CFStringRef kSecCTRetirementDateKey = CFSTR("expiry"); // For backwards compatibility, retirement date is represented with the "expiry" key
+const CFStringRef kSecCTReadOnlyDateKey = CFSTR("frozen"); // For backwards compatibility, read-only date is represented with the "frozen" key
+const CFStringRef kSecCTShardStartDateKey = CFSTR("start_inclusive");
+const CFStringRef kSecCTShardEndDateKey = CFSTR("end_exclusive");
+const CFStringRef kSecCTPublicKeyKey = CFSTR("key");
+
+enum {
+ kSecCTEntryTypeCert = 0,
+ kSecCTEntryTypePreCert = 1,
+};
+
+/***
+
+struct {
+ Version sct_version; // 1 byte
+ LogID id; // 32 bytes
+ uint64 timestamp; // 8 bytes
+ CtExtensions extensions; // 2 bytes len field, + n bytes data
+ digitally-signed struct { // 1 byte hash alg, 1 byte sig alg, n bytes signature
+ Version sct_version;
+ SignatureType signature_type = certificate_timestamp;
+ uint64 timestamp;
+ LogEntryType entry_type;
+ select(entry_type) {
+ case x509_entry: ASN.1Cert;
+ case precert_entry: PreCert;
+ } signed_entry;
+ CtExtensions extensions;
+ };
+} SignedCertificateTimestamp;
+
+***/
+
+static const
+SecAsn1Oid *oidForSigAlg(SSL_HashAlgorithm hash, SSL_SignatureAlgorithm alg)
+{
+ switch(alg) {
+ case SSL_SignatureAlgorithmRSA:
+ switch (hash) {
+ case SSL_HashAlgorithmSHA1:
+ return &CSSMOID_SHA1WithRSA;
+ case SSL_HashAlgorithmSHA256:
+ return &CSSMOID_SHA256WithRSA;
+ case SSL_HashAlgorithmSHA384:
+ return &CSSMOID_SHA384WithRSA;
+ default:
+ break;
+ }
+ case SSL_SignatureAlgorithmECDSA:
+ switch (hash) {
+ case SSL_HashAlgorithmSHA1:
+ return &CSSMOID_ECDSA_WithSHA1;
+ case SSL_HashAlgorithmSHA256:
+ return &CSSMOID_ECDSA_WithSHA256;
+ case SSL_HashAlgorithmSHA384:
+ return &CSSMOID_ECDSA_WithSHA384;
+ default:
+ break;
+ }
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+
+static size_t SSLDecodeUint16(const uint8_t *p)
+{
+ return (p[0]<<8 | p[1]);
+}
+
+static uint8_t *SSLEncodeUint16(uint8_t *p, size_t len)
+{
+ p[0] = (len >> 8)&0xff;
+ p[1] = (len & 0xff);
+ return p+2;
+}
+
+static uint8_t *SSLEncodeUint24(uint8_t *p, size_t len)
+{
+ p[0] = (len >> 16)&0xff;
+ p[1] = (len >> 8)&0xff;
+ p[2] = (len & 0xff);
+ return p+3;
+}
+
+
+static
+uint64_t SSLDecodeUint64(const uint8_t *p)
+{
+ uint64_t u = 0;
+ for(int i=0; i<8; i++) {
+ u=(u<<8)|p[0];
+ p++;
+ }
+ return u;
+}
+
+
+static CFDataRef copy_x509_entry_from_chain(SecPVCRef pvc)
+{
+ SecCertificateRef leafCert = SecPVCGetCertificateAtIndex(pvc, 0);
+
+ CFMutableDataRef data = CFDataCreateMutable(kCFAllocatorDefault, 3+SecCertificateGetLength(leafCert));
+
+ CFDataSetLength(data, 3+SecCertificateGetLength(leafCert));
+
+ uint8_t *q = CFDataGetMutableBytePtr(data);
+ q = SSLEncodeUint24(q, SecCertificateGetLength(leafCert));
+ memcpy(q, SecCertificateGetBytePtr(leafCert), SecCertificateGetLength(leafCert));
+
+ return data;
+}
+
+
+static CFDataRef copy_precert_entry_from_chain(SecPVCRef pvc)
+{
+ SecCertificateRef leafCert = NULL;
+ SecCertificateRef issuer = NULL;
+ CFDataRef issuerKeyHash = NULL;
+ CFDataRef tbs_precert = NULL;
+ CFMutableDataRef data= NULL;
+
+ require_quiet(SecPVCGetCertificateCount(pvc)>=2, out); //we need the issuer key for precerts.
+ leafCert = SecPVCGetCertificateAtIndex(pvc, 0);
+ issuer = SecPVCGetCertificateAtIndex(pvc, 1);
+
+ require(leafCert, out);
+ require(issuer, out); // Those two would likely indicate an internal error, since we already checked the chain length above.
+ issuerKeyHash = SecCertificateCopySubjectPublicKeyInfoSHA256Digest(issuer);
+ tbs_precert = SecCertificateCopyPrecertTBS(leafCert);
+
+ require(issuerKeyHash, out);
+ require(tbs_precert, out);
+ data = CFDataCreateMutable(kCFAllocatorDefault, CFDataGetLength(issuerKeyHash) + 3 + CFDataGetLength(tbs_precert));
+ CFDataSetLength(data, CFDataGetLength(issuerKeyHash) + 3 + CFDataGetLength(tbs_precert));
+
+ uint8_t *q = CFDataGetMutableBytePtr(data);
+ memcpy(q, CFDataGetBytePtr(issuerKeyHash), CFDataGetLength(issuerKeyHash)); q += CFDataGetLength(issuerKeyHash); // issuer key hash
+ q = SSLEncodeUint24(q, CFDataGetLength(tbs_precert));
+ memcpy(q, CFDataGetBytePtr(tbs_precert), CFDataGetLength(tbs_precert));
+
+out:
+ CFReleaseSafe(issuerKeyHash);
+ CFReleaseSafe(tbs_precert);
+ return data;
+}
+
+static
+CFAbsoluteTime TimestampToCFAbsoluteTime(uint64_t ts)
+{
+ return (ts / 1000) - kCFAbsoluteTimeIntervalSince1970;
+}
+
+static
+uint64_t TimestampFromCFAbsoluteTime(CFAbsoluteTime at)
+{
+ return (uint64_t)(at + kCFAbsoluteTimeIntervalSince1970) * 1000;
+}
+
+static bool isSCTValidForLogData(CFDictionaryRef logData, int entry_type, CFAbsoluteTime sct_time, CFAbsoluteTime cert_expiry_date) {
+ /* only embedded SCTs can be used from retired logs. */
+ if(entry_type==kSecCTEntryTypeCert && CFDictionaryContainsKey(logData, kSecCTRetirementDateKey)) {
+ return false;
+ }
+
+ /* SCTs from after the transition to read-only are not valid (and indicate a operator failure) */
+ CFDateRef frozen_date = CFDictionaryGetValue(logData, kSecCTReadOnlyDateKey);
+ if (frozen_date && (sct_time > CFDateGetAbsoluteTime(frozen_date))) {
+ secerror("Frozen CT log issued SCT after freezing (log=%@)\n", logData);
+ return false;
+ }
+
+ /* If the log is temporally sharded, the certificate expiry date must be within the temporal shard window */
+ CFDateRef start_inclusive = CFDictionaryGetValue(logData, kSecCTShardStartDateKey);
+ CFDateRef end_exclusive = CFDictionaryGetValue(logData, kSecCTShardEndDateKey);
+ if (start_inclusive && (cert_expiry_date < CFDateGetAbsoluteTime(start_inclusive))) {
+ return false;
+ }
+ if (end_exclusive && (cert_expiry_date >= CFDateGetAbsoluteTime(end_exclusive))) {
+ return false;
+ }
+
+ return true;
+}
+
+
+/*
+ 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'.
+ - If entry_type = 0, the log must be currently qualified.
+ - If entry_type = 1, the log may be expired.
+
+ 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.
+
+ */
+static CFDictionaryRef getSCTValidatingLog(CFDataRef sct, int entry_type, CFDataRef entry, uint64_t vt, CFAbsoluteTime cert_expiry_date, CFDictionaryRef trustedLogs, CFAbsoluteTime *sct_at)
+{
+ uint8_t version;
+ const uint8_t *logID;
+ const uint8_t *timestampData;
+ uint64_t timestamp;
+ size_t extensionsLen;
+ const uint8_t *extensionsData;
+ uint8_t hashAlg;
+ uint8_t sigAlg;
+ size_t signatureLen;
+ const uint8_t *signatureData;
+ 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);
+
+ require(len>=43, out);
+
+ version = p[0]; p++; len--;
+ logID = p; p+=32; len-=32;
+ timestampData = p; p+=8; len-=8;
+ extensionsLen = SSLDecodeUint16(p); p+=2; len-=2;
+
+ require(len>=extensionsLen, out);
+ extensionsData = p; p+=extensionsLen; len-=extensionsLen;
+
+ require(len>=4, out);
+ hashAlg=p[0]; p++; len--;
+ sigAlg=p[0]; p++; len--;
+ signatureLen = SSLDecodeUint16(p); p+=2; len-=2;
+ require(len==signatureLen, out); /* We do not tolerate any extra data after the signature */
+ signatureData = p;
+
+ /* verify version: only v1(0) is supported */
+ if(version!=0) {
+ secerror("SCT version unsupported: %d\n", version);
+ goto out;
+ }
+
+ /* verify timestamp not in the future */
+ timestamp = SSLDecodeUint64(timestampData);
+ if(timestamp > vt) {
+ secerror("SCT is in the future: %llu > %llu\n", timestamp, vt);
+ goto out;
+ }
+
+ uint8_t *q;
+
+ /* signed entry */
+ size_t signed_data_len = 12 + CFDataGetLength(entry) + 2 + extensionsLen ;
+ signed_data = malloc(signed_data_len);
+ require(signed_data, out);
+ q = signed_data;
+ *q++ = version;
+ *q++ = 0; // certificate_timestamp
+ memcpy(q, timestampData, 8); q+=8;
+ q = SSLEncodeUint16(q, entry_type); // logentry type: 0=cert 1=precert
+ memcpy(q, CFDataGetBytePtr(entry), CFDataGetLength(entry)); q += CFDataGetLength(entry);
+ q = SSLEncodeUint16(q, extensionsLen);
+ memcpy(q, extensionsData, extensionsLen);
+
+ logIDData = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, logID, 32, kCFAllocatorNull);
+
+ CFDictionaryRef logData = CFDictionaryGetValue(trustedLogs, logIDData);
+ CFAbsoluteTime sct_time = TimestampToCFAbsoluteTime(timestamp);
+ require(logData && isSCTValidForLogData(logData, entry_type, sct_time, cert_expiry_date), out);
+
+ CFDataRef logKeyData = CFDictionaryGetValue(logData, kSecCTPublicKeyKey);
+ require(logKeyData, out); // This failing would be an internal logic error
+ pubKey = SecKeyCreateFromSubjectPublicKeyInfoData(kCFAllocatorDefault, logKeyData);
+ require(pubKey, out);
+
+ oid = oidForSigAlg(hashAlg, sigAlg);
+ require(oid, out);
+
+ algId.algorithm = *oid;
+ algId.parameters.Data = NULL;
+ algId.parameters.Length = 0;
+
+ if(SecKeyDigestAndVerify(pubKey, &algId, signed_data, signed_data_len, signatureData, signatureLen)==0) {
+ *sct_at = sct_time;
+ 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;
+ SecCertificateRef leafCert = NULL;
+ SecCertificateRef issuer = NULL;
+ CFArrayRef ocspResponsesData = NULL;
+ SecOCSPRequestRef ocspRequest = NULL;
+
+ ocspResponsesData = SecPathBuilderCopyOCSPResponses(pvc->builder);
+ require_quiet(ocspResponsesData, out);
+
+ require_quiet(SecPVCGetCertificateCount(pvc)>=2, out); //we need the issuer key for precerts.
+ leafCert = SecPVCGetCertificateAtIndex(pvc, 0);
+ issuer = SecPVCGetCertificateAtIndex(pvc, 1);
+
+ require(leafCert, out);
+ require(issuer, out); // not quiet: Those two would likely indicate an internal error, since we already checked the chain length above.
+ ocspRequest = SecOCSPRequestCreate(leafCert, issuer);
+
+ SCTs = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
+ require(SCTs, out);
+
+ CFArrayForEach(ocspResponsesData, ^(const void *value) {
+ /* TODO: Should the builder already have the appropriate SecOCSPResponseRef ? */
+ SecOCSPResponseRef ocspResponse = SecOCSPResponseCreate(value);
+ if(ocspResponse && SecOCSPGetResponseStatus(ocspResponse)==kSecOCSPSuccess) {
+ SecOCSPSingleResponseRef ocspSingleResponse = SecOCSPResponseCopySingleResponse(ocspResponse, ocspRequest);
+ if(ocspSingleResponse) {
+ CFArrayRef singleResponseSCTs = SecOCSPSingleResponseCopySCTs(ocspSingleResponse);
+ if(singleResponseSCTs) {
+ CFArrayAppendArray(SCTs, singleResponseSCTs, CFRangeMake(0, CFArrayGetCount(singleResponseSCTs)));
+ CFRelease(singleResponseSCTs);
+ }
+ SecOCSPSingleResponseDestroy(ocspSingleResponse);
+ }
+ }
+ if(ocspResponse) SecOCSPResponseFinalize(ocspResponse);
+ });
+
+ if(CFArrayGetCount(SCTs)==0) {
+ CFReleaseNull(SCTs);
+ }
+
+out:
+ CFReleaseSafe(ocspResponsesData);
+ if(ocspRequest)
+ SecOCSPRequestFinalize(ocspRequest);
+
+ return SCTs;
+}
+
+static bool find_validating_logs(SecPVCRef pvc, CFDictionaryRef trustedLogs,
+ CFDictionaryRef CF_RETURNS_RETAINED * _Nonnull outCurrentLogsValidatingScts,
+ CFDictionaryRef CF_RETURNS_RETAINED * _Nonnull outLogsValidatingEmbeddedScts,
+ bool * _Nonnull out_at_least_one_currently_valid_external,
+ bool * _Nonnull out_at_least_one_currently_valid_embedded) {
+ SecCertificateRef leafCert = SecPVCGetCertificateAtIndex(pvc, 0);
+ CFArrayRef embeddedScts = SecCertificateCopySignedCertificateTimestamps(leafCert);
+ CFArrayRef builderScts = SecPathBuilderCopySignedCertificateTimestamps(pvc->builder);
+ CFArrayRef ocspScts = copy_ocsp_scts(pvc);
+ CFDataRef precertEntry = copy_precert_entry_from_chain(pvc);
+ CFDataRef x509Entry = copy_x509_entry_from_chain(pvc);
+ __block CFAbsoluteTime certExpiry = SecCertificateNotValidAfter(leafCert);
+ bool result = NO;
+
+ // This eventually contain list of logs who validated the SCT.
+ CFMutableDictionaryRef currentLogsValidatingScts = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ CFMutableDictionaryRef logsValidatingEmbeddedScts = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+
+ uint64_t vt = TimestampFromCFAbsoluteTime(SecPVCGetVerifyTime(pvc));
+
+ __block bool at_least_one_currently_valid_external = 0;
+ __block bool at_least_one_currently_valid_embedded = 0;
+
+ require(logsValidatingEmbeddedScts, out);
+ require(currentLogsValidatingScts, out);
+
+ /* Skip if there are no SCTs. */
+ bool no_scts = (embeddedScts && CFArrayGetCount(embeddedScts) > 0) ||
+ (builderScts && CFArrayGetCount(builderScts) > 0) ||
+ (ocspScts && CFArrayGetCount(ocspScts) > 0);
+ require_quiet(no_scts, out);
+
+ if(trustedLogs && CFDictionaryGetCount(trustedLogs) > 0) { // 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){
+ CFAbsoluteTime sct_at;
+ CFDictionaryRef log = getSCTValidatingLog(value, 1, precertEntry, vt, certExpiry, trustedLogs, &sct_at);
+ if(log) {
+ addValidatingLog(logsValidatingEmbeddedScts, log, sct_at);
+ if(!CFDictionaryContainsKey(log, kSecCTRetirementDateKey)) {
+ 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){
+ CFAbsoluteTime sct_at;
+ CFDictionaryRef log = getSCTValidatingLog(value, 0, x509Entry, vt, certExpiry, 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){
+ CFAbsoluteTime sct_at;
+ CFDictionaryRef log = getSCTValidatingLog(value, 0, x509Entry, vt, certExpiry, trustedLogs, &sct_at);
+ if(log) {
+ addValidatingLog(currentLogsValidatingScts, log, sct_at);
+ at_least_one_currently_valid_external = true;
+ }
+ });
+ }
+ }
+
+ if (CFDictionaryGetCount(currentLogsValidatingScts) > 0) {
+ result = true;
+ *outCurrentLogsValidatingScts = CFRetainSafe(currentLogsValidatingScts);
+ *outLogsValidatingEmbeddedScts = CFRetainSafe(logsValidatingEmbeddedScts);
+ *out_at_least_one_currently_valid_embedded = at_least_one_currently_valid_embedded;
+ *out_at_least_one_currently_valid_external = at_least_one_currently_valid_external;
+ }
+
+out:
+ CFReleaseSafe(logsValidatingEmbeddedScts);
+ CFReleaseSafe(currentLogsValidatingScts);
+ CFReleaseSafe(builderScts);
+ CFReleaseSafe(embeddedScts);
+ CFReleaseSafe(ocspScts);
+ CFReleaseSafe(precertEntry);
+ CFReleaseSafe(x509Entry);
+ return result;
+}
+
+static bool verify_tls_ct_policy(SecPVCRef pvc, CFDictionaryRef currentLogsValidatingScts, CFDictionaryRef logsValidatingEmbeddedScts,
+ bool at_least_one_currently_valid_external, bool at_least_one_currently_valid_embedded,
+ CFIndex * _Nonnull outTrustedSCTCount)
+{
+ if (!logsValidatingEmbeddedScts || !currentLogsValidatingScts) {
+ return false;
+ }
+
+ __block CFAbsoluteTime issuanceTime = SecPVCGetVerifyTime(pvc);
+ SecCertificateRef leafCert = SecPVCGetCertificateAtIndex(pvc, 0);
+ __block CFIndex trustedSCTCount = CFDictionaryGetCount(currentLogsValidatingScts);
+
+ /* We now have 2 sets of logs that validated those SCTS, count them and make a final decision.
+
+ 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.
+
+ B1: SCTs from 2 currently valid logs (from any source)
+ B2: At least 1 external SCT from a currently valid log.
+
+ */
+
+ bool hasValidExternalSCT = (at_least_one_currently_valid_external && CFDictionaryGetCount(currentLogsValidatingScts)>=2);
+ bool hasValidEmbeddedSCT = (at_least_one_currently_valid_embedded);
+ SecCertificatePathVCRef path = SecPathBuilderGetPath(pvc->builder);
+ bool result = false;
+
+ if (hasValidEmbeddedSCT) {
+ /* Calculate issuance time based on timestamp of SCTs from current logs */
+ CFDictionaryForEach(currentLogsValidatingScts, ^(const void *key, const void *value) {
+ CFDictionaryRef log = key;
+ if(!CFDictionaryContainsKey(log, kSecCTRetirementDateKey)) {
+ // Log is still qualified
+ CFDateRef ts = (CFDateRef) value;
+ CFAbsoluteTime timestamp = CFDateGetAbsoluteTime(ts);
+ if(timestamp < issuanceTime) {
+ issuanceTime = timestamp;
+ }
+ }
+ });
+ SecCertificatePathVCSetIssuanceTime(path, issuanceTime);
+ }
+ if (hasValidExternalSCT) {
+ /* Note: since external SCT validates this cert, we do not need to
+ override issuance time here. If the cert also has a valid embedded
+ SCT, issuanceTime will be calculated and set in the block above. */
+ result = true;
+ } else if (hasValidEmbeddedSCT) {
+ __block int lifetime; // in Months
+ __block unsigned once_or_current_qualified_embedded = 0;
+
+ /* Count Logs */
+ __block bool failed_once_check = false;
+ CFDictionaryForEach(logsValidatingEmbeddedScts, ^(const void *key, const void *value) {
+ CFDictionaryRef log = key;
+ CFDateRef ts = value;
+ CFDateRef expiry = CFDictionaryGetValue(log, kSecCTRetirementDateKey);
+ if (expiry == NULL) { // Currently qualified OR
+ once_or_current_qualified_embedded++;
+ } else if (CFDateCompare(ts, expiry, NULL) == kCFCompareLessThan && // Once qualified. That is, qualified at the time of SCT AND
+ issuanceTime < CFDateGetAbsoluteTime(expiry)) { // at the time of issuance.)
+ once_or_current_qualified_embedded++;
+ trustedSCTCount++;
+ } else {
+ failed_once_check = true;
+ }
+ });
+
+ 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;
+ }
+
+ if(once_or_current_qualified_embedded >= requiredEmbeddedSctsCount){
+ result = true;
+ }
+ }
+
+ *outTrustedSCTCount = trustedSCTCount;
+ return result;
+}
+
+static void report_ct_analytics(SecPVCRef pvc, CFDictionaryRef currentLogsValidatingScts, CFIndex trustedSCTCount)
+{
+ /* Record analytics data for CT */
+ TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(pvc->builder);
+ if (!analytics) {
+ return;
+ }
+
+ SecCertificateRef leafCert = SecPVCGetCertificateAtIndex(pvc, 0);
+ CFArrayRef embeddedScts = SecCertificateCopySignedCertificateTimestamps(leafCert);
+ CFArrayRef builderScts = SecPathBuilderCopySignedCertificateTimestamps(pvc->builder);
+ CFArrayRef ocspScts = copy_ocsp_scts(pvc);
+
+ uint32_t sctCount = 0;
+ /* Count the total number of SCTs we found and report where we got them */
+ if (embeddedScts && CFArrayGetCount(embeddedScts) > 0) {
+ analytics->sct_sources |= TA_SCTEmbedded;
+ sctCount += CFArrayGetCount(embeddedScts);
+ }
+ if (builderScts && CFArrayGetCount(builderScts) > 0) {
+ analytics->sct_sources |= TA_SCT_TLS;
+ sctCount += CFArrayGetCount(builderScts);
+ }
+ if (ocspScts && CFArrayGetCount(ocspScts) > 0) {
+ analytics->sct_sources |= TA_SCT_OCSP;
+ sctCount += CFArrayGetCount(ocspScts);
+ }
+ /* Report how many of those SCTs were once or currently qualified */
+ analytics->number_trusted_scts = (uint32_t)trustedSCTCount;
+ /* Report how many SCTs we got */
+ analytics->number_scts = sctCount;
+ /* Only one current SCT -- close to failure */
+ if (CFDictionaryGetCount(currentLogsValidatingScts) == 1) {
+ analytics->ct_one_current = true;
+ }
+
+ CFReleaseNull(embeddedScts);
+ CFReleaseNull(builderScts);
+ CFReleaseNull(ocspScts);
+}
+
+void SecPolicyCheckCT(SecPVCRef pvc)
+{
+ CFDictionaryRef trustedLogs = SecPathBuilderCopyTrustedLogs(pvc->builder);
+ if (!trustedLogs) {
+ SecOTAPKIRef otapkiref = SecOTAPKICopyCurrentOTAPKIRef();
+ trustedLogs = SecOTAPKICopyTrustedCTLogs(otapkiref);
+ CFReleaseSafe(otapkiref);
+ }
+
+ CFDictionaryRef currentLogsValidatingScts = NULL;
+ CFDictionaryRef logsValidatingEmbeddedScts = NULL;
+ bool at_least_one_currently_valid_external = false;
+ bool at_least_one_currently_valid_embedded = false;
+ CFIndex trustedSCTCount = 0;
+ SecCertificatePathVCRef path = SecPathBuilderGetPath(pvc->builder);
+
+ SecCertificatePathVCSetIsCT(path, false);
+ if (find_validating_logs(pvc, trustedLogs, ¤tLogsValidatingScts, &logsValidatingEmbeddedScts, &at_least_one_currently_valid_external, &at_least_one_currently_valid_embedded)) {
+ if (verify_tls_ct_policy(pvc, currentLogsValidatingScts, logsValidatingEmbeddedScts, at_least_one_currently_valid_external, at_least_one_currently_valid_embedded, &trustedSCTCount)) {
+ SecCertificatePathVCSetIsCT(path, true);
+ }
+ report_ct_analytics(pvc, currentLogsValidatingScts, trustedSCTCount);
+ }
+
+ CFReleaseNull(currentLogsValidatingScts);
+ CFReleaseNull(logsValidatingEmbeddedScts);
+ CFReleaseNull(trustedLogs);
+}
+
+bool SecPolicyCheckNonTlsCT(SecPVCRef pvc)
+{
+ CFDictionaryRef trustedLogs = SecPathBuilderCopyTrustedLogs(pvc->builder);
+ if (!trustedLogs) {
+ SecOTAPKIRef otapkiref = SecOTAPKICopyCurrentOTAPKIRef();
+ trustedLogs = SecOTAPKICopyNonTlsTrustedCTLogs(otapkiref);
+ CFReleaseSafe(otapkiref);
+ }
+
+ CFDictionaryRef currentLogsValidatingScts = NULL;
+ CFDictionaryRef logsValidatingEmbeddedScts = NULL;
+ bool at_least_one_currently_valid_external = false;
+ bool at_least_one_currently_valid_embedded = false;
+ CFIndex trustedSCTCount = 0;
+ SecCertificatePathVCRef path = SecPathBuilderGetPath(pvc->builder);
+ bool result = false;
+
+ SecCertificatePathVCSetIsCT(path, false);
+ if (find_validating_logs(pvc, trustedLogs, ¤tLogsValidatingScts, &logsValidatingEmbeddedScts, &at_least_one_currently_valid_external, &at_least_one_currently_valid_embedded)) {
+ if (verify_tls_ct_policy(pvc, currentLogsValidatingScts, logsValidatingEmbeddedScts, at_least_one_currently_valid_external, at_least_one_currently_valid_embedded, &trustedSCTCount)) {
+ result = true;
+ }
+ report_ct_analytics(pvc, currentLogsValidatingScts, trustedSCTCount);
+ }
+
+ CFReleaseNull(currentLogsValidatingScts);
+ CFReleaseNull(logsValidatingEmbeddedScts);
+ CFReleaseNull(trustedLogs);
+ return result;
+}