Add PolicyImplTest for Certificate Transparency
A new FakeX509Certificate class is added, based on MockX509Certificate
from AOSP SslCertificateTest.
Bug: 319829948
Test: atest CtsLibcoreTestCases:com.android.org.conscrypt.ct.PolicyImplTest
Change-Id: Ib9ae572bb873eea39b7f394d4e558f7acc78eb48
diff --git a/platform/src/test/java/org/conscrypt/ct/PolicyImplTest.java b/platform/src/test/java/org/conscrypt/ct/PolicyImplTest.java
new file mode 100644
index 0000000..7322999
--- /dev/null
+++ b/platform/src/test/java/org/conscrypt/ct/PolicyImplTest.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.conscrypt.ct;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.conscrypt.java.security.cert.FakeX509Certificate;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.security.PublicKey;
+import java.security.cert.X509Certificate;
+
+@RunWith(JUnit4.class)
+public class PolicyImplTest {
+ private static LogInfo usableOp1Log1;
+ private static LogInfo usableOp1Log2;
+ private static LogInfo retiredOp1Log;
+ private static LogInfo usableOp2Log;
+ private static LogInfo retiredOp2Log;
+ private static SignedCertificateTimestamp embeddedSCT;
+
+ private static class FakePublicKey implements PublicKey {
+ static final long serialVersionUID = 1;
+ final byte[] key;
+
+ FakePublicKey(byte[] key) {
+ this.key = key;
+ }
+
+ @Override
+ public byte[] getEncoded() {
+ return this.key;
+ }
+
+ @Override
+ public String getAlgorithm() {
+ return "";
+ }
+
+ @Override
+ public String getFormat() {
+ return "";
+ }
+ }
+
+ @BeforeClass
+ public static void setUp() {
+ /* Defines LogInfo for the tests. Only a subset of the attributes are
+ * expected to be used, namely the LogID (based on the public key), the
+ * operator name and the log state.
+ */
+ usableOp1Log1 = new LogInfo.Builder()
+ .setPublicKey(new FakePublicKey(new byte[] {0x01}))
+ .setUrl("")
+ .setOperator("operator 1")
+ .setState(LogInfo.STATE_USABLE)
+ .build();
+ usableOp1Log2 = new LogInfo.Builder()
+ .setPublicKey(new FakePublicKey(new byte[] {0x02}))
+ .setUrl("")
+ .setOperator("operator 1")
+ .setState(LogInfo.STATE_USABLE)
+ .build();
+ retiredOp1Log = new LogInfo.Builder()
+ .setPublicKey(new FakePublicKey(new byte[] {0x03}))
+ .setUrl("")
+ .setOperator("operator 1")
+ .setState(LogInfo.STATE_RETIRED)
+ .build();
+ usableOp2Log = new LogInfo.Builder()
+ .setPublicKey(new FakePublicKey(new byte[] {0x04}))
+ .setUrl("")
+ .setOperator("operator 2")
+ .setState(LogInfo.STATE_USABLE)
+ .build();
+ retiredOp2Log = new LogInfo.Builder()
+ .setPublicKey(new FakePublicKey(new byte[] {0x05}))
+ .setUrl("")
+ .setOperator("operator 2")
+ .setState(LogInfo.STATE_RETIRED)
+ .build();
+ /* Only the origin of the SCT is used during the evaluation for policy
+ * compliance. The signature is validated at the previous step (see
+ * the Verifier class).
+ */
+ embeddedSCT = new SignedCertificateTimestamp(SignedCertificateTimestamp.Version.V1, null, 0,
+ null, null, SignedCertificateTimestamp.Origin.EMBEDDED);
+ }
+
+ @Test
+ public void emptyVerificationResult() throws Exception {
+ Policy p = new PolicyImpl();
+ VerificationResult result = new VerificationResult();
+
+ X509Certificate leaf = new FakeX509Certificate();
+ assertFalse("An empty VerificationResult", p.doesResultConformToPolicy(result, leaf));
+ }
+
+ @Test
+ public void validVerificationResult() throws Exception {
+ Policy p = new PolicyImpl();
+
+ VerifiedSCT vsct1 = new VerifiedSCT.Builder(embeddedSCT)
+ .setStatus(VerifiedSCT.Status.VALID)
+ .setLogInfo(usableOp1Log1)
+ .build();
+
+ VerifiedSCT vsct2 = new VerifiedSCT.Builder(embeddedSCT)
+ .setStatus(VerifiedSCT.Status.VALID)
+ .setLogInfo(usableOp2Log)
+ .build();
+
+ VerificationResult result = new VerificationResult();
+ result.add(vsct1);
+ result.add(vsct2);
+
+ X509Certificate leaf = new FakeX509Certificate();
+ assertTrue("Two valid SCTs from different operators",
+ p.doesResultConformToPolicy(result, leaf));
+ }
+
+ @Test
+ public void validWithRetiredVerificationResult() throws Exception {
+ Policy p = new PolicyImpl();
+
+ VerifiedSCT vsct1 = new VerifiedSCT.Builder(embeddedSCT)
+ .setStatus(VerifiedSCT.Status.VALID)
+ .setLogInfo(retiredOp1Log)
+ .build();
+
+ VerifiedSCT vsct2 = new VerifiedSCT.Builder(embeddedSCT)
+ .setStatus(VerifiedSCT.Status.VALID)
+ .setLogInfo(usableOp2Log)
+ .build();
+
+ VerificationResult result = new VerificationResult();
+ result.add(vsct1);
+ result.add(vsct2);
+
+ X509Certificate leaf = new FakeX509Certificate();
+ assertTrue("One valid, one retired SCTs from different operators",
+ p.doesResultConformToPolicy(result, leaf));
+ }
+
+ @Test
+ public void invalidOneSctVerificationResult() throws Exception {
+ Policy p = new PolicyImpl();
+
+ VerifiedSCT vsct1 = new VerifiedSCT.Builder(embeddedSCT)
+ .setStatus(VerifiedSCT.Status.VALID)
+ .setLogInfo(usableOp1Log1)
+ .build();
+
+ VerificationResult result = new VerificationResult();
+ result.add(vsct1);
+
+ X509Certificate leaf = new FakeX509Certificate();
+ assertFalse("One valid SCT", p.doesResultConformToPolicy(result, leaf));
+ }
+
+ @Test
+ public void invalidTwoSctsVerificationResult() throws Exception {
+ Policy p = new PolicyImpl();
+
+ VerifiedSCT vsct1 = new VerifiedSCT.Builder(embeddedSCT)
+ .setStatus(VerifiedSCT.Status.VALID)
+ .setLogInfo(retiredOp1Log)
+ .build();
+
+ VerifiedSCT vsct2 = new VerifiedSCT.Builder(embeddedSCT)
+ .setStatus(VerifiedSCT.Status.VALID)
+ .setLogInfo(retiredOp2Log)
+ .build();
+
+ VerificationResult result = new VerificationResult();
+ result.add(vsct1);
+ result.add(vsct2);
+
+ X509Certificate leaf = new FakeX509Certificate();
+ assertFalse("Two retired SCTs from different operators",
+ p.doesResultConformToPolicy(result, leaf));
+ }
+}
diff --git a/repackaged/platform/src/test/java/com/android/org/conscrypt/ct/PolicyImplTest.java b/repackaged/platform/src/test/java/com/android/org/conscrypt/ct/PolicyImplTest.java
new file mode 100644
index 0000000..030f670
--- /dev/null
+++ b/repackaged/platform/src/test/java/com/android/org/conscrypt/ct/PolicyImplTest.java
@@ -0,0 +1,205 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.org.conscrypt.ct;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.org.conscrypt.java.security.cert.FakeX509Certificate;
+import java.security.PublicKey;
+import java.security.cert.X509Certificate;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+@RunWith(JUnit4.class)
+public class PolicyImplTest {
+ private static LogInfo usableOp1Log1;
+ private static LogInfo usableOp1Log2;
+ private static LogInfo retiredOp1Log;
+ private static LogInfo usableOp2Log;
+ private static LogInfo retiredOp2Log;
+ private static SignedCertificateTimestamp embeddedSCT;
+
+ private static class FakePublicKey implements PublicKey {
+ static final long serialVersionUID = 1;
+ final byte[] key;
+
+ FakePublicKey(byte[] key) {
+ this.key = key;
+ }
+
+ @Override
+ public byte[] getEncoded() {
+ return this.key;
+ }
+
+ @Override
+ public String getAlgorithm() {
+ return "";
+ }
+
+ @Override
+ public String getFormat() {
+ return "";
+ }
+ }
+
+ @BeforeClass
+ public static void setUp() {
+ /* Defines LogInfo for the tests. Only a subset of the attributes are
+ * expected to be used, namely the LogID (based on the public key), the
+ * operator name and the log state.
+ */
+ usableOp1Log1 = new LogInfo.Builder()
+ .setPublicKey(new FakePublicKey(new byte[] {0x01}))
+ .setUrl("")
+ .setOperator("operator 1")
+ .setState(LogInfo.STATE_USABLE)
+ .build();
+ usableOp1Log2 = new LogInfo.Builder()
+ .setPublicKey(new FakePublicKey(new byte[] {0x02}))
+ .setUrl("")
+ .setOperator("operator 1")
+ .setState(LogInfo.STATE_USABLE)
+ .build();
+ retiredOp1Log = new LogInfo.Builder()
+ .setPublicKey(new FakePublicKey(new byte[] {0x03}))
+ .setUrl("")
+ .setOperator("operator 1")
+ .setState(LogInfo.STATE_RETIRED)
+ .build();
+ usableOp2Log = new LogInfo.Builder()
+ .setPublicKey(new FakePublicKey(new byte[] {0x04}))
+ .setUrl("")
+ .setOperator("operator 2")
+ .setState(LogInfo.STATE_USABLE)
+ .build();
+ retiredOp2Log = new LogInfo.Builder()
+ .setPublicKey(new FakePublicKey(new byte[] {0x05}))
+ .setUrl("")
+ .setOperator("operator 2")
+ .setState(LogInfo.STATE_RETIRED)
+ .build();
+ /* Only the origin of the SCT is used during the evaluation for policy
+ * compliance. The signature is validated at the previous step (see
+ * the Verifier class).
+ */
+ embeddedSCT = new SignedCertificateTimestamp(SignedCertificateTimestamp.Version.V1, null, 0,
+ null, null, SignedCertificateTimestamp.Origin.EMBEDDED);
+ }
+
+ @Test
+ public void emptyVerificationResult() throws Exception {
+ Policy p = new PolicyImpl();
+ VerificationResult result = new VerificationResult();
+
+ X509Certificate leaf = new FakeX509Certificate();
+ assertFalse("An empty VerificationResult", p.doesResultConformToPolicy(result, leaf));
+ }
+
+ @Test
+ public void validVerificationResult() throws Exception {
+ Policy p = new PolicyImpl();
+
+ VerifiedSCT vsct1 = new VerifiedSCT.Builder(embeddedSCT)
+ .setStatus(VerifiedSCT.Status.VALID)
+ .setLogInfo(usableOp1Log1)
+ .build();
+
+ VerifiedSCT vsct2 = new VerifiedSCT.Builder(embeddedSCT)
+ .setStatus(VerifiedSCT.Status.VALID)
+ .setLogInfo(usableOp2Log)
+ .build();
+
+ VerificationResult result = new VerificationResult();
+ result.add(vsct1);
+ result.add(vsct2);
+
+ X509Certificate leaf = new FakeX509Certificate();
+ assertTrue("Two valid SCTs from different operators",
+ p.doesResultConformToPolicy(result, leaf));
+ }
+
+ @Test
+ public void validWithRetiredVerificationResult() throws Exception {
+ Policy p = new PolicyImpl();
+
+ VerifiedSCT vsct1 = new VerifiedSCT.Builder(embeddedSCT)
+ .setStatus(VerifiedSCT.Status.VALID)
+ .setLogInfo(retiredOp1Log)
+ .build();
+
+ VerifiedSCT vsct2 = new VerifiedSCT.Builder(embeddedSCT)
+ .setStatus(VerifiedSCT.Status.VALID)
+ .setLogInfo(usableOp2Log)
+ .build();
+
+ VerificationResult result = new VerificationResult();
+ result.add(vsct1);
+ result.add(vsct2);
+
+ X509Certificate leaf = new FakeX509Certificate();
+ assertTrue("One valid, one retired SCTs from different operators",
+ p.doesResultConformToPolicy(result, leaf));
+ }
+
+ @Test
+ public void invalidOneSctVerificationResult() throws Exception {
+ Policy p = new PolicyImpl();
+
+ VerifiedSCT vsct1 = new VerifiedSCT.Builder(embeddedSCT)
+ .setStatus(VerifiedSCT.Status.VALID)
+ .setLogInfo(usableOp1Log1)
+ .build();
+
+ VerificationResult result = new VerificationResult();
+ result.add(vsct1);
+
+ X509Certificate leaf = new FakeX509Certificate();
+ assertFalse("One valid SCT", p.doesResultConformToPolicy(result, leaf));
+ }
+
+ @Test
+ public void invalidTwoSctsVerificationResult() throws Exception {
+ Policy p = new PolicyImpl();
+
+ VerifiedSCT vsct1 = new VerifiedSCT.Builder(embeddedSCT)
+ .setStatus(VerifiedSCT.Status.VALID)
+ .setLogInfo(retiredOp1Log)
+ .build();
+
+ VerifiedSCT vsct2 = new VerifiedSCT.Builder(embeddedSCT)
+ .setStatus(VerifiedSCT.Status.VALID)
+ .setLogInfo(retiredOp2Log)
+ .build();
+
+ VerificationResult result = new VerificationResult();
+ result.add(vsct1);
+ result.add(vsct2);
+
+ X509Certificate leaf = new FakeX509Certificate();
+ assertFalse("Two retired SCTs from different operators",
+ p.doesResultConformToPolicy(result, leaf));
+ }
+}
diff --git a/repackaged/testing/src/main/java/com/android/org/conscrypt/java/security/cert/FakeX509Certificate.java b/repackaged/testing/src/main/java/com/android/org/conscrypt/java/security/cert/FakeX509Certificate.java
new file mode 100644
index 0000000..f4b55f3
--- /dev/null
+++ b/repackaged/testing/src/main/java/com/android/org/conscrypt/java/security/cert/FakeX509Certificate.java
@@ -0,0 +1,171 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.org.conscrypt.java.security.cert;
+
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Principal;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import java.util.Set;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public class FakeX509Certificate extends X509Certificate {
+ @Override
+ public void checkValidity()
+ throws CertificateExpiredException, CertificateNotYetValidException {}
+
+ @Override
+ public void checkValidity(Date date)
+ throws CertificateExpiredException, CertificateNotYetValidException {}
+
+ @Override
+ public int getBasicConstraints() {
+ return 0;
+ }
+
+ @Override
+ public Principal getIssuerDN() {
+ return new MockPrincipal();
+ }
+
+ @Override
+ public boolean[] getIssuerUniqueID() {
+ return null;
+ }
+
+ @Override
+ public boolean[] getKeyUsage() {
+ return null;
+ }
+
+ @Override
+ public Date getNotAfter() {
+ return new Date(System.currentTimeMillis());
+ }
+
+ @Override
+ public Date getNotBefore() {
+ return new Date(System.currentTimeMillis() - 1000);
+ }
+
+ @Override
+ public BigInteger getSerialNumber() {
+ return null;
+ }
+
+ @Override
+ public String getSigAlgName() {
+ return null;
+ }
+
+ @Override
+ public String getSigAlgOID() {
+ return null;
+ }
+
+ @Override
+ public byte[] getSigAlgParams() {
+ return null;
+ }
+
+ @Override
+ public byte[] getSignature() {
+ return null;
+ }
+
+ @Override
+ public Principal getSubjectDN() {
+ return new MockPrincipal();
+ }
+
+ class MockPrincipal implements Principal {
+ public String getName() {
+ return null;
+ }
+ }
+ @Override
+ public boolean[] getSubjectUniqueID() {
+ return null;
+ }
+
+ @Override
+ public byte[] getTBSCertificate() throws CertificateEncodingException {
+ return null;
+ }
+
+ @Override
+ public int getVersion() {
+ return 0;
+ }
+
+ @Override
+ public byte[] getEncoded() throws CertificateEncodingException {
+ return null;
+ }
+
+ @Override
+ public PublicKey getPublicKey() {
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return null;
+ }
+
+ @Override
+ public void verify(PublicKey key)
+ throws CertificateException, NoSuchAlgorithmException, InvalidKeyException,
+ NoSuchProviderException, SignatureException {}
+
+ @Override
+ public void verify(PublicKey key, String sigProvider)
+ throws CertificateException, NoSuchAlgorithmException, InvalidKeyException,
+ NoSuchProviderException, SignatureException {}
+
+ @Override
+ public Set<String> getCriticalExtensionOIDs() {
+ return null;
+ }
+
+ @Override
+ public byte[] getExtensionValue(String oid) {
+ return null;
+ }
+
+ @Override
+ public Set<String> getNonCriticalExtensionOIDs() {
+ return null;
+ }
+
+ @Override
+ public boolean hasUnsupportedCriticalExtension() {
+ return false;
+ }
+}
diff --git a/testing/src/main/java/org/conscrypt/java/security/cert/FakeX509Certificate.java b/testing/src/main/java/org/conscrypt/java/security/cert/FakeX509Certificate.java
new file mode 100644
index 0000000..ed61cc4
--- /dev/null
+++ b/testing/src/main/java/org/conscrypt/java/security/cert/FakeX509Certificate.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.conscrypt.java.security.cert;
+
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Principal;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import java.util.Set;
+
+public class FakeX509Certificate extends X509Certificate {
+ @Override
+ public void checkValidity()
+ throws CertificateExpiredException, CertificateNotYetValidException {}
+
+ @Override
+ public void checkValidity(Date date)
+ throws CertificateExpiredException, CertificateNotYetValidException {}
+
+ @Override
+ public int getBasicConstraints() {
+ return 0;
+ }
+
+ @Override
+ public Principal getIssuerDN() {
+ return new MockPrincipal();
+ }
+
+ @Override
+ public boolean[] getIssuerUniqueID() {
+ return null;
+ }
+
+ @Override
+ public boolean[] getKeyUsage() {
+ return null;
+ }
+
+ @Override
+ public Date getNotAfter() {
+ return new Date(System.currentTimeMillis());
+ }
+
+ @Override
+ public Date getNotBefore() {
+ return new Date(System.currentTimeMillis() - 1000);
+ }
+
+ @Override
+ public BigInteger getSerialNumber() {
+ return null;
+ }
+
+ @Override
+ public String getSigAlgName() {
+ return null;
+ }
+
+ @Override
+ public String getSigAlgOID() {
+ return null;
+ }
+
+ @Override
+ public byte[] getSigAlgParams() {
+ return null;
+ }
+
+ @Override
+ public byte[] getSignature() {
+ return null;
+ }
+
+ @Override
+ public Principal getSubjectDN() {
+ return new MockPrincipal();
+ }
+
+ class MockPrincipal implements Principal {
+ public String getName() {
+ return null;
+ }
+ }
+ @Override
+ public boolean[] getSubjectUniqueID() {
+ return null;
+ }
+
+ @Override
+ public byte[] getTBSCertificate() throws CertificateEncodingException {
+ return null;
+ }
+
+ @Override
+ public int getVersion() {
+ return 0;
+ }
+
+ @Override
+ public byte[] getEncoded() throws CertificateEncodingException {
+ return null;
+ }
+
+ @Override
+ public PublicKey getPublicKey() {
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return null;
+ }
+
+ @Override
+ public void verify(PublicKey key)
+ throws CertificateException, NoSuchAlgorithmException, InvalidKeyException,
+ NoSuchProviderException, SignatureException {}
+
+ @Override
+ public void verify(PublicKey key, String sigProvider)
+ throws CertificateException, NoSuchAlgorithmException, InvalidKeyException,
+ NoSuchProviderException, SignatureException {}
+
+ @Override
+ public Set<String> getCriticalExtensionOIDs() {
+ return null;
+ }
+
+ @Override
+ public byte[] getExtensionValue(String oid) {
+ return null;
+ }
+
+ @Override
+ public Set<String> getNonCriticalExtensionOIDs() {
+ return null;
+ }
+
+ @Override
+ public boolean hasUnsupportedCriticalExtension() {
+ return false;
+ }
+}