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;
     }
 }