Implement Android CT Policy for embedded SCTs
This is the first part of the policy implementation which verifies SCTs
that are embedded in the X509 certificate. A similar second part will be
added to verify SCTs from OCSP or TLS data.
The policy is based on the current Chrome policy[1].
[1] https://googlechrome.github.io/CertificateTransparency/ct_policy.html
Test: atest
Bug: 319829948
Change-Id: If993e9c68afd2fd439838849f85d16d72f909861
diff --git a/platform/src/main/java/org/conscrypt/ct/PolicyImpl.java b/platform/src/main/java/org/conscrypt/ct/PolicyImpl.java
index 95559c1..9e42b77 100644
--- a/platform/src/main/java/org/conscrypt/ct/PolicyImpl.java
+++ b/platform/src/main/java/org/conscrypt/ct/PolicyImpl.java
@@ -19,16 +19,89 @@
import java.security.cert.X509Certificate;
import java.util.HashSet;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
import org.conscrypt.Internal;
@Internal
public class PolicyImpl implements Policy {
@Override
public boolean doesResultConformToPolicy(VerificationResult result, X509Certificate leaf) {
- Set<String> logSet = new HashSet<>();
- for (VerifiedSCT verifiedSCT : result.getValidSCTs()) {
- logSet.add(verifiedSCT.getLogInfo().getOperator());
+ Set<VerifiedSCT> embeddedValidSCTs = new HashSet<>();
+ Set<VerifiedSCT> ocspOrTLSValidSCTs = new HashSet<>();
+ for (VerifiedSCT vsct : result.getValidSCTs()) {
+ if (vsct.getSct().getOrigin() == SignedCertificateTimestamp.Origin.EMBEDDED) {
+ embeddedValidSCTs.add(vsct);
+ } else {
+ ocspOrTLSValidSCTs.add(vsct);
+ }
}
- return logSet.size() >= 2;
+ if (embeddedValidSCTs.size() > 0 && conformEmbeddedSCTs(embeddedValidSCTs, leaf)) {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean conformEmbeddedSCTs(Set<VerifiedSCT> embeddedValidSCTs, X509Certificate leaf) {
+ /* 1. At least one Embedded SCT from a CT Log that was Qualified,
+ * Usable, or ReadOnly at the time of check;
+ */
+ boolean found = false;
+ for (VerifiedSCT vsct : embeddedValidSCTs) {
+ LogInfo log = vsct.getLogInfo();
+ switch (log.getState()) {
+ case LogInfo.STATE_QUALIFIED:
+ case LogInfo.STATE_USABLE:
+ case LogInfo.STATE_READONLY:
+ found = true;
+ }
+ }
+ if (!found) {
+ return false;
+ }
+
+ /* 2. There are Embedded SCTs from at least N distinct CT Logs that
+ * were Qualified, Usable, ReadOnly, or Retired at the time of check,
+ * where N is defined in the following table;
+ *
+ * Certificate Lifetime Number of SCTs from distinct CT Logs
+ * <= 180 days 2
+ * > 180 days 3
+ */
+ Set<LogInfo> validLogs = new HashSet<>();
+ int numberSCTsRequired;
+ long certLifetimeMs = leaf.getNotAfter().getTime() - leaf.getNotBefore().getTime();
+ long certLifetimeDays = TimeUnit.DAYS.convert(certLifetimeMs, TimeUnit.MILLISECONDS);
+ if (certLifetimeDays <= 180) {
+ numberSCTsRequired = 2;
+ } else {
+ numberSCTsRequired = 3;
+ }
+ for (VerifiedSCT vsct : embeddedValidSCTs) {
+ LogInfo log = vsct.getLogInfo();
+ switch (log.getState()) {
+ case LogInfo.STATE_QUALIFIED:
+ case LogInfo.STATE_USABLE:
+ case LogInfo.STATE_READONLY:
+ case LogInfo.STATE_RETIRED:
+ validLogs.add(log);
+ }
+ }
+ if (validLogs.size() < numberSCTsRequired) {
+ return false;
+ }
+
+ /* 3. Among the SCTs satisfying requirements 1 and 2, at least two SCTs
+ * must be issued from distinct CT Log Operators as recognized by
+ * Chrome.
+ */
+ Set<String> operators = new HashSet<>();
+ for (LogInfo logInfo : validLogs) {
+ operators.add(logInfo.getOperator());
+ }
+ if (operators.size() < 2) {
+ return false;
+ }
+
+ return true;
}
}
diff --git a/repackaged/platform/src/main/java/com/android/org/conscrypt/ct/PolicyImpl.java b/repackaged/platform/src/main/java/com/android/org/conscrypt/ct/PolicyImpl.java
index 0ba0c6d..87824c1 100644
--- a/repackaged/platform/src/main/java/com/android/org/conscrypt/ct/PolicyImpl.java
+++ b/repackaged/platform/src/main/java/com/android/org/conscrypt/ct/PolicyImpl.java
@@ -17,10 +17,11 @@
package com.android.org.conscrypt.ct;
+import com.android.org.conscrypt.Internal;
import java.security.cert.X509Certificate;
import java.util.HashSet;
import java.util.Set;
-import com.android.org.conscrypt.Internal;
+import java.util.concurrent.TimeUnit;
/**
* @hide This class is not part of the Android public SDK API
@@ -29,10 +30,82 @@
public class PolicyImpl implements Policy {
@Override
public boolean doesResultConformToPolicy(VerificationResult result, X509Certificate leaf) {
- Set<String> logSet = new HashSet<>();
- for (VerifiedSCT verifiedSCT : result.getValidSCTs()) {
- logSet.add(verifiedSCT.getLogInfo().getOperator());
+ Set<VerifiedSCT> embeddedValidSCTs = new HashSet<>();
+ Set<VerifiedSCT> ocspOrTLSValidSCTs = new HashSet<>();
+ for (VerifiedSCT vsct : result.getValidSCTs()) {
+ if (vsct.getSct().getOrigin() == SignedCertificateTimestamp.Origin.EMBEDDED) {
+ embeddedValidSCTs.add(vsct);
+ } else {
+ ocspOrTLSValidSCTs.add(vsct);
+ }
}
- return logSet.size() >= 2;
+ if (embeddedValidSCTs.size() > 0 && conformEmbeddedSCTs(embeddedValidSCTs, leaf)) {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean conformEmbeddedSCTs(Set<VerifiedSCT> embeddedValidSCTs, X509Certificate leaf) {
+ /* 1. At least one Embedded SCT from a CT Log that was Qualified,
+ * Usable, or ReadOnly at the time of check;
+ */
+ boolean found = false;
+ for (VerifiedSCT vsct : embeddedValidSCTs) {
+ LogInfo log = vsct.getLogInfo();
+ switch (log.getState()) {
+ case LogInfo.STATE_QUALIFIED:
+ case LogInfo.STATE_USABLE:
+ case LogInfo.STATE_READONLY:
+ found = true;
+ }
+ }
+ if (!found) {
+ return false;
+ }
+
+ /* 2. There are Embedded SCTs from at least N distinct CT Logs that
+ * were Qualified, Usable, ReadOnly, or Retired at the time of check,
+ * where N is defined in the following table;
+ *
+ * Certificate Lifetime Number of SCTs from distinct CT Logs
+ * <= 180 days 2
+ * > 180 days 3
+ */
+ Set<LogInfo> validLogs = new HashSet<>();
+ int numberSCTsRequired;
+ long certLifetimeMs = leaf.getNotAfter().getTime() - leaf.getNotBefore().getTime();
+ long certLifetimeDays = TimeUnit.DAYS.convert(certLifetimeMs, TimeUnit.MILLISECONDS);
+ if (certLifetimeDays <= 180) {
+ numberSCTsRequired = 2;
+ } else {
+ numberSCTsRequired = 3;
+ }
+ for (VerifiedSCT vsct : embeddedValidSCTs) {
+ LogInfo log = vsct.getLogInfo();
+ switch (log.getState()) {
+ case LogInfo.STATE_QUALIFIED:
+ case LogInfo.STATE_USABLE:
+ case LogInfo.STATE_READONLY:
+ case LogInfo.STATE_RETIRED:
+ validLogs.add(log);
+ }
+ }
+ if (validLogs.size() < numberSCTsRequired) {
+ return false;
+ }
+
+ /* 3. Among the SCTs satisfying requirements 1 and 2, at least two SCTs
+ * must be issued from distinct CT Log Operators as recognized by
+ * Chrome.
+ */
+ Set<String> operators = new HashSet<>();
+ for (LogInfo logInfo : validLogs) {
+ operators.add(logInfo.getOperator());
+ }
+ if (operators.size() < 2) {
+ return false;
+ }
+
+ return true;
}
}