Upstream multiple AOSP changes. (#1226)
With working tests this time. Passing on OpenJDK with
both isTlsV1Enabled() returning true and file (We should
make that configurable from Conscrypt.Builder).
Contains the following AOSP changes + test fixes:
e6827ba2b Validate LogStore against the Policy
3fe1ea3e9 Filter out SCTs emitted after a log expired
e841e6c0d Use enum as outcome of doesResultConformToPolicy
dbdd64cfd Add PolicyImplTest for Certificate Transparency
97918a0cd Implement Android CT Policy for embedded SCTs
c9f38dbdb Remove logStore attribute from Policy
9d5f0c3aa Fix hashcode for LogInfo
a59840d01 Keep LogInfo in VerifiedSCT
2eb5e7506 Add operator name to LogInfo
d8519cf7e Remove PolicyImpl minimumLogCount argument
92961a569 Remove "CT" prefix from org.conscrypt.ct classes
81d0929eb Use Flags.certificateTransparencyPlatform()
30b81399 Use ByteArray consistently
98f0f2b1 Support parsing CT v3 JSON log list
bb60a900 TrustedCertificateStore: Mitigate NPE when checking updateable certs directory
feacee50 Add State to CTLogInfo
633a2475 Remove ct SystemLogDir
dab378e0 Remove InternalUtil
4c224130 Remove ct fallback logs
9b48e0e5 Add a java_library target for conscrypt-tests
e33c851e Gate tls removal by api level
65e6c8ef Bring back sslv3
c90bd39b Add caching for cert blocklist
5de91066 Read default SHA256 pubkey blocklist
8e656b13 Add support for SHA256 blocklist entries
12c479ac Deprecate the serial-based blocklist
396823df Rename SSL_CONTEXT_ALL
b0825731 Filter protocols when creating SSLParameterImpl
diff --git a/android/src/main/java/org/conscrypt/Platform.java b/android/src/main/java/org/conscrypt/Platform.java
index 64c9769..b212d4d 100644
--- a/android/src/main/java/org/conscrypt/Platform.java
+++ b/android/src/main/java/org/conscrypt/Platform.java
@@ -60,8 +60,8 @@
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.StandardConstants;
import javax.net.ssl.X509TrustManager;
-import org.conscrypt.ct.CTLogStore;
-import org.conscrypt.ct.CTPolicy;
+import org.conscrypt.ct.LogStore;
+import org.conscrypt.ct.Policy;
import org.conscrypt.metrics.CipherSuite;
import org.conscrypt.metrics.ConscryptStatsLog;
import org.conscrypt.metrics.Protocol;
@@ -884,11 +884,11 @@
return null;
}
- static CTLogStore newDefaultLogStore() {
+ static LogStore newDefaultLogStore() {
return null;
}
- static CTPolicy newDefaultPolicy(CTLogStore logStore) {
+ static Policy newDefaultPolicy() {
return null;
}
@@ -955,4 +955,12 @@
public static boolean isTlsV1Deprecated() {
return true;
}
+
+ public static boolean isTlsV1Filtered() {
+ return false;
+ }
+
+ public static boolean isTlsV1Supported() {
+ return false;
+ }
}
diff --git a/common/src/main/java/org/conscrypt/ArrayUtils.java b/common/src/main/java/org/conscrypt/ArrayUtils.java
index d946713..5b4d68f 100644
--- a/common/src/main/java/org/conscrypt/ArrayUtils.java
+++ b/common/src/main/java/org/conscrypt/ArrayUtils.java
@@ -74,4 +74,11 @@
}
return result;
}
+
+ /**
+ * Checks if given array is null or has zero elements.
+ */
+ public static <T> boolean isEmpty(T[] array) {
+ return array == null || array.length == 0;
+ }
}
diff --git a/common/src/main/java/org/conscrypt/ByteArray.java b/common/src/main/java/org/conscrypt/ByteArray.java
index bfc544f..3e97eb5 100644
--- a/common/src/main/java/org/conscrypt/ByteArray.java
+++ b/common/src/main/java/org/conscrypt/ByteArray.java
@@ -21,11 +21,12 @@
/**
* Byte array wrapper for hashtable use. Implements equals() and hashCode().
*/
-final class ByteArray {
+@Internal
+public final class ByteArray {
private final byte[] bytes;
private final int hashCode;
- ByteArray(byte[] bytes) {
+ public ByteArray(byte[] bytes) {
this.bytes = bytes;
this.hashCode = Arrays.hashCode(bytes);
}
@@ -37,6 +38,9 @@
@Override
public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
if (!(o instanceof ByteArray)) {
return false;
}
diff --git a/common/src/main/java/org/conscrypt/NativeCrypto.java b/common/src/main/java/org/conscrypt/NativeCrypto.java
index 1f7005f..5c7d328 100644
--- a/common/src/main/java/org/conscrypt/NativeCrypto.java
+++ b/common/src/main/java/org/conscrypt/NativeCrypto.java
@@ -789,8 +789,9 @@
// --- SSL handling --------------------------------------------------------
static final String OBSOLETE_PROTOCOL_SSLV3 = "SSLv3";
- private static final String DEPRECATED_PROTOCOL_TLSV1 = "TLSv1";
- private static final String DEPRECATED_PROTOCOL_TLSV1_1 = "TLSv1.1";
+ static final String DEPRECATED_PROTOCOL_TLSV1 = "TLSv1";
+ static final String DEPRECATED_PROTOCOL_TLSV1_1 = "TLSv1.1";
+
private static final String SUPPORTED_PROTOCOL_TLSV1_2 = "TLSv1.2";
static final String SUPPORTED_PROTOCOL_TLSV1_3 = "TLSv1.3";
@@ -1030,6 +1031,11 @@
DEPRECATED_PROTOCOL_TLSV1_1,
};
+ private static final String[] SUPPORTED_PROTOCOLS_TLSV1 = Platform.isTlsV1Supported()
+ ? new String[] {
+ DEPRECATED_PROTOCOL_TLSV1,
+ DEPRECATED_PROTOCOL_TLSV1_1,
+ } : new String[0];
/** Protocols to enable by default when "TLSv1.3" is requested. */
static final String[] TLSV13_PROTOCOLS = ArrayUtils.concatValues(
@@ -1053,12 +1059,13 @@
static final String[] TLSV1_PROTOCOLS = TLSV11_PROTOCOLS;
static final String[] DEFAULT_PROTOCOLS = TLSV13_PROTOCOLS;
- private static final String[] SUPPORTED_PROTOCOLS = new String[] {
- DEPRECATED_PROTOCOL_TLSV1,
- DEPRECATED_PROTOCOL_TLSV1_1,
+
+ // If we ever get a new protocol go look for tests which are skipped using
+ // assumeTlsV11Enabled()
+ private static final String[] SUPPORTED_PROTOCOLS = ArrayUtils.concatValues(
+ SUPPORTED_PROTOCOLS_TLSV1,
SUPPORTED_PROTOCOL_TLSV1_2,
- SUPPORTED_PROTOCOL_TLSV1_3,
- };
+ SUPPORTED_PROTOCOL_TLSV1_3);
public static String[] getDefaultProtocols() {
if (Platform.isTlsV1Deprecated()) {
@@ -1135,11 +1142,7 @@
if (protocol == null) {
throw new IllegalArgumentException("protocols contains null");
}
- if (!protocol.equals(DEPRECATED_PROTOCOL_TLSV1)
- && !protocol.equals(DEPRECATED_PROTOCOL_TLSV1_1)
- && !protocol.equals(SUPPORTED_PROTOCOL_TLSV1_2)
- && !protocol.equals(SUPPORTED_PROTOCOL_TLSV1_3)
- && !protocol.equals(OBSOLETE_PROTOCOL_SSLV3)) {
+ if (!Arrays.asList(SUPPORTED_PROTOCOLS).contains(protocol)) {
throw new IllegalArgumentException("protocol " + protocol + " is not supported");
}
}
diff --git a/common/src/main/java/org/conscrypt/NativeSsl.java b/common/src/main/java/org/conscrypt/NativeSsl.java
index 79369ca..7d260bc 100644
--- a/common/src/main/java/org/conscrypt/NativeSsl.java
+++ b/common/src/main/java/org/conscrypt/NativeSsl.java
@@ -308,8 +308,10 @@
if (parameters.getEnabledProtocols().length == 0 && parameters.isEnabledProtocolsFiltered) {
throw new SSLHandshakeException("No enabled protocols; "
- + NativeCrypto.OBSOLETE_PROTOCOL_SSLV3
- + " is no longer supported and was filtered from the list");
+ + NativeCrypto.OBSOLETE_PROTOCOL_SSLV3 + ", "
+ + NativeCrypto.DEPRECATED_PROTOCOL_TLSV1
+ + " and " + NativeCrypto.DEPRECATED_PROTOCOL_TLSV1_1
+ + " are no longer supported and were filtered from the list");
}
NativeCrypto.setEnabledProtocols(ssl, this, parameters.enabledProtocols);
NativeCrypto.setEnabledCipherSuites(
diff --git a/common/src/main/java/org/conscrypt/OpenSSLKey.java b/common/src/main/java/org/conscrypt/OpenSSLKey.java
index e5e81f7..4249b8e 100644
--- a/common/src/main/java/org/conscrypt/OpenSSLKey.java
+++ b/common/src/main/java/org/conscrypt/OpenSSLKey.java
@@ -32,7 +32,8 @@
/**
* Represents a BoringSSL {@code EVP_PKEY}.
*/
-final class OpenSSLKey {
+@Internal
+public final class OpenSSLKey {
private final NativeRef.EVP_PKEY ctx;
private final boolean wrapped;
@@ -255,7 +256,7 @@
*
* @throws InvalidKeyException if parsing fails
*/
- static OpenSSLKey fromPublicKeyPemInputStream(InputStream is)
+ public static OpenSSLKey fromPublicKeyPemInputStream(InputStream is)
throws InvalidKeyException {
OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is, true);
try {
@@ -272,7 +273,7 @@
}
}
- PublicKey getPublicKey() throws NoSuchAlgorithmException {
+ public PublicKey getPublicKey() throws NoSuchAlgorithmException {
switch (NativeCrypto.EVP_PKEY_type(ctx)) {
case NativeConstants.EVP_PKEY_RSA:
return new OpenSSLRSAPublicKey(this);
diff --git a/common/src/main/java/org/conscrypt/SSLParametersImpl.java b/common/src/main/java/org/conscrypt/SSLParametersImpl.java
index c38da79..846414b 100644
--- a/common/src/main/java/org/conscrypt/SSLParametersImpl.java
+++ b/common/src/main/java/org/conscrypt/SSLParametersImpl.java
@@ -27,6 +27,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.List;
import java.util.Set;
import javax.crypto.SecretKey;
import javax.net.ssl.KeyManager;
@@ -144,8 +145,20 @@
}
// initialize the list of cipher suites and protocols enabled by default
- enabledProtocols = NativeCrypto.checkEnabledProtocols(
- protocols == null ? NativeCrypto.getDefaultProtocols() : protocols).clone();
+ if (protocols == null) {
+ enabledProtocols = NativeCrypto.getDefaultProtocols().clone();
+ } else {
+ String[] filteredProtocols =
+ filterFromProtocols(protocols, Arrays.asList(!Platform.isTlsV1Filtered()
+ ? new String[0]
+ : new String[] {
+ NativeCrypto.OBSOLETE_PROTOCOL_SSLV3,
+ NativeCrypto.DEPRECATED_PROTOCOL_TLSV1,
+ NativeCrypto.DEPRECATED_PROTOCOL_TLSV1_1,
+ }));
+ isEnabledProtocolsFiltered = protocols.length != filteredProtocols.length;
+ enabledProtocols = NativeCrypto.checkEnabledProtocols(filteredProtocols).clone();
+ }
boolean x509CipherSuitesNeeded = (x509KeyManager != null) || (x509TrustManager != null);
boolean pskCipherSuitesNeeded = pskKeyManager != null;
enabledCipherSuites = getDefaultCipherSuites(
@@ -282,7 +295,13 @@
throw new IllegalArgumentException("protocols == null");
}
String[] filteredProtocols =
- filterFromProtocols(protocols, NativeCrypto.OBSOLETE_PROTOCOL_SSLV3);
+ filterFromProtocols(protocols, Arrays.asList(!Platform.isTlsV1Filtered()
+ ? new String[0]
+ : new String[] {
+ NativeCrypto.OBSOLETE_PROTOCOL_SSLV3,
+ NativeCrypto.DEPRECATED_PROTOCOL_TLSV1,
+ NativeCrypto.DEPRECATED_PROTOCOL_TLSV1_1,
+ }));
isEnabledProtocolsFiltered = protocols.length != filteredProtocols.length;
enabledProtocols = NativeCrypto.checkEnabledProtocols(filteredProtocols).clone();
}
@@ -430,14 +449,15 @@
* This filters {@code obsoleteProtocol} from the list of {@code protocols}
* down to help with app compatibility.
*/
- private static String[] filterFromProtocols(String[] protocols, String obsoleteProtocol) {
- if (protocols.length == 1 && obsoleteProtocol.equals(protocols[0])) {
+ private static String[] filterFromProtocols(String[] protocols,
+ List<String> obsoleteProtocols) {
+ if (protocols.length == 1 && obsoleteProtocols.contains(protocols[0])) {
return EMPTY_STRING_ARRAY;
}
ArrayList<String> newProtocols = new ArrayList<String>();
for (String protocol : protocols) {
- if (!obsoleteProtocol.equals(protocol)) {
+ if (!obsoleteProtocols.contains(protocol)) {
newProtocols.add(protocol);
}
}
diff --git a/common/src/main/java/org/conscrypt/TrustManagerImpl.java b/common/src/main/java/org/conscrypt/TrustManagerImpl.java
index 1bacf7e..31937ef 100644
--- a/common/src/main/java/org/conscrypt/TrustManagerImpl.java
+++ b/common/src/main/java/org/conscrypt/TrustManagerImpl.java
@@ -34,6 +34,12 @@
package org.conscrypt;
+import org.conscrypt.ct.LogStore;
+import org.conscrypt.ct.Policy;
+import org.conscrypt.ct.PolicyCompliance;
+import org.conscrypt.ct.VerificationResult;
+import org.conscrypt.ct.Verifier;
+
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.Socket;
@@ -63,16 +69,13 @@
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
+
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.X509ExtendedTrustManager;
-import org.conscrypt.ct.CTLogStore;
-import org.conscrypt.ct.CTPolicy;
-import org.conscrypt.ct.CTVerificationResult;
-import org.conscrypt.ct.CTVerifier;
/**
*
@@ -139,8 +142,9 @@
private final Exception err;
private final CertificateFactory factory;
private final CertBlocklist blocklist;
- private CTVerifier ctVerifier;
- private CTPolicy ctPolicy;
+ private LogStore ctLogStore;
+ private Verifier ctVerifier;
+ private Policy ctPolicy;
private ConscryptHostnameVerifier hostnameVerifier;
@@ -163,18 +167,16 @@
this(keyStore, manager, certStore, null);
}
- public TrustManagerImpl(KeyStore keyStore, CertPinManager manager,
- ConscryptCertStore certStore,
- CertBlocklist blocklist) {
+ public TrustManagerImpl(KeyStore keyStore, CertPinManager manager, ConscryptCertStore certStore,
+ CertBlocklist blocklist) {
this(keyStore, manager, certStore, blocklist, null, null, null);
}
/**
* For testing only.
*/
- public TrustManagerImpl(KeyStore keyStore, CertPinManager manager,
- ConscryptCertStore certStore, CertBlocklist blocklist, CTLogStore ctLogStore,
- CTVerifier ctVerifier, CTPolicy ctPolicy) {
+ public TrustManagerImpl(KeyStore keyStore, CertPinManager manager, ConscryptCertStore certStore,
+ CertBlocklist blocklist, LogStore ctLogStore, Verifier ctVerifier, Policy ctPolicy) {
CertPathValidator validatorLocal = null;
CertificateFactory factoryLocal = null;
KeyStore rootKeyStoreLocal = null;
@@ -214,7 +216,7 @@
}
if (ctPolicy == null) {
- ctPolicy = Platform.newDefaultPolicy(ctLogStore);
+ ctPolicy = Platform.newDefaultPolicy();
}
this.pinManager = manager;
@@ -227,8 +229,12 @@
this.acceptedIssuers = acceptedIssuersLocal;
this.err = errLocal;
this.blocklist = blocklist;
- this.ctVerifier = new CTVerifier(ctLogStore);
+ this.ctLogStore = ctLogStore;
+ this.ctVerifier = new Verifier(ctLogStore);
this.ctPolicy = ctPolicy;
+ if (ctLogStore != null) {
+ ctLogStore.setPolicy(ctPolicy);
+ }
}
@SuppressWarnings("JdkObsolete") // KeyStore#aliases is the only API available
@@ -680,7 +686,7 @@
if (!clientAuth &&
(ctEnabledOverride || (host != null && Platform
.isCTVerificationRequired(host)))) {
- checkCT(host, wholeChain, ocspData, tlsSctData);
+ checkCT(wholeChain, ocspData, tlsSctData);
}
if (untrustedChain.isEmpty()) {
@@ -726,15 +732,23 @@
}
}
- private void checkCT(String host, List<X509Certificate> chain, byte[] ocspData, byte[] tlsData)
+ private void checkCT(List<X509Certificate> chain, byte[] ocspData, byte[] tlsData)
throws CertificateException {
- CTVerificationResult result =
+ if (ctLogStore.getState() != LogStore.State.COMPLIANT) {
+ /* Fail open. For some reason, the LogStore is not usable. It could
+ * be because there is no log list available or that the log list
+ * is too old (according to the policy). */
+ return;
+ }
+ VerificationResult result =
ctVerifier.verifySignedCertificateTimestamps(chain, tlsData, ocspData);
- if (!ctPolicy.doesResultConformToPolicy(result, host,
- chain.toArray(new X509Certificate[chain.size()]))) {
+ X509Certificate leaf = chain.get(0);
+ PolicyCompliance compliance = ctPolicy.doesResultConformToPolicy(result, leaf);
+ if (compliance != PolicyCompliance.COMPLY) {
throw new CertificateException(
- "Certificate chain does not conform to required transparency policy.");
+ "Certificate chain does not conform to required transparency policy: "
+ + compliance.name());
}
}
@@ -1025,12 +1039,12 @@
}
// Replace the CTVerifier. For testing only.
- public void setCTVerifier(CTVerifier verifier) {
+ public void setCTVerifier(Verifier verifier) {
this.ctVerifier = verifier;
}
// Replace the CTPolicy. For testing only.
- public void setCTPolicy(CTPolicy policy) {
+ public void setCTPolicy(Policy policy) {
this.ctPolicy = policy;
}
}
diff --git a/common/src/main/java/org/conscrypt/ct/CTLogInfo.java b/common/src/main/java/org/conscrypt/ct/CTLogInfo.java
deleted file mode 100644
index c2e312a..0000000
--- a/common/src/main/java/org/conscrypt/ct/CTLogInfo.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2015 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 java.security.InvalidKeyException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.PublicKey;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.util.Arrays;
-import org.conscrypt.Internal;
-
-/**
- * Properties about a Certificate Transparency Log.
- * This object stores information about a CT log, its public key, description and URL.
- * It allows verification of SCTs against the log's public key.
- */
-@Internal
-public class CTLogInfo {
- private final byte[] logId;
- private final PublicKey publicKey;
- private final String description;
- private final String url;
-
- public CTLogInfo(PublicKey publicKey, String description, String url) {
- try {
- this.logId = MessageDigest.getInstance("SHA-256")
- .digest(publicKey.getEncoded());
- } catch (NoSuchAlgorithmException e) {
- // SHA-256 is guaranteed to be available
- throw new RuntimeException(e);
- }
-
- this.publicKey = publicKey;
- this.description = description;
- this.url = url;
- }
-
- /**
- * Get the log's ID, that is the SHA-256 hash of it's public key
- */
- public byte[] getID() {
- return logId;
- }
-
- public PublicKey getPublicKey() {
- return publicKey;
- }
-
- public String getDescription() {
- return description;
- }
-
- public String getUrl() {
- return url;
- }
-
- @Override
- public boolean equals(Object other) {
- if (this == other) {
- return true;
- }
- if (!(other instanceof CTLogInfo)) {
- return false;
- }
-
- CTLogInfo that = (CTLogInfo)other;
- return
- this.publicKey.equals(that.publicKey) &&
- this.description.equals(that.description) &&
- this.url.equals(that.url);
- }
-
- @Override
- public int hashCode() {
- int hash = 1;
- hash = hash * 31 + publicKey.hashCode();
- hash = hash * 31 + description.hashCode();
- hash = hash * 31 + url.hashCode();
-
- return hash;
- }
-
- /**
- * Verify the signature of a signed certificate timestamp for the given certificate entry
- * against the log's public key.
- *
- * @return the result of the verification
- */
- public VerifiedSCT.Status verifySingleSCT(SignedCertificateTimestamp sct,
- CertificateEntry entry) {
- if (!Arrays.equals(sct.getLogID(), getID())) {
- return VerifiedSCT.Status.UNKNOWN_LOG;
- }
-
- byte[] toVerify;
- try {
- toVerify = sct.encodeTBS(entry);
- } catch (SerializationException e) {
- return VerifiedSCT.Status.INVALID_SCT;
- }
-
- Signature signature;
- try {
- String algorithm = sct.getSignature().getAlgorithm();
- signature = Signature.getInstance(algorithm);
- } catch (NoSuchAlgorithmException e) {
- return VerifiedSCT.Status.INVALID_SCT;
- }
-
- try {
- signature.initVerify(publicKey);
- } catch (InvalidKeyException e) {
- return VerifiedSCT.Status.INVALID_SCT;
- }
-
- try {
- signature.update(toVerify);
- if (!signature.verify(sct.getSignature().getSignature())) {
- return VerifiedSCT.Status.INVALID_SIGNATURE;
- }
- return VerifiedSCT.Status.VALID;
- } catch (SignatureException e) {
- // This only happens if the signature is not initialized,
- // but we call initVerify just before, so it should never do
- throw new RuntimeException(e);
- }
- }
-}
-
diff --git a/common/src/main/java/org/conscrypt/ct/CertificateEntry.java b/common/src/main/java/org/conscrypt/ct/CertificateEntry.java
index 72ed530..137ded1 100644
--- a/common/src/main/java/org/conscrypt/ct/CertificateEntry.java
+++ b/common/src/main/java/org/conscrypt/ct/CertificateEntry.java
@@ -61,8 +61,8 @@
} else if (entryType == LogEntryType.X509_ENTRY && issuerKeyHash != null) {
throw new IllegalArgumentException("unexpected issuerKeyHash for X509 entry.");
}
-
- if (issuerKeyHash != null && issuerKeyHash.length != CTConstants.ISSUER_KEY_HASH_LENGTH) {
+
+ if (issuerKeyHash != null && issuerKeyHash.length != Constants.ISSUER_KEY_HASH_LENGTH) {
throw new IllegalArgumentException("issuerKeyHash must be 32 bytes long");
}
@@ -83,11 +83,11 @@
public static CertificateEntry createForPrecertificate(OpenSSLX509Certificate leaf,
OpenSSLX509Certificate issuer) throws CertificateException {
try {
- if (!leaf.getNonCriticalExtensionOIDs().contains(CTConstants.X509_SCT_LIST_OID)) {
+ if (!leaf.getNonCriticalExtensionOIDs().contains(Constants.X509_SCT_LIST_OID)) {
throw new CertificateException("Certificate does not contain embedded signed timestamps");
}
- byte[] tbs = leaf.getTBSCertificateWithoutExtension(CTConstants.X509_SCT_LIST_OID);
+ byte[] tbs = leaf.getTBSCertificateWithoutExtension(Constants.X509_SCT_LIST_OID);
byte[] issuerKey = issuer.getPublicKey().getEncoded();
MessageDigest md = MessageDigest.getInstance("SHA-256");
@@ -124,11 +124,11 @@
* TLS encode the CertificateEntry structure.
*/
public void encode(OutputStream output) throws SerializationException {
- Serialization.writeNumber(output, entryType.ordinal(), CTConstants.LOG_ENTRY_TYPE_LENGTH);
+ Serialization.writeNumber(output, entryType.ordinal(), Constants.LOG_ENTRY_TYPE_LENGTH);
if (entryType == LogEntryType.PRECERT_ENTRY) {
Serialization.writeFixedBytes(output, issuerKeyHash);
}
- Serialization.writeVariableBytes(output, certificate, CTConstants.CERTIFICATE_LENGTH_BYTES);
+ Serialization.writeVariableBytes(output, certificate, Constants.CERTIFICATE_LENGTH_BYTES);
}
}
diff --git a/common/src/main/java/org/conscrypt/ct/CTConstants.java b/common/src/main/java/org/conscrypt/ct/Constants.java
similarity index 98%
rename from common/src/main/java/org/conscrypt/ct/CTConstants.java
rename to common/src/main/java/org/conscrypt/ct/Constants.java
index 76133d9..71bcab2 100644
--- a/common/src/main/java/org/conscrypt/ct/CTConstants.java
+++ b/common/src/main/java/org/conscrypt/ct/Constants.java
@@ -19,7 +19,7 @@
import org.conscrypt.Internal;
@Internal
-public class CTConstants {
+public class Constants {
public static final String X509_SCT_LIST_OID = "1.3.6.1.4.1.11129.2.4.2";
public static final String OCSP_SCT_LIST_OID = "1.3.6.1.4.1.11129.2.4.5";
@@ -41,4 +41,3 @@
public static final int ISSUER_KEY_HASH_LENGTH = 32;
}
-
diff --git a/common/src/main/java/org/conscrypt/ct/DigitallySigned.java b/common/src/main/java/org/conscrypt/ct/DigitallySigned.java
index b5f4478..15720d9 100644
--- a/common/src/main/java/org/conscrypt/ct/DigitallySigned.java
+++ b/common/src/main/java/org/conscrypt/ct/DigitallySigned.java
@@ -107,10 +107,9 @@
throws SerializationException {
try {
return new DigitallySigned(
- Serialization.readNumber(input, CTConstants.HASH_ALGORITHM_LENGTH),
- Serialization.readNumber(input, CTConstants.SIGNATURE_ALGORITHM_LENGTH),
- Serialization.readVariableBytes(input, CTConstants.SIGNATURE_LENGTH_BYTES)
- );
+ Serialization.readNumber(input, Constants.HASH_ALGORITHM_LENGTH),
+ Serialization.readNumber(input, Constants.SIGNATURE_ALGORITHM_LENGTH),
+ Serialization.readVariableBytes(input, Constants.SIGNATURE_LENGTH_BYTES));
} catch (IllegalArgumentException e) {
throw new SerializationException(e);
}
diff --git a/common/src/main/java/org/conscrypt/ct/LogInfo.java b/common/src/main/java/org/conscrypt/ct/LogInfo.java
new file mode 100644
index 0000000..99c8139
--- /dev/null
+++ b/common/src/main/java/org/conscrypt/ct/LogInfo.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2015 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 java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.util.Arrays;
+import java.util.Objects;
+import org.conscrypt.Internal;
+
+/**
+ * Properties about a Certificate Transparency Log.
+ * This object stores information about a CT log, its public key, description and URL.
+ * It allows verification of SCTs against the log's public key.
+ */
+@Internal
+public class LogInfo {
+ public static final int STATE_UNKNOWN = 0;
+ public static final int STATE_PENDING = 1;
+ public static final int STATE_QUALIFIED = 2;
+ public static final int STATE_USABLE = 3;
+ public static final int STATE_READONLY = 4;
+ public static final int STATE_RETIRED = 5;
+ public static final int STATE_REJECTED = 6;
+
+ private final byte[] logId;
+ private final PublicKey publicKey;
+ private final int state;
+ private final long stateTimestamp;
+ private final String description;
+ private final String url;
+ private final String operator;
+
+ private LogInfo(Builder builder) {
+ /* Based on the required fields for the log list schema v3. Notably,
+ * the state may be absent. The logId must match the public key, this
+ * is validated in the builder. */
+ Objects.requireNonNull(builder.logId);
+ Objects.requireNonNull(builder.publicKey);
+ Objects.requireNonNull(builder.url);
+ Objects.requireNonNull(builder.operator);
+
+ this.logId = builder.logId;
+ this.publicKey = builder.publicKey;
+ this.state = builder.state;
+ this.stateTimestamp = builder.stateTimestamp;
+ this.description = builder.description;
+ this.url = builder.url;
+ this.operator = builder.operator;
+ }
+
+ public static class Builder {
+ private byte[] logId;
+ private PublicKey publicKey;
+ private int state;
+ private long stateTimestamp;
+ private String description;
+ private String url;
+ private String operator;
+
+ public Builder setPublicKey(PublicKey publicKey) {
+ Objects.requireNonNull(publicKey);
+ this.publicKey = publicKey;
+ try {
+ this.logId = MessageDigest.getInstance("SHA-256").digest(publicKey.getEncoded());
+ } catch (NoSuchAlgorithmException e) {
+ // SHA-256 is guaranteed to be available
+ throw new RuntimeException(e);
+ }
+ return this;
+ }
+
+ public Builder setState(int state, long timestamp) {
+ if (state < 0 || state > STATE_REJECTED) {
+ throw new IllegalArgumentException("invalid state value");
+ }
+ this.state = state;
+ this.stateTimestamp = timestamp;
+ return this;
+ }
+
+ public Builder setDescription(String description) {
+ Objects.requireNonNull(description);
+ this.description = description;
+ return this;
+ }
+
+ public Builder setUrl(String url) {
+ Objects.requireNonNull(url);
+ this.url = url;
+ return this;
+ }
+
+ public Builder setOperator(String operator) {
+ Objects.requireNonNull(operator);
+ this.operator = operator;
+ return this;
+ }
+
+ public LogInfo build() {
+ return new LogInfo(this);
+ }
+ }
+
+ /**
+ * Get the log's ID, that is the SHA-256 hash of it's public key
+ */
+ public byte[] getID() {
+ return logId;
+ }
+
+ public PublicKey getPublicKey() {
+ return publicKey;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public int getState() {
+ return state;
+ }
+
+ public int getStateAt(long when) {
+ if (when >= this.stateTimestamp) {
+ return state;
+ }
+ return STATE_UNKNOWN;
+ }
+
+ public long getStateTimestamp() {
+ return stateTimestamp;
+ }
+
+ public String getOperator() {
+ return operator;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof LogInfo)) {
+ return false;
+ }
+
+ LogInfo that = (LogInfo) other;
+ return this.state == that.state && this.description.equals(that.description)
+ && this.url.equals(that.url) && this.operator.equals(that.operator)
+ && this.stateTimestamp == that.stateTimestamp
+ && Arrays.equals(this.logId, that.logId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ Arrays.hashCode(logId), description, url, state, stateTimestamp, operator);
+ }
+
+ /**
+ * Verify the signature of a signed certificate timestamp for the given certificate entry
+ * against the log's public key.
+ *
+ * @return the result of the verification
+ */
+ public VerifiedSCT.Status verifySingleSCT(
+ SignedCertificateTimestamp sct, CertificateEntry entry) {
+ if (!Arrays.equals(sct.getLogID(), getID())) {
+ return VerifiedSCT.Status.UNKNOWN_LOG;
+ }
+
+ byte[] toVerify;
+ try {
+ toVerify = sct.encodeTBS(entry);
+ } catch (SerializationException e) {
+ return VerifiedSCT.Status.INVALID_SCT;
+ }
+
+ Signature signature;
+ try {
+ String algorithm = sct.getSignature().getAlgorithm();
+ signature = Signature.getInstance(algorithm);
+ } catch (NoSuchAlgorithmException e) {
+ return VerifiedSCT.Status.INVALID_SCT;
+ }
+
+ try {
+ signature.initVerify(publicKey);
+ } catch (InvalidKeyException e) {
+ return VerifiedSCT.Status.INVALID_SCT;
+ }
+
+ try {
+ signature.update(toVerify);
+ if (!signature.verify(sct.getSignature().getSignature())) {
+ return VerifiedSCT.Status.INVALID_SIGNATURE;
+ }
+ return VerifiedSCT.Status.VALID;
+ } catch (SignatureException e) {
+ // This only happens if the signature is not initialized,
+ // but we call initVerify just before, so it should never do
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/common/src/main/java/org/conscrypt/ct/CTPolicy.java b/common/src/main/java/org/conscrypt/ct/LogStore.java
similarity index 69%
copy from common/src/main/java/org/conscrypt/ct/CTPolicy.java
copy to common/src/main/java/org/conscrypt/ct/LogStore.java
index 455cabd..10e099c 100644
--- a/common/src/main/java/org/conscrypt/ct/CTPolicy.java
+++ b/common/src/main/java/org/conscrypt/ct/LogStore.java
@@ -16,11 +16,24 @@
package org.conscrypt.ct;
-import java.security.cert.X509Certificate;
import org.conscrypt.Internal;
@Internal
-public interface CTPolicy {
- boolean doesResultConformToPolicy(CTVerificationResult result, String hostname,
- X509Certificate[] chain);
+public interface LogStore {
+ public enum State {
+ UNINITIALIZED,
+ NOT_FOUND,
+ MALFORMED,
+ LOADED,
+ COMPLIANT,
+ NON_COMPLIANT,
+ }
+
+ void setPolicy(Policy policy);
+
+ State getState();
+
+ long getTimestamp();
+
+ LogInfo getKnownLog(byte[] logId);
}
diff --git a/common/src/main/java/org/conscrypt/ct/CTPolicy.java b/common/src/main/java/org/conscrypt/ct/Policy.java
similarity index 81%
rename from common/src/main/java/org/conscrypt/ct/CTPolicy.java
rename to common/src/main/java/org/conscrypt/ct/Policy.java
index 455cabd..5b3d95a 100644
--- a/common/src/main/java/org/conscrypt/ct/CTPolicy.java
+++ b/common/src/main/java/org/conscrypt/ct/Policy.java
@@ -20,7 +20,7 @@
import org.conscrypt.Internal;
@Internal
-public interface CTPolicy {
- boolean doesResultConformToPolicy(CTVerificationResult result, String hostname,
- X509Certificate[] chain);
+public interface Policy {
+ boolean isLogStoreCompliant(LogStore store);
+ PolicyCompliance doesResultConformToPolicy(VerificationResult result, X509Certificate leaf);
}
diff --git a/common/src/main/java/org/conscrypt/ct/CTLogStore.java b/common/src/main/java/org/conscrypt/ct/PolicyCompliance.java
similarity index 81%
rename from common/src/main/java/org/conscrypt/ct/CTLogStore.java
rename to common/src/main/java/org/conscrypt/ct/PolicyCompliance.java
index bf30d66..d889ee7 100644
--- a/common/src/main/java/org/conscrypt/ct/CTLogStore.java
+++ b/common/src/main/java/org/conscrypt/ct/PolicyCompliance.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -19,7 +19,8 @@
import org.conscrypt.Internal;
@Internal
-public interface CTLogStore {
- CTLogInfo getKnownLog(byte[] logId);
+public enum PolicyCompliance {
+ COMPLY,
+ NOT_ENOUGH_SCTS,
+ NOT_ENOUGH_DIVERSE_SCTS
}
-
diff --git a/common/src/main/java/org/conscrypt/ct/SignedCertificateTimestamp.java b/common/src/main/java/org/conscrypt/ct/SignedCertificateTimestamp.java
index d23f9ed..8ad3788 100644
--- a/common/src/main/java/org/conscrypt/ct/SignedCertificateTimestamp.java
+++ b/common/src/main/java/org/conscrypt/ct/SignedCertificateTimestamp.java
@@ -87,19 +87,16 @@
*/
public static SignedCertificateTimestamp decode(InputStream input, Origin origin)
throws SerializationException {
- int version = Serialization.readNumber(input, CTConstants.VERSION_LENGTH);
+ int version = Serialization.readNumber(input, Constants.VERSION_LENGTH);
if (version != Version.V1.ordinal()) {
throw new SerializationException("Unsupported SCT version " + version);
}
- return new SignedCertificateTimestamp(
- Version.V1,
- Serialization.readFixedBytes(input, CTConstants.LOGID_LENGTH),
- Serialization.readLong(input, CTConstants.TIMESTAMP_LENGTH),
- Serialization.readVariableBytes(input, CTConstants.EXTENSIONS_LENGTH_BYTES),
- DigitallySigned.decode(input),
- origin
- );
+ return new SignedCertificateTimestamp(Version.V1,
+ Serialization.readFixedBytes(input, Constants.LOGID_LENGTH),
+ Serialization.readLong(input, Constants.TIMESTAMP_LENGTH),
+ Serialization.readVariableBytes(input, Constants.EXTENSIONS_LENGTH_BYTES),
+ DigitallySigned.decode(input), origin);
}
/**
@@ -115,12 +112,12 @@
*/
public void encodeTBS(OutputStream output, CertificateEntry certEntry)
throws SerializationException {
- Serialization.writeNumber(output, version.ordinal(), CTConstants.VERSION_LENGTH);
+ Serialization.writeNumber(output, version.ordinal(), Constants.VERSION_LENGTH);
Serialization.writeNumber(output, SignatureType.CERTIFICATE_TIMESTAMP.ordinal(),
- CTConstants.SIGNATURE_TYPE_LENGTH);
- Serialization.writeNumber(output, timestamp, CTConstants.TIMESTAMP_LENGTH);
+ Constants.SIGNATURE_TYPE_LENGTH);
+ Serialization.writeNumber(output, timestamp, Constants.TIMESTAMP_LENGTH);
certEntry.encode(output);
- Serialization.writeVariableBytes(output, extensions, CTConstants.EXTENSIONS_LENGTH_BYTES);
+ Serialization.writeVariableBytes(output, extensions, Constants.EXTENSIONS_LENGTH_BYTES);
}
/**
diff --git a/common/src/main/java/org/conscrypt/ct/CTVerificationResult.java b/common/src/main/java/org/conscrypt/ct/VerificationResult.java
similarity index 75%
rename from common/src/main/java/org/conscrypt/ct/CTVerificationResult.java
rename to common/src/main/java/org/conscrypt/ct/VerificationResult.java
index b21e9ac..354b16a 100644
--- a/common/src/main/java/org/conscrypt/ct/CTVerificationResult.java
+++ b/common/src/main/java/org/conscrypt/ct/VerificationResult.java
@@ -21,13 +21,21 @@
import java.util.List;
import org.conscrypt.Internal;
+/**
+ * Container for verified SignedCertificateTimestamp.
+ *
+ * getValidSCTs returns SCTs which were found to match a known log and for
+ * which the signature has been verified. There is no guarantee on the state of
+ * the log (e.g., getLogInfo.getState() may return STATE_UNKNOWN). Further
+ * verification on the compliance with the policy is performed in PolicyImpl.
+ */
@Internal
-public class CTVerificationResult {
+public class VerificationResult {
private final ArrayList<VerifiedSCT> validSCTs = new ArrayList<VerifiedSCT>();
private final ArrayList<VerifiedSCT> invalidSCTs = new ArrayList<VerifiedSCT>();
public void add(VerifiedSCT result) {
- if (result.status == VerifiedSCT.Status.VALID) {
+ if (result.isValid()) {
validSCTs.add(result);
} else {
invalidSCTs.add(result);
@@ -42,4 +50,3 @@
return Collections.unmodifiableList(invalidSCTs);
}
}
-
diff --git a/common/src/main/java/org/conscrypt/ct/VerifiedSCT.java b/common/src/main/java/org/conscrypt/ct/VerifiedSCT.java
index 7eaf45d..6c9c008 100644
--- a/common/src/main/java/org/conscrypt/ct/VerifiedSCT.java
+++ b/common/src/main/java/org/conscrypt/ct/VerifiedSCT.java
@@ -16,6 +16,7 @@
package org.conscrypt.ct;
+import java.util.Objects;
import org.conscrypt.Internal;
/**
@@ -30,12 +31,61 @@
INVALID_SCT
}
- public final SignedCertificateTimestamp sct;
- public final Status status;
+ private final SignedCertificateTimestamp sct;
+ private final Status status;
+ private final LogInfo logInfo;
- public VerifiedSCT(SignedCertificateTimestamp sct, Status status) {
- this.sct = sct;
- this.status = status;
+ private VerifiedSCT(Builder builder) {
+ Objects.requireNonNull(builder.sct);
+ Objects.requireNonNull(builder.status);
+ if (builder.status == Status.VALID) {
+ Objects.requireNonNull(builder.logInfo);
+ }
+
+ this.sct = builder.sct;
+ this.status = builder.status;
+ this.logInfo = builder.logInfo;
+ }
+
+ public SignedCertificateTimestamp getSct() {
+ return sct;
+ }
+
+ public Status getStatus() {
+ return status;
+ }
+
+ public boolean isValid() {
+ return status == Status.VALID;
+ }
+
+ public LogInfo getLogInfo() {
+ return logInfo;
+ }
+
+ public static class Builder {
+ private SignedCertificateTimestamp sct;
+ private Status status;
+ private LogInfo logInfo;
+
+ public Builder(SignedCertificateTimestamp sct) {
+ this.sct = sct;
+ }
+
+ public Builder setStatus(Status status) {
+ this.status = status;
+ return this;
+ }
+
+ public Builder setLogInfo(LogInfo logInfo) {
+ Objects.requireNonNull(logInfo);
+ this.logInfo = logInfo;
+ return this;
+ }
+
+ public VerifiedSCT build() {
+ return new VerifiedSCT(this);
+ }
}
}
diff --git a/common/src/main/java/org/conscrypt/ct/CTVerifier.java b/common/src/main/java/org/conscrypt/ct/Verifier.java
similarity index 73%
rename from common/src/main/java/org/conscrypt/ct/CTVerifier.java
rename to common/src/main/java/org/conscrypt/ct/Verifier.java
index 2f1f79b..79d90d9 100644
--- a/common/src/main/java/org/conscrypt/ct/CTVerifier.java
+++ b/common/src/main/java/org/conscrypt/ct/Verifier.java
@@ -27,18 +27,18 @@
import org.conscrypt.OpenSSLX509Certificate;
@Internal
-public class CTVerifier {
- private final CTLogStore store;
+public class Verifier {
+ private final LogStore store;
- public CTVerifier(CTLogStore store) {
+ public Verifier(LogStore store) {
this.store = store;
}
- public CTVerificationResult verifySignedCertificateTimestamps(List<X509Certificate> chain,
+ public VerificationResult verifySignedCertificateTimestamps(List<X509Certificate> chain,
byte[] tlsData, byte[] ocspData) throws CertificateEncodingException {
OpenSSLX509Certificate[] certs = new OpenSSLX509Certificate[chain.size()];
int i = 0;
- for(X509Certificate cert : chain) {
+ for (X509Certificate cert : chain) {
certs[i++] = OpenSSLX509Certificate.fromCertificate(cert);
}
return verifySignedCertificateTimestamps(certs, tlsData, ocspData);
@@ -50,7 +50,7 @@
* response, and verified against the list of known logs.
* @throws IllegalArgumentException if the chain is empty
*/
- public CTVerificationResult verifySignedCertificateTimestamps(OpenSSLX509Certificate[] chain,
+ public VerificationResult verifySignedCertificateTimestamps(OpenSSLX509Certificate[] chain,
byte[] tlsData, byte[] ocspData) throws CertificateEncodingException {
if (chain.length == 0) {
throw new IllegalArgumentException("Chain of certificates mustn't be empty.");
@@ -58,7 +58,7 @@
OpenSSLX509Certificate leaf = chain[0];
- CTVerificationResult result = new CTVerificationResult();
+ VerificationResult result = new VerificationResult();
List<SignedCertificateTimestamp> tlsScts = getSCTsFromTLSExtension(tlsData);
verifyExternalSCTs(tlsScts, leaf, result);
@@ -75,8 +75,7 @@
* The result of the verification for each sct is added to {@code result}.
*/
private void verifyEmbeddedSCTs(List<SignedCertificateTimestamp> scts,
- OpenSSLX509Certificate[] chain,
- CTVerificationResult result) {
+ OpenSSLX509Certificate[] chain, VerificationResult result) {
// Avoid creating the cert entry if we don't need it
if (scts.isEmpty()) {
return;
@@ -99,10 +98,7 @@
return;
}
- for (SignedCertificateTimestamp sct: scts) {
- VerifiedSCT.Status status = verifySingleSCT(sct, precertEntry);
- result.add(new VerifiedSCT(sct, status));
- }
+ verifySCTs(scts, precertEntry, result);
}
/**
@@ -111,8 +107,7 @@
* The result of the verification for each sct is added to {@code result}.
*/
private void verifyExternalSCTs(List<SignedCertificateTimestamp> scts,
- OpenSSLX509Certificate leaf,
- CTVerificationResult result) {
+ OpenSSLX509Certificate leaf, VerificationResult result) {
// Avoid creating the cert entry if we don't need it
if (scts.isEmpty()) {
return;
@@ -126,32 +121,38 @@
return;
}
- for (SignedCertificateTimestamp sct: scts) {
- VerifiedSCT.Status status = verifySingleSCT(sct, x509Entry);
- result.add(new VerifiedSCT(sct, status));
- }
+ verifySCTs(scts, x509Entry, result);
}
/**
- * Verify a single SCT for the given Certificate Entry
+ * Verify a list of SCTs.
*/
- private VerifiedSCT.Status verifySingleSCT(SignedCertificateTimestamp sct,
- CertificateEntry certEntry) {
- CTLogInfo log = store.getKnownLog(sct.getLogID());
- if (log == null) {
- return VerifiedSCT.Status.UNKNOWN_LOG;
+ private void verifySCTs(List<SignedCertificateTimestamp> scts, CertificateEntry certEntry,
+ VerificationResult result) {
+ for (SignedCertificateTimestamp sct : scts) {
+ VerifiedSCT.Builder builder = new VerifiedSCT.Builder(sct);
+ LogInfo log = store.getKnownLog(sct.getLogID());
+ if (log == null) {
+ builder.setStatus(VerifiedSCT.Status.UNKNOWN_LOG);
+ } else {
+ VerifiedSCT.Status status = log.verifySingleSCT(sct, certEntry);
+ builder.setStatus(status);
+ if (status == VerifiedSCT.Status.VALID) {
+ builder.setLogInfo(log);
+ }
+ }
+ result.add(builder.build());
}
-
- return log.verifySingleSCT(sct, certEntry);
}
/**
* Add every SCT in {@code scts} to {@code result} with INVALID_SCT as status
*/
- private void markSCTsAsInvalid(List<SignedCertificateTimestamp> scts,
- CTVerificationResult result) {
- for (SignedCertificateTimestamp sct: scts) {
- result.add(new VerifiedSCT(sct, VerifiedSCT.Status.INVALID_SCT));
+ private void markSCTsAsInvalid(
+ List<SignedCertificateTimestamp> scts, VerificationResult result) {
+ for (SignedCertificateTimestamp sct : scts) {
+ VerifiedSCT.Builder builder = new VerifiedSCT.Builder(sct);
+ result.add(builder.setStatus(VerifiedSCT.Status.INVALID_SCT).build());
}
}
@@ -163,24 +164,25 @@
* @param origin used to create the SignedCertificateTimestamp instances.
*/
@SuppressWarnings("MixedMutabilityReturnType")
- private static List<SignedCertificateTimestamp> getSCTsFromSCTList(byte[] data,
- SignedCertificateTimestamp.Origin origin) {
+ private static List<SignedCertificateTimestamp> getSCTsFromSCTList(
+ byte[] data, SignedCertificateTimestamp.Origin origin) {
if (data == null) {
return Collections.emptyList();
}
byte[][] sctList;
try {
- sctList = Serialization.readList(data, CTConstants.SCT_LIST_LENGTH_BYTES,
- CTConstants.SERIALIZED_SCT_LENGTH_BYTES);
+ sctList = Serialization.readList(
+ data, Constants.SCT_LIST_LENGTH_BYTES, Constants.SERIALIZED_SCT_LENGTH_BYTES);
} catch (SerializationException e) {
return Collections.emptyList();
}
List<SignedCertificateTimestamp> scts = new ArrayList<SignedCertificateTimestamp>();
- for (byte[] encodedSCT: sctList) {
- try {
- SignedCertificateTimestamp sct = SignedCertificateTimestamp.decode(encodedSCT, origin);
+ for (byte[] encodedSCT : sctList) {
+ try {
+ SignedCertificateTimestamp sct =
+ SignedCertificateTimestamp.decode(encodedSCT, origin);
scts.add(sct);
} catch (SerializationException e) {
// Ignore errors
@@ -210,23 +212,21 @@
* issuer in order to identify the relevant SingleResponse from the OCSP response,
* or an empty list is returned
*/
- private List<SignedCertificateTimestamp> getSCTsFromOCSPResponse(byte[] data,
- OpenSSLX509Certificate[] chain) {
+ private List<SignedCertificateTimestamp> getSCTsFromOCSPResponse(
+ byte[] data, OpenSSLX509Certificate[] chain) {
if (data == null || chain.length < 2) {
return Collections.emptyList();
}
- byte[] extData = NativeCrypto.get_ocsp_single_extension(data, CTConstants.OCSP_SCT_LIST_OID,
- chain[0].getContext(), chain[0],
- chain[1].getContext(), chain[1]);
+ byte[] extData = NativeCrypto.get_ocsp_single_extension(data, Constants.OCSP_SCT_LIST_OID,
+ chain[0].getContext(), chain[0], chain[1].getContext(), chain[1]);
if (extData == null) {
return Collections.emptyList();
}
try {
return getSCTsFromSCTList(
- Serialization.readDEROctetString(
- Serialization.readDEROctetString(extData)),
+ Serialization.readDEROctetString(Serialization.readDEROctetString(extData)),
SignedCertificateTimestamp.Origin.OCSP_RESPONSE);
} catch (SerializationException e) {
return Collections.emptyList();
@@ -240,19 +240,17 @@
* to be parsed, an empty list is returned. Individual SCTs which fail to be parsed are ignored.
*/
private List<SignedCertificateTimestamp> getSCTsFromX509Extension(OpenSSLX509Certificate leaf) {
- byte[] extData = leaf.getExtensionValue(CTConstants.X509_SCT_LIST_OID);
+ byte[] extData = leaf.getExtensionValue(Constants.X509_SCT_LIST_OID);
if (extData == null) {
return Collections.emptyList();
}
try {
return getSCTsFromSCTList(
- Serialization.readDEROctetString(
- Serialization.readDEROctetString(extData)),
+ Serialization.readDEROctetString(Serialization.readDEROctetString(extData)),
SignedCertificateTimestamp.Origin.EMBEDDED);
} catch (SerializationException e) {
return Collections.emptyList();
}
}
}
-
diff --git a/common/src/test/java/org/conscrypt/ct/CTVerifierTest.java b/common/src/test/java/org/conscrypt/ct/VerifierTest.java
similarity index 64%
rename from common/src/test/java/org/conscrypt/ct/CTVerifierTest.java
rename to common/src/test/java/org/conscrypt/ct/VerifierTest.java
index 9aaf8db..e7b94ba 100644
--- a/common/src/test/java/org/conscrypt/ct/CTVerifierTest.java
+++ b/common/src/test/java/org/conscrypt/ct/VerifierTest.java
@@ -30,25 +30,44 @@
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
-public class CTVerifierTest {
+public class VerifierTest {
private OpenSSLX509Certificate ca;
private OpenSSLX509Certificate cert;
private OpenSSLX509Certificate certEmbedded;
- private CTVerifier ctVerifier;
+ private Verifier ctVerifier;
@Before
public void setUp() throws Exception {
ca = OpenSSLX509Certificate.fromX509PemInputStream(openTestFile("ca-cert.pem"));
cert = OpenSSLX509Certificate.fromX509PemInputStream(openTestFile("cert.pem"));
- certEmbedded = OpenSSLX509Certificate.fromX509PemInputStream(
- openTestFile("cert-ct-embedded.pem"));
+ certEmbedded =
+ OpenSSLX509Certificate.fromX509PemInputStream(openTestFile("cert-ct-embedded.pem"));
PublicKey key = TestUtils.readPublicKeyPemFile("ct-server-key-public.pem");
- final CTLogInfo log = new CTLogInfo(key, "Test Log", "foo");
- CTLogStore store = new CTLogStore() {
+ final LogInfo log = new LogInfo.Builder()
+ .setPublicKey(key)
+ .setDescription("Test Log")
+ .setUrl("http://example.com")
+ .setOperator("LogOperator")
+ .setState(LogInfo.STATE_USABLE, 1643709600000L)
+ .build();
+ LogStore store = new LogStore() {
@Override
- public CTLogInfo getKnownLog(byte[] logId) {
+ public void setPolicy(Policy policy) {}
+
+ @Override
+ public State getState() {
+ return LogStore.State.COMPLIANT;
+ }
+
+ @Override
+ public long getTimestamp() {
+ return 0;
+ }
+
+ @Override
+ public LogInfo getKnownLog(byte[] logId) {
if (Arrays.equals(logId, log.getID())) {
return log;
} else {
@@ -57,120 +76,116 @@
}
};
- ctVerifier = new CTVerifier(store);
+ ctVerifier = new Verifier(store);
}
@Test
public void test_verifySignedCertificateTimestamps_withOCSPResponse() throws Exception {
- OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] { cert, ca };
+ OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] {cert, ca};
byte[] ocspResponse = readTestFile("ocsp-response.der");
- CTVerificationResult result =
- ctVerifier.verifySignedCertificateTimestamps(chain, null, ocspResponse);
+ VerificationResult result =
+ ctVerifier.verifySignedCertificateTimestamps(chain, null, ocspResponse);
assertEquals(1, result.getValidSCTs().size());
assertEquals(0, result.getInvalidSCTs().size());
}
@Test
public void test_verifySignedCertificateTimestamps_withTLSExtension() throws Exception {
- OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] { cert, ca };
+ OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] {cert, ca};
byte[] tlsExtension = readTestFile("ct-signed-timestamp-list");
- CTVerificationResult result =
- ctVerifier.verifySignedCertificateTimestamps(chain, tlsExtension, null);
+ VerificationResult result =
+ ctVerifier.verifySignedCertificateTimestamps(chain, tlsExtension, null);
assertEquals(1, result.getValidSCTs().size());
assertEquals(0, result.getInvalidSCTs().size());
}
@Test
public void test_verifySignedCertificateTimestamps_withEmbeddedExtension() throws Exception {
- OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] { certEmbedded, ca };
+ OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] {certEmbedded, ca};
- CTVerificationResult result =
- ctVerifier.verifySignedCertificateTimestamps(chain, null, null);
+ VerificationResult result = ctVerifier.verifySignedCertificateTimestamps(chain, null, null);
assertEquals(1, result.getValidSCTs().size());
assertEquals(0, result.getInvalidSCTs().size());
}
@Test
public void test_verifySignedCertificateTimestamps_withoutTimestamp() throws Exception {
- OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] { cert, ca };
+ OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] {cert, ca};
- CTVerificationResult result =
- ctVerifier.verifySignedCertificateTimestamps(chain, null, null);
+ VerificationResult result = ctVerifier.verifySignedCertificateTimestamps(chain, null, null);
assertEquals(0, result.getValidSCTs().size());
assertEquals(0, result.getInvalidSCTs().size());
}
@Test
public void test_verifySignedCertificateTimestamps_withInvalidSignature() throws Exception {
- OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] { cert, ca };
+ OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] {cert, ca};
byte[] tlsExtension = readTestFile("ct-signed-timestamp-list-invalid");
- CTVerificationResult result =
- ctVerifier.verifySignedCertificateTimestamps(chain, tlsExtension, null);
+ VerificationResult result =
+ ctVerifier.verifySignedCertificateTimestamps(chain, tlsExtension, null);
assertEquals(0, result.getValidSCTs().size());
assertEquals(1, result.getInvalidSCTs().size());
- assertEquals(VerifiedSCT.Status.INVALID_SIGNATURE,
- result.getInvalidSCTs().get(0).status);
+ assertEquals(
+ VerifiedSCT.Status.INVALID_SIGNATURE, result.getInvalidSCTs().get(0).getStatus());
}
@Test
public void test_verifySignedCertificateTimestamps_withUnknownLog() throws Exception {
- OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] { cert, ca };
+ OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] {cert, ca};
byte[] tlsExtension = readTestFile("ct-signed-timestamp-list-unknown");
- CTVerificationResult result =
- ctVerifier.verifySignedCertificateTimestamps(chain, tlsExtension, null);
+ VerificationResult result =
+ ctVerifier.verifySignedCertificateTimestamps(chain, tlsExtension, null);
assertEquals(0, result.getValidSCTs().size());
assertEquals(1, result.getInvalidSCTs().size());
- assertEquals(VerifiedSCT.Status.UNKNOWN_LOG,
- result.getInvalidSCTs().get(0).status);
+ assertEquals(VerifiedSCT.Status.UNKNOWN_LOG, result.getInvalidSCTs().get(0).getStatus());
}
@Test
public void test_verifySignedCertificateTimestamps_withInvalidEncoding() throws Exception {
- OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] { cert, ca };
+ OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] {cert, ca};
// Just some garbage data which will fail to deserialize
- byte[] tlsExtension = new byte[] { 1, 2, 3, 4 };
+ byte[] tlsExtension = new byte[] {1, 2, 3, 4};
- CTVerificationResult result =
- ctVerifier.verifySignedCertificateTimestamps(chain, tlsExtension, null);
+ VerificationResult result =
+ ctVerifier.verifySignedCertificateTimestamps(chain, tlsExtension, null);
assertEquals(0, result.getValidSCTs().size());
assertEquals(0, result.getInvalidSCTs().size());
}
@Test
public void test_verifySignedCertificateTimestamps_withInvalidOCSPResponse() throws Exception {
- OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] { cert, ca };
+ OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] {cert, ca};
// Just some garbage data which will fail to deserialize
- byte[] ocspResponse = new byte[] { 1, 2, 3, 4 };
+ byte[] ocspResponse = new byte[] {1, 2, 3, 4};
- CTVerificationResult result =
- ctVerifier.verifySignedCertificateTimestamps(chain, null, ocspResponse);
+ VerificationResult result =
+ ctVerifier.verifySignedCertificateTimestamps(chain, null, ocspResponse);
assertEquals(0, result.getValidSCTs().size());
assertEquals(0, result.getInvalidSCTs().size());
}
@Test
public void test_verifySignedCertificateTimestamps_withMultipleTimestamps() throws Exception {
- OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] { cert, ca };
+ OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] {cert, ca};
byte[] tlsExtension = readTestFile("ct-signed-timestamp-list-invalid");
byte[] ocspResponse = readTestFile("ocsp-response.der");
- CTVerificationResult result =
- ctVerifier.verifySignedCertificateTimestamps(chain, tlsExtension, ocspResponse);
+ VerificationResult result =
+ ctVerifier.verifySignedCertificateTimestamps(chain, tlsExtension, ocspResponse);
assertEquals(1, result.getValidSCTs().size());
assertEquals(1, result.getInvalidSCTs().size());
assertEquals(SignedCertificateTimestamp.Origin.OCSP_RESPONSE,
- result.getValidSCTs().get(0).sct.getOrigin());
+ result.getValidSCTs().get(0).getSct().getOrigin());
assertEquals(SignedCertificateTimestamp.Origin.TLS_EXTENSION,
- result.getInvalidSCTs().get(0).sct.getOrigin());
+ result.getInvalidSCTs().get(0).getSct().getOrigin());
}
}
-
diff --git a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLContextTest.java b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLContextTest.java
index 40acd1b..3ed4dd3 100644
--- a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLContextTest.java
+++ b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLContextTest.java
@@ -16,6 +16,7 @@
package org.conscrypt.javax.net.ssl;
+import static org.conscrypt.TestUtils.isTlsV1Supported;
import static org.conscrypt.TestUtils.isWindows;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -119,6 +120,18 @@
}
@Test
+ public void test_SSLContext_allProtocols() throws Exception {
+ SSLConfigurationAsserts.assertSSLContextDefaultConfiguration(SSLContext.getDefault());
+
+ for (String protocol : StandardNames.SSL_CONTEXT_PROTOCOLS) {
+ SSLContext sslContext = SSLContext.getInstance(protocol);
+ if (!protocol.equals(StandardNames.SSL_CONTEXT_PROTOCOLS_DEFAULT)) {
+ sslContext.init(null, null, null);
+ }
+ }
+ }
+
+ @Test
public void test_SSLContext_pskOnlyConfiguration_defaultProviderOnly() throws Exception {
// Test the scenario where only a PSKKeyManager is provided and no TrustManagers are
// provided.
diff --git a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketVersionCompatibilityTest.java b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketVersionCompatibilityTest.java
index 5ce4d5f..9e87aa5 100644
--- a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketVersionCompatibilityTest.java
+++ b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketVersionCompatibilityTest.java
@@ -16,10 +16,14 @@
package org.conscrypt.javax.net.ssl;
+
import static org.conscrypt.TestUtils.osName;
import static org.conscrypt.TestUtils.isOsx;
import static org.conscrypt.TestUtils.isLinux;
import static org.conscrypt.TestUtils.isWindows;
+import static org.conscrypt.TestUtils.isTlsV1Deprecated;
+import static org.conscrypt.TestUtils.isTlsV1Filtered;
+import static org.conscrypt.TestUtils.isTlsV1Supported;
import static org.conscrypt.TestUtils.UTF_8;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@@ -109,7 +113,9 @@
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import tests.net.DelegatingSSLSocketFactory;
@@ -123,6 +129,9 @@
@RunWith(Parameterized.class)
public class SSLSocketVersionCompatibilityTest {
+// @Rule
+// public TestRule switchTargetSdkVersionRule = SwitchTargetSdkVersionRule.getInstance();
+
@Parameterized.Parameters(name = "{index}: {0} client, {1} server")
public static Iterable<Object[]> data() {
return Arrays.asList(new Object[][] {
@@ -1690,7 +1699,7 @@
@Test
public void test_SSLSocket_ClientHello_ALPN() throws Exception {
final String[] protocolList = new String[] { "h2", "http/1.1" };
-
+
ForEachRunner.runNamed(new ForEachRunner.Callback<SSLSocketFactory>() {
@Override
public void run(SSLSocketFactory sslSocketFactory) throws Exception {
@@ -1926,7 +1935,36 @@
}
@Test
- public void test_SSLSocket_SSLv3Unsupported() throws Exception {
+ public void test_SSLSocket_TLSv1Supported() throws Exception {
+ assumeTrue(isTlsV1Supported());
+ TestSSLContext context = new TestSSLContext.Builder()
+ .clientProtocol(clientVersion)
+ .serverProtocol(serverVersion)
+ .build();
+ final SSLSocket client =
+ (SSLSocket) context.clientContext.getSocketFactory().createSocket();
+ client.setEnabledProtocols(new String[] {"TLSv1", "TLSv1.1"});
+ assertEquals(2, client.getEnabledProtocols().length);
+ }
+
+// @TargetSdkVersion(35)
+ @Test
+ public void test_SSLSocket_SSLv3Unsupported_35() throws Exception {
+ assumeFalse(isTlsV1Filtered());
+ TestSSLContext context = new TestSSLContext.Builder()
+ .clientProtocol(clientVersion)
+ .serverProtocol(serverVersion)
+ .build();
+ final SSLSocket client =
+ (SSLSocket) context.clientContext.getSocketFactory().createSocket();
+ assertThrows(IllegalArgumentException.class, () -> client.setEnabledProtocols(new String[] {"SSLv3"}));
+ assertThrows(IllegalArgumentException.class, () -> client.setEnabledProtocols(new String[] {"SSL"}));
+ }
+
+// @TargetSdkVersion(34)
+ @Test
+ @Ignore("For platform CTS only")
+ public void test_SSLSocket_SSLv3Unsupported_34() throws Exception {
TestSSLContext context = new TestSSLContext.Builder()
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
@@ -1944,6 +1982,41 @@
}
}
+// @TargetSdkVersion(34)
+ @Test
+ @Ignore("For platform CTS only")
+ public void test_TLSv1Filtered_34() throws Exception {
+ TestSSLContext context = new TestSSLContext.Builder()
+ .clientProtocol(clientVersion)
+ .serverProtocol(serverVersion)
+ .build();
+ final SSLSocket client =
+ (SSLSocket) context.clientContext.getSocketFactory().createSocket();
+ client.setEnabledProtocols(new String[] {"TLSv1", "TLSv1.1", "TLSv1.2"});
+ assertEquals(1, client.getEnabledProtocols().length);
+ assertEquals("TLSv1.2", client.getEnabledProtocols()[0]);
+ }
+
+// @TargetSdkVersion(35)
+ @Test
+ public void test_TLSv1Filtered_35() throws Exception {
+ assumeTrue(isTlsV1Filtered());
+ TestSSLContext context = new TestSSLContext.Builder()
+ .clientProtocol(clientVersion)
+ .serverProtocol(serverVersion)
+ .build();
+ final SSLSocket client =
+ (SSLSocket) context.clientContext.getSocketFactory().createSocket();
+ assertThrows(IllegalArgumentException.class, () ->
+ client.setEnabledProtocols(new String[] {"TLSv1", "TLSv1.1", "TLSv1.2"}));
+ }
+
+ @Test
+ public void test_TLSv1Unsupported_notEnabled() throws Exception {
+ assumeTrue(!isTlsV1Supported());
+ assertTrue(isTlsV1Deprecated());
+ }
+
// Under some circumstances, the file descriptor socket may get finalized but still
// be reused by the JDK's built-in HTTP connection reuse code. Ensure that a
// SocketException is thrown if that happens.
diff --git a/libcore-stub/src/main/java/libcore/net/NetworkSecurityPolicy.java b/libcore-stub/src/main/java/libcore/net/NetworkSecurityPolicy.java
new file mode 100644
index 0000000..e7ca0f1
--- /dev/null
+++ b/libcore-stub/src/main/java/libcore/net/NetworkSecurityPolicy.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2015 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 libcore.net;
+
+/**
+ * Network security policy for this process/application.
+ *
+ * <p>Network stacks/components are expected to honor this policy. Components which can use the
+ * Android framework API should be accessing this policy via the framework's
+ * {@code android.security.NetworkSecurityPolicy} instead of via this class.
+ *
+ * <p>The policy currently consists of a single flag: whether cleartext network traffic is
+ * permitted. See {@link #isCleartextTrafficPermitted()}.
+ */
+public abstract class NetworkSecurityPolicy {
+ private static volatile NetworkSecurityPolicy instance = new DefaultNetworkSecurityPolicy();
+
+ public static NetworkSecurityPolicy getInstance() {
+ return instance;
+ }
+
+ public static void setInstance(NetworkSecurityPolicy policy) {
+ if (policy == null) {
+ throw new NullPointerException("policy == null");
+ }
+ instance = policy;
+ }
+
+ /**
+ * Returns {@code true} if cleartext network traffic (e.g. HTTP, FTP, XMPP, IMAP, SMTP --
+ * without TLS or STARTTLS) is permitted for all network communications of this process.
+ *
+ * <p>{@link #isCleartextTrafficPermitted(String)} should be used to determine if cleartext
+ * traffic is permitted for a specific host.
+ *
+ * <p>When cleartext network traffic is not permitted, the platform's components (e.g. HTTP
+ * stacks, {@code WebView}, {@code MediaPlayer}) will refuse this process's requests to use
+ * cleartext traffic. Third-party libraries are encouraged to do the same.
+ *
+ * <p>This flag is honored on a best effort basis because it's impossible to prevent all
+ * cleartext traffic from an application given the level of access provided to applications on
+ * Android. For example, there's no expectation that {@link java.net.Socket} API will honor this
+ * flag. Luckily, most network traffic from apps is handled by higher-level network stacks which
+ * can be made to honor this flag. Platform-provided network stacks (e.g. HTTP and FTP) honor
+ * this flag from day one, and well-established third-party network stacks will eventually
+ * honor it.
+ */
+ public abstract boolean isCleartextTrafficPermitted();
+
+ /**
+ * Returns {@code true} if cleartext network traffic (e.g. HTTP, FTP, XMPP, IMAP, SMTP --
+ * without TLS or STARTTLS) is permitted for communicating with {@code hostname} for this
+ * process.
+ *
+ * <p>See {@link #isCleartextTrafficPermitted} for more details.
+ */
+ public abstract boolean isCleartextTrafficPermitted(String hostname);
+
+ /**
+ * Returns {@code true} if Certificate Transparency information is required to be presented by
+ * the server and verified by the client in TLS connections to {@code hostname}.
+ *
+ * <p>See RFC6962 section 3.3 for more details.
+ */
+ public abstract boolean isCertificateTransparencyVerificationRequired(String hostname);
+
+ public static final class DefaultNetworkSecurityPolicy extends NetworkSecurityPolicy {
+ @Override
+ public boolean isCleartextTrafficPermitted() {
+ return true;
+ }
+
+ @Override
+ public boolean isCleartextTrafficPermitted(String hostname) {
+ return isCleartextTrafficPermitted();
+ }
+
+ @Override
+ public boolean isCertificateTransparencyVerificationRequired(String hostname) {
+ return false;
+ }
+ }
+}
diff --git a/openjdk/src/main/java/org/conscrypt/Platform.java b/openjdk/src/main/java/org/conscrypt/Platform.java
index f5770ca..b3726fe 100644
--- a/openjdk/src/main/java/org/conscrypt/Platform.java
+++ b/openjdk/src/main/java/org/conscrypt/Platform.java
@@ -78,8 +78,8 @@
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509ExtendedTrustManager;
import javax.net.ssl.X509TrustManager;
-import org.conscrypt.ct.CTLogStore;
-import org.conscrypt.ct.CTPolicy;
+import org.conscrypt.ct.LogStore;
+import org.conscrypt.ct.Policy;
/**
* Platform-specific methods for OpenJDK.
@@ -718,11 +718,11 @@
return null;
}
- static CTLogStore newDefaultLogStore() {
+ static LogStore newDefaultLogStore() {
return null;
}
- static CTPolicy newDefaultPolicy(CTLogStore logStore) {
+ static Policy newDefaultPolicy() {
return null;
}
@@ -813,4 +813,12 @@
public static boolean isTlsV1Deprecated() {
return true;
}
+
+ public static boolean isTlsV1Filtered() {
+ return false;
+ }
+
+ public static boolean isTlsV1Supported() {
+ return true;
+ }
}
diff --git a/openjdk/src/test/java/org/conscrypt/ConscryptOpenJdkSuite.java b/openjdk/src/test/java/org/conscrypt/ConscryptOpenJdkSuite.java
index d5401e3..f5ae14b 100644
--- a/openjdk/src/test/java/org/conscrypt/ConscryptOpenJdkSuite.java
+++ b/openjdk/src/test/java/org/conscrypt/ConscryptOpenJdkSuite.java
@@ -18,8 +18,8 @@
import static org.conscrypt.TestUtils.installConscryptAsDefaultProvider;
-import org.conscrypt.ct.CTVerifierTest;
import org.conscrypt.ct.SerializationTest;
+import org.conscrypt.ct.VerifierTest;
import org.conscrypt.java.security.AlgorithmParameterGeneratorTestDH;
import org.conscrypt.java.security.AlgorithmParameterGeneratorTestDSA;
import org.conscrypt.java.security.AlgorithmParametersPSSTest;
@@ -111,7 +111,7 @@
TestSessionBuilderTest.class,
TrustManagerImplTest.class,
// org.conscrypt.ct tests
- CTVerifierTest.class,
+ VerifierTest.class,
SerializationTest.class,
// java.security tests
CertificateFactoryTest.class,
diff --git a/openjdk/src/test/java/org/conscrypt/ConscryptSocketTest.java b/openjdk/src/test/java/org/conscrypt/ConscryptSocketTest.java
index adb9184..4ffc56f 100644
--- a/openjdk/src/test/java/org/conscrypt/ConscryptSocketTest.java
+++ b/openjdk/src/test/java/org/conscrypt/ConscryptSocketTest.java
@@ -25,6 +25,9 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
@@ -639,6 +642,7 @@
@Test
public void test_setEnabledProtocols_FiltersSSLv3_HandshakeException() throws Exception {
+ assumeTrue(TestUtils.isTlsV1Filtered());
TestConnection connection = new TestConnection(new X509Certificate[] {cert, ca}, certKey);
connection.clientHooks = new ClientHooks() {
@@ -653,16 +657,56 @@
};
connection.doHandshake();
- assertThat(connection.clientException, instanceOf(SSLHandshakeException.class));
+ assertTrue("Expected SSLHandshakeException, but got "
+ + connection.clientException.getClass().getSimpleName()
+ + ": " + connection.clientException.getMessage(),
+ connection.clientException instanceof SSLHandshakeException);
assertTrue(
connection.clientException.getMessage().contains("SSLv3 is no longer supported"));
- assertThat(connection.serverException, instanceOf(SSLHandshakeException.class));
+ assertTrue("Expected SSLHandshakeException, but got "
+ + connection.serverException.getClass().getSimpleName()
+ + ": " + connection.serverException.getMessage(),
+ connection.serverException instanceof SSLHandshakeException);
assertFalse(connection.clientHooks.isHandshakeCompleted);
assertFalse(connection.serverHooks.isHandshakeCompleted);
}
@Test
+ public void test_setEnabledProtocols_RejectsSSLv3_IfNotFiltered() throws Exception {
+ assumeFalse(TestUtils.isTlsV1Filtered());
+ TestConnection connection = new TestConnection(new X509Certificate[] {cert, ca}, certKey);
+
+ connection.clientHooks = new ClientHooks() {
+ @Override
+ public AbstractConscryptSocket createSocket(ServerSocket listener) throws IOException {
+ try (AbstractConscryptSocket socket = super.createSocket(listener)) {
+ socket.setEnabledProtocols(new String[]{"SSLv3"});
+ fail("SSLv3 should be rejected");
+ return socket;
+ }
+ }
+ };
+
+ connection.doHandshake();
+ assertTrue("Expected SSLHandshakeException, but got "
+ + connection.clientException.getClass().getSimpleName()
+ + ": " + connection.clientException.getMessage(),
+ connection.clientException instanceof IllegalArgumentException);
+ assertTrue(
+ connection.clientException.getMessage().contains("SSLv3 is not supported"));
+ assertTrue("Expected SSLHandshakeException, but got "
+ + connection.serverException.getClass().getSimpleName()
+ + ": " + connection.serverException.getMessage(),
+ connection.serverException instanceof SSLHandshakeException);
+
+ assertFalse(connection.clientHooks.isHandshakeCompleted);
+ assertFalse(connection.serverHooks.isHandshakeCompleted);
+ }
+
+
+
+ @Test
public void savedSessionWorksAfterClose() throws Exception {
String alpnProtocol = "spdy/2";
String[] alpnProtocols = new String[]{alpnProtocol};
diff --git a/openjdk/src/test/resources/test_blocklist_ca2.pem b/openjdk/src/test/resources/test_blocklist_ca2.pem
new file mode 100644
index 0000000..ca33594
--- /dev/null
+++ b/openjdk/src/test/resources/test_blocklist_ca2.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDHTCCAgWgAwIBAgIUY4GMRArmOpdsPFvMChsC64re26kwDQYJKoZIhvcNAQEL
+BQAwHjEcMBoGA1UEAwwTYmxhY2tsaXN0IHRlc3QgQ0EgMjAeFw0yNDA2MDcwNTAx
+NTJaFw0zNDA2MDUwNTAxNTJaMB4xHDAaBgNVBAMME2JsYWNrbGlzdCB0ZXN0IENB
+IDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCZiCGg9O8L4drk5R0J
+AwFvnm4aS2NZXnD0xUwE4iK0Nhw0Fb5A/yd4aW1RL8gCqcYXQywHrQAZq+PBUnG9
+ArtRoOTugkSD7gRFPFi7xcq6tdjVinkG33BUqSoyZb584sAwLfuKmG4WgGF6Bm2g
+vTh4kzvvqzJmWiDMHAxJCUiE+OQ3kBKoGUqplyn97Td3ZKs6mi1lAYV5xAu5M3EK
+fFC6g4Ckvy/5ZMLrROwz2bTtMQEsIRAeFRWWQJlDKF4RbceFmhDavwMSHCZu9ZGM
+HPAapXg+3LdQM+uqiQoguItY9dv1SJbWlcsxl5NzqDkPLfZeIhMTEYk/PtMKIsLk
+7aUvAgMBAAGjUzBRMB0GA1UdDgQWBBQyrTPvjxNE7FvnFRmD50u9H+o5nTAfBgNV
+HSMEGDAWgBQyrTPvjxNE7FvnFRmD50u9H+o5nTAPBgNVHRMBAf8EBTADAQH/MA0G
+CSqGSIb3DQEBCwUAA4IBAQBOAdrI1ycTfwL/PMUCXAlwbosKFyDESgGgbfE0blHc
+MkT0AY4ODxU2+oEM+UTwFJ/3ZC88hlJdhqL4spwC1lTCP8DpydxzR+KN8iEUDQT6
+MeXjmll7qUzk5f+/xF3BCp+5ZqympYNPT3vwf25k308aT6/LNrWCvQYxPJJue1aS
+kPQU6y3ktgsdXmSL696fGi/VcuNmJKjDJn+po7NS+F07Addnf5t1v9iwNMI2OwiG
+LzZ/3wiQNU03KlM6fiy++e0VIxrxShg1cYMlKQanzFo3cFkigT5JNDHTA2gVV0Kz
+7bEMyIVMLL59ky3bVdxVCJdi5brhgqV1C8Wly0bgm555
+-----END CERTIFICATE-----
diff --git a/openjdk/src/test/resources/test_blocklist_ca2_key.pem b/openjdk/src/test/resources/test_blocklist_ca2_key.pem
new file mode 100644
index 0000000..f8a4bb5
--- /dev/null
+++ b/openjdk/src/test/resources/test_blocklist_ca2_key.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCZiCGg9O8L4drk
+5R0JAwFvnm4aS2NZXnD0xUwE4iK0Nhw0Fb5A/yd4aW1RL8gCqcYXQywHrQAZq+PB
+UnG9ArtRoOTugkSD7gRFPFi7xcq6tdjVinkG33BUqSoyZb584sAwLfuKmG4WgGF6
+Bm2gvTh4kzvvqzJmWiDMHAxJCUiE+OQ3kBKoGUqplyn97Td3ZKs6mi1lAYV5xAu5
+M3EKfFC6g4Ckvy/5ZMLrROwz2bTtMQEsIRAeFRWWQJlDKF4RbceFmhDavwMSHCZu
+9ZGMHPAapXg+3LdQM+uqiQoguItY9dv1SJbWlcsxl5NzqDkPLfZeIhMTEYk/PtMK
+IsLk7aUvAgMBAAECggEAP2Jww8MrJ4QseyBNvukzQBIv0YI7N2uihaMokcGMY0sN
+lME/RRUyBee8nm50DAlsQzFTra2SI4cP5cG0PDyy+e3LZd55C+CJec4Csi7j1fZ6
+WRqsgZZgiUs3pQvVOzjf8GQje6IXnQmOdLLPsrM766eZcIaErbXa4XlY5xRCkMaO
+RavPwvbAFKKKCjndJs5OZpwZwR/UcHMlYLR3Dg+ozzlG48dMr0ao/3bimGPsc8/t
+eXzlVq2vi8xdRm53MdzarH9VWTYg3AalKEedLxxfvyGaZrpviCZdftHvH+sRruCy
+D280CSlmwLFc5xVGQTN9zNolEWarxU4fcusgmyfXrQKBgQDVqbrSOBDKF1WlyT/J
+vsCCRywoTBVDxvB0BeoC3ICHh5KISbtfjqy/CR4qLGnuov0ZXVR5UmGHs3gl21qm
+HjX1IRWo+8bfaoxBqK1zcFd4XtkPNPtEAcQvH0MFjnBXd0WcC/7qzvUUIC2RGuWY
+YmxCj4uFdSTLwLwiTDwXdAy7HQKBgQC39C8Ygu7MlTO9EpyQdoOSbQC+VgaPPABO
+LgQLoGH5K7sNOjutHo3Bt3iKFsdPkTHkZc3alBhH+61zgbsmGFrNJI+XRgkeGpLR
+/6Bbi7ivswsJcHmodOKOr62RKMJArwqOqN2MhNlZTBDjgIy4owN2B0YHwd9GTQWu
+JOjfwHwjuwKBgQCpxjtPjQMyQcZpfHc2LF81Za5dus7u0yX/Wy+t5F4w0vYJW2UK
+sgjrpygT5MSrvVEVlYZo/J/Ivz+J/TmTY9AGHqriYmWM41HdXlWss6idWehp3/SD
+/k9QDiwoPx1fMsPaEeIV3Cr7OfJbKZ8kLZjObtczTXjWeihDrIXXMPxotQKBgQC3
+mO1QZ43zfo7PDL5aqQ6UnFp7ndyaJOahIOhEumROjsj4YMCi/rW5PGcAW8+9qErF
+jJ4ypFC/t3/cowSo9vHZgb4W233KH/edxKbF9+Py6J4BY9LowRBGHSz8jlOiv5Gn
+5P6KeyV7LKJGjkzlEz4nFQdeQq+XuNQMhSYv/CtqdQKBgEQKDQRiKgl4DjbDWNjV
+3EUFH/LBvwPUXnrfeECXuyiTrTHrUsTtahcrjTQs/ByX15YSmyVhbUSsYQ6dkafZ
+aVNl9zNLQYYa2y3VbBs0q5xEW+iCmc4w/LsW+mnUiFmKlYVokwGVEDkqMyDkk3oB
+58f2hIryzy4MDkh0Iqixr6o1
+-----END PRIVATE KEY-----
diff --git a/platform/src/main/java/org/conscrypt/CertBlocklistImpl.java b/platform/src/main/java/org/conscrypt/CertBlocklistImpl.java
index 2428d4c..305b74b 100644
--- a/platform/src/main/java/org/conscrypt/CertBlocklistImpl.java
+++ b/platform/src/main/java/org/conscrypt/CertBlocklistImpl.java
@@ -24,14 +24,16 @@
import java.io.IOException;
import java.io.RandomAccessFile;
import java.math.BigInteger;
-import java.security.GeneralSecurityException;
import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -41,14 +43,35 @@
private static final Logger logger = Logger.getLogger(CertBlocklistImpl.class.getName());
private final Set<BigInteger> serialBlocklist;
- private final Set<ByteString> pubkeyBlocklist;
+ private final Set<ByteArray> sha1PubkeyBlocklist;
+ private final Set<ByteArray> sha256PubkeyBlocklist;
+ private Map<ByteArray, Boolean> cache;
+
+ /**
+ * Number of entries in the cache. The cache contains public keys which are
+ * at most 4096 bits (512 bytes) for RSA. For a cache size of 64, that is
+ * at most 512 * 64 = 32,768 bytes.
+ */
+ private static final int CACHE_SIZE = 64;
/**
* public for testing only.
*/
- public CertBlocklistImpl(Set<BigInteger> serialBlocklist, Set<ByteString> pubkeyBlocklist) {
+ public CertBlocklistImpl(Set<BigInteger> serialBlocklist, Set<ByteArray> sha1PubkeyBlocklist) {
+ this(serialBlocklist, sha1PubkeyBlocklist, Collections.emptySet());
+ }
+
+ public CertBlocklistImpl(Set<BigInteger> serialBlocklist, Set<ByteArray> sha1PubkeyBlocklist,
+ Set<ByteArray> sha256PubkeyBlocklist) {
+ this.cache = Collections.synchronizedMap(new LinkedHashMap<ByteArray, Boolean>() {
+ @Override
+ protected boolean removeEldestEntry(Map.Entry<ByteArray, Boolean> eldest) {
+ return size() > CACHE_SIZE;
+ }
+ });
this.serialBlocklist = serialBlocklist;
- this.pubkeyBlocklist = pubkeyBlocklist;
+ this.sha1PubkeyBlocklist = sha1PubkeyBlocklist;
+ this.sha256PubkeyBlocklist = sha256PubkeyBlocklist;
}
public static CertBlocklist getDefault() {
@@ -56,10 +79,14 @@
String blocklistRoot = androidData + "/misc/keychain/";
String defaultPubkeyBlocklistPath = blocklistRoot + "pubkey_blacklist.txt";
String defaultSerialBlocklistPath = blocklistRoot + "serial_blacklist.txt";
+ String defaultPubkeySha256BlocklistPath = blocklistRoot + "pubkey_sha256_blocklist.txt";
- Set<ByteString> pubkeyBlocklist = readPublicKeyBlockList(defaultPubkeyBlocklistPath);
+ Set<ByteArray> sha1PubkeyBlocklist =
+ readPublicKeyBlockList(defaultPubkeyBlocklistPath, "SHA-1");
+ Set<ByteArray> sha256PubkeyBlocklist =
+ readPublicKeyBlockList(defaultPubkeySha256BlocklistPath, "SHA-256");
Set<BigInteger> serialBlocklist = readSerialBlockList(defaultSerialBlocklistPath);
- return new CertBlocklistImpl(serialBlocklist, pubkeyBlocklist);
+ return new CertBlocklistImpl(serialBlocklist, sha1PubkeyBlocklist, sha256PubkeyBlocklist);
}
private static boolean isHex(String value) {
@@ -72,8 +99,8 @@
}
}
- private static boolean isPubkeyHash(String value) {
- if (value.length() != 40) {
+ private static boolean isPubkeyHash(String value, int expectedHashLength) {
+ if (value.length() != expectedHashLength) {
logger.log(Level.WARNING, "Invalid pubkey hash length: " + value.length());
return false;
}
@@ -129,32 +156,12 @@
}
private static Set<BigInteger> readSerialBlockList(String path) {
-
- /* Start out with a base set of known bad values.
- *
- * WARNING: Do not add short serials to this list!
- *
- * Since this currently doesn't compare the serial + issuer, you
- * should only add serials that have enough entropy here. Short
- * serials may inadvertently match a certificate that was issued
- * not in compliance with the Baseline Requirements.
+ /*
+ * Deprecated. Serials may inadvertently match a certificate that was
+ * issued not in compliance with the Baseline Requirements. Prefer
+ * using the certificate public key.
*/
- Set<BigInteger> bl = new HashSet<BigInteger>(Arrays.asList(
- // From http://src.chromium.org/viewvc/chrome/trunk/src/net/base/x509_certificate.cc?revision=78748&view=markup
- // Not a real certificate. For testing only.
- new BigInteger("077a59bcd53459601ca6907267a6dd1c", 16),
- new BigInteger("047ecbe9fca55f7bd09eae36e10cae1e", 16),
- new BigInteger("d8f35f4eb7872b2dab0692e315382fb0", 16),
- new BigInteger("b0b7133ed096f9b56fae91c874bd3ac0", 16),
- new BigInteger("9239d5348f40d1695a745470e1f23f43", 16),
- new BigInteger("e9028b9578e415dc1a710a2b88154447", 16),
- new BigInteger("d7558fdaf5f1105bb213282b707729a3", 16),
- new BigInteger("f5c86af36162f13a64f54f6dc9587c06", 16),
- new BigInteger("392a434f0e07df1f8aa305de34e0c229", 16),
- new BigInteger("3e75ced46b693021218830ae86a82a71", 16)
- ));
-
- // attempt to augment it with values taken from gservices
+ Set<BigInteger> bl = new HashSet<BigInteger>();
String serialBlocklist = readBlocklist(path);
if (!serialBlocklist.equals("")) {
for (String value : serialBlocklist.split(",", -1)) {
@@ -170,15 +177,13 @@
return Collections.unmodifiableSet(bl);
}
- private static Set<ByteString> readPublicKeyBlockList(String path) {
-
- // start out with a base set of known bad values
- Set<ByteString> bl = new HashSet<ByteString>(toByteStrings(
+ static final byte[][] SHA1_BUILTINS = {
// Blocklist test cert for CTS. The cert and key can be found in
// src/test/resources/blocklist_test_ca.pem and
// src/test/resources/blocklist_test_ca_key.pem.
"bae78e6bed65a2bf60ddedde7fd91e825865e93d".getBytes(UTF_8),
- // From http://src.chromium.org/viewvc/chrome/branches/782/src/net/base/x509_certificate.cc?r1=98750&r2=98749&pathrev=98750
+ // From
+ // http://src.chromium.org/viewvc/chrome/branches/782/src/net/base/x509_certificate.cc?r1=98750&r2=98749&pathrev=98750
// C=NL, O=DigiNotar, CN=DigiNotar Root CA/emailAddress=info@diginotar.nl
"410f36363258f30b347d12ce4863e433437806a8".getBytes(UTF_8),
// Subject: CN=DigiNotar Cyber CA
@@ -205,16 +210,49 @@
"783333c9687df63377efceddd82efa9101913e8e".getBytes(UTF_8),
// Subject: Subject: C=FR, O=DG Tr\xC3\xA9sor, CN=AC DG Tr\xC3\xA9sor SSL
// Issuer: C=FR, O=DGTPE, CN=AC DGTPE Signature Authentification
- "3ecf4bbbe46096d514bb539bb913d77aa4ef31bf".getBytes(UTF_8)
- ));
+ "3ecf4bbbe46096d514bb539bb913d77aa4ef31bf".getBytes(UTF_8),
+ };
+
+ static final byte[][] SHA256_BUILTINS = {
+ // Blocklist test cert for CTS. The cert and key can be found in
+ // src/test/resources/blocklist_test_ca2.pem and
+ // src/test/resources/blocklist_test_ca2_key.pem.
+ "809964b15e9bd312993d9984045551f503f2cf8e68f39188921ba30fe623f9fd".getBytes(UTF_8),
+ };
+
+ private static Set<ByteArray> readPublicKeyBlockList(String path, String hashType) {
+ Set<ByteArray> bl;
+
+ switch (hashType) {
+ case "SHA-1":
+ bl = new HashSet<ByteArray>(toByteArrays(SHA1_BUILTINS));
+ break;
+ case "SHA-256":
+ bl = new HashSet<ByteArray>(toByteArrays(SHA256_BUILTINS));
+ break;
+ default:
+ throw new RuntimeException(
+ "Unknown hashType: " + hashType + ". Expected SHA-1 or SHA-256");
+ }
+
+ MessageDigest md;
+ try {
+ md = MessageDigest.getInstance(hashType);
+ } catch (NoSuchAlgorithmException e) {
+ logger.log(Level.SEVERE, "Unable to get " + hashType + " MessageDigest", e);
+ return bl;
+ }
+ // The hashes are encoded with hexadecimal values. There should be
+ // twice as many characters as the digest length in bytes.
+ int hashLength = md.getDigestLength() * 2;
// attempt to augment it with values taken from gservices
String pubkeyBlocklist = readBlocklist(path);
if (!pubkeyBlocklist.equals("")) {
for (String value : pubkeyBlocklist.split(",", -1)) {
value = value.trim();
- if (isPubkeyHash(value)) {
- bl.add(new ByteString(value.getBytes(UTF_8)));
+ if (isPubkeyHash(value, hashLength)) {
+ bl.add(new ByteArray(value.getBytes(UTF_8)));
} else {
logger.log(Level.WARNING, "Tried to blocklist invalid pubkey " + value);
}
@@ -224,22 +262,46 @@
return bl;
}
- @Override
- public boolean isPublicKeyBlockListed(PublicKey publicKey) {
- byte[] encoded = publicKey.getEncoded();
+ private static boolean isPublicKeyBlockListed(
+ byte[] encodedPublicKey, Set<ByteArray> blocklist, String hashType) {
MessageDigest md;
try {
- md = MessageDigest.getInstance("SHA1");
- } catch (GeneralSecurityException e) {
- logger.log(Level.SEVERE, "Unable to get SHA1 MessageDigest", e);
+ md = MessageDigest.getInstance(hashType);
+ } catch (NoSuchAlgorithmException e) {
+ logger.log(Level.SEVERE, "Unable to get " + hashType + " MessageDigest", e);
return false;
}
- byte[] out = toHex(md.digest(encoded));
- for (ByteString blocklisted : pubkeyBlocklist) {
- if (Arrays.equals(blocklisted.bytes, out)) {
+ ByteArray out = new ByteArray(toHex(md.digest(encodedPublicKey)));
+ if (blocklist.contains(out)) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isPublicKeyBlockListed(PublicKey publicKey) {
+ byte[] encodedPublicKey = publicKey.getEncoded();
+ // cacheKey is a view on encodedPublicKey. Because it is used as a key
+ // for a Map, its underlying array (encodedPublicKey) should not be
+ // modified.
+ ByteArray cacheKey = new ByteArray(encodedPublicKey);
+ Boolean cachedResult = cache.get(cacheKey);
+ if (cachedResult != null) {
+ return cachedResult.booleanValue();
+ }
+ if (!sha1PubkeyBlocklist.isEmpty()) {
+ if (isPublicKeyBlockListed(encodedPublicKey, sha1PubkeyBlocklist, "SHA-1")) {
+ cache.put(cacheKey, true);
return true;
}
}
+ if (!sha256PubkeyBlocklist.isEmpty()) {
+ if (isPublicKeyBlockListed(encodedPublicKey, sha256PubkeyBlocklist, "SHA-256")) {
+ cache.put(cacheKey, true);
+ return true;
+ }
+ }
+ cache.put(cacheKey, false);
return false;
}
@@ -263,37 +325,11 @@
return serialBlocklist.contains(serial);
}
- private static List<ByteString> toByteStrings(byte[]... allBytes) {
- List<ByteString> byteStrings = new ArrayList<>(allBytes.length + 1);
+ private static List<ByteArray> toByteArrays(byte[]... allBytes) {
+ List<ByteArray> byteArrays = new ArrayList<>(allBytes.length + 1);
for (byte[] bytes : allBytes) {
- byteStrings.add(new ByteString(bytes));
+ byteArrays.add(new ByteArray(bytes));
}
- return byteStrings;
- }
-
- private static class ByteString {
- final byte[] bytes;
-
- public ByteString(byte[] bytes) {
- this.bytes = bytes;
- }
-
- @Override
- public boolean equals(Object o) {
- if (o == this) {
- return true;
- }
- if (!(o instanceof ByteString)) {
- return false;
- }
-
- ByteString other = (ByteString) o;
- return Arrays.equals(bytes, other.bytes);
- }
-
- @Override
- public int hashCode() {
- return Arrays.hashCode(bytes);
- }
+ return byteArrays;
}
}
diff --git a/platform/src/main/java/org/conscrypt/InternalUtil.java b/platform/src/main/java/org/conscrypt/InternalUtil.java
deleted file mode 100644
index 39558c4..0000000
--- a/platform/src/main/java/org/conscrypt/InternalUtil.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2017 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;
-
-import java.io.InputStream;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.PublicKey;
-import org.conscrypt.OpenSSLX509CertificateFactory.ParsingException;
-
-/**
- * Helper to initialize the JNI libraries. This version runs when compiled
- * as part of the platform.
- */
-@Internal
-public final class InternalUtil {
- public static PublicKey logKeyToPublicKey(byte[] logKey)
- throws NoSuchAlgorithmException {
- try {
- return new OpenSSLKey(NativeCrypto.EVP_parse_public_key(logKey)).getPublicKey();
- } catch (ParsingException e) {
- throw new NoSuchAlgorithmException(e);
- }
- }
-
- public static PublicKey readPublicKeyPem(InputStream pem) throws InvalidKeyException, NoSuchAlgorithmException {
- return OpenSSLKey.fromPublicKeyPemInputStream(pem).getPublicKey();
- }
-
- private InternalUtil() {
- }
-}
diff --git a/platform/src/main/java/org/conscrypt/Platform.java b/platform/src/main/java/org/conscrypt/Platform.java
index 36bc5bc..85bd6da 100644
--- a/platform/src/main/java/org/conscrypt/Platform.java
+++ b/platform/src/main/java/org/conscrypt/Platform.java
@@ -25,6 +25,7 @@
import android.system.StructTimeval;
import dalvik.system.BlockGuard;
import dalvik.system.CloseGuard;
+import dalvik.system.VMRuntime;
import java.io.FileDescriptor;
import java.io.IOException;
import java.lang.System;
@@ -61,12 +62,14 @@
import javax.net.ssl.StandardConstants;
import javax.net.ssl.X509ExtendedTrustManager;
import javax.net.ssl.X509TrustManager;
-import org.conscrypt.ct.CTLogStore;
-import org.conscrypt.ct.CTLogStoreImpl;
-import org.conscrypt.ct.CTPolicy;
-import org.conscrypt.ct.CTPolicyImpl;
+import libcore.net.NetworkSecurityPolicy;
+import org.conscrypt.ct.LogStore;
+import org.conscrypt.ct.LogStoreImpl;
+import org.conscrypt.ct.Policy;
+import org.conscrypt.ct.PolicyImpl;
import org.conscrypt.metrics.CipherSuite;
import org.conscrypt.metrics.ConscryptStatsLog;
+import org.conscrypt.metrics.OptionalMethod;
import org.conscrypt.metrics.Protocol;
import sun.security.x509.AlgorithmId;
@@ -461,6 +464,10 @@
}
static boolean isCTVerificationRequired(String hostname) {
+ if (Flags.certificateTransparencyPlatform()) {
+ return NetworkSecurityPolicy.getInstance()
+ .isCertificateTransparencyVerificationRequired(hostname);
+ }
return false;
}
@@ -486,12 +493,12 @@
return CertBlocklistImpl.getDefault();
}
- static CTLogStore newDefaultLogStore() {
- return new CTLogStoreImpl();
+ static LogStore newDefaultLogStore() {
+ return new LogStoreImpl();
}
- static CTPolicy newDefaultPolicy(CTLogStore logStore) {
- return new CTPolicyImpl(logStore, 2);
+ static Policy newDefaultPolicy() {
+ return new PolicyImpl();
}
static boolean serverNamePermitted(SSLParametersImpl parameters, String serverName) {
@@ -537,6 +544,34 @@
}
public static boolean isTlsV1Deprecated() {
+ return true;
+ }
+
+ public static boolean isTlsV1Filtered() {
+ Object targetSdkVersion = getTargetSdkVersion();
+ if ((targetSdkVersion != null) && ((int) targetSdkVersion > 34))
+ return false;
+ return true;
+ }
+
+ public static boolean isTlsV1Supported() {
return false;
}
+
+ static Object getTargetSdkVersion() {
+ try {
+ Class<?> vmRuntime = Class.forName("dalvik.system.VMRuntime");
+ if (vmRuntime == null) {
+ return null;
+ }
+ OptionalMethod getSdkVersion =
+ new OptionalMethod(vmRuntime,
+ "getTargetSdkVersion");
+ return getSdkVersion.invokeStatic();
+ } catch (ClassNotFoundException e) {
+ return null;
+ } catch (NullPointerException e) {
+ return null;
+ }
+ }
}
diff --git a/platform/src/main/java/org/conscrypt/TrustedCertificateStore.java b/platform/src/main/java/org/conscrypt/TrustedCertificateStore.java
index 14df376..bcf7816 100644
--- a/platform/src/main/java/org/conscrypt/TrustedCertificateStore.java
+++ b/platform/src/main/java/org/conscrypt/TrustedCertificateStore.java
@@ -35,6 +35,7 @@
import java.util.List;
import java.util.Set;
import javax.security.auth.x500.X500Principal;
+import org.conscrypt.ArrayUtils;
import org.conscrypt.io.IoUtils;
import org.conscrypt.metrics.OptionalMethod;
@@ -116,7 +117,7 @@
if ((System.getProperty("system.certs.enabled") != null)
&& (System.getProperty("system.certs.enabled")).equals("true"))
return false;
- if (updatableDir.exists() && !(updatableDir.list().length == 0))
+ if (updatableDir.exists() && !(ArrayUtils.isEmpty(updatableDir.list())))
return true;
return false;
}
diff --git a/platform/src/main/java/org/conscrypt/ct/CTLogStoreImpl.java b/platform/src/main/java/org/conscrypt/ct/CTLogStoreImpl.java
deleted file mode 100644
index 0f37a8d..0000000
--- a/platform/src/main/java/org/conscrypt/ct/CTLogStoreImpl.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2015 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 java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.PublicKey;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Scanner;
-import java.util.Set;
-import org.conscrypt.Internal;
-import org.conscrypt.InternalUtil;
-
-@Internal
-public class CTLogStoreImpl implements CTLogStore {
- private static final Charset US_ASCII = StandardCharsets.US_ASCII;
-
- /**
- * Thrown when parsing of a log file fails.
- */
- public static class InvalidLogFileException extends Exception {
- public InvalidLogFileException() {
- }
-
- public InvalidLogFileException(String message) {
- super(message);
- }
-
- public InvalidLogFileException(String message, Throwable cause) {
- super(message, cause);
- }
-
- public InvalidLogFileException(Throwable cause) {
- super(cause);
- }
- }
-
- private static final File defaultUserLogDir;
- private static final File defaultSystemLogDir;
- // Lazy loaded by CTLogStoreImpl()
- private static volatile CTLogInfo[] defaultFallbackLogs = null;
- static {
- String ANDROID_DATA = System.getenv("ANDROID_DATA");
- String ANDROID_ROOT = System.getenv("ANDROID_ROOT");
- defaultUserLogDir = new File(ANDROID_DATA + "/misc/keychain/trusted_ct_logs/current/");
- defaultSystemLogDir = new File(ANDROID_ROOT + "/etc/security/ct_known_logs/");
- }
-
- private final File userLogDir;
- private final File systemLogDir;
- private final CTLogInfo[] fallbackLogs;
-
- private final HashMap<ByteBuffer, CTLogInfo> logCache = new HashMap<>();
- private final Set<ByteBuffer> missingLogCache
- = Collections.synchronizedSet(new HashSet<ByteBuffer>());
-
- public CTLogStoreImpl() {
- this(defaultUserLogDir,
- defaultSystemLogDir,
- getDefaultFallbackLogs());
- }
-
- public CTLogStoreImpl(File userLogDir, File systemLogDir, CTLogInfo[] fallbackLogs) {
- this.userLogDir = userLogDir;
- this.systemLogDir = systemLogDir;
- this.fallbackLogs = fallbackLogs;
- }
-
- @Override
- public CTLogInfo getKnownLog(byte[] logId) {
- ByteBuffer buf = ByteBuffer.wrap(logId);
- CTLogInfo log = logCache.get(buf);
- if (log != null) {
- return log;
- }
- if (missingLogCache.contains(buf)) {
- return null;
- }
-
- log = findKnownLog(logId);
- if (log != null) {
- logCache.put(buf, log);
- } else {
- missingLogCache.add(buf);
- }
-
- return log;
- }
-
- private CTLogInfo findKnownLog(byte[] logId) {
- String filename = hexEncode(logId);
- try {
- return loadLog(new File(userLogDir, filename));
- } catch (InvalidLogFileException e) {
- return null;
- } catch (FileNotFoundException e) {
- // Ignored
- }
-
- try {
- return loadLog(new File(systemLogDir, filename));
- } catch (InvalidLogFileException e) {
- return null;
- } catch (FileNotFoundException e) {
- // Ignored
- }
-
- // If the updateable logs dont exist then use the fallback logs.
- if (!userLogDir.exists()) {
- for (CTLogInfo log: fallbackLogs) {
- if (Arrays.equals(logId, log.getID())) {
- return log;
- }
- }
- }
- return null;
- }
-
- public static CTLogInfo[] getDefaultFallbackLogs() {
- CTLogInfo[] result = defaultFallbackLogs;
- if (result == null) {
- // single-check idiom
- defaultFallbackLogs = result = createDefaultFallbackLogs();
- }
- return result;
- }
-
- private static CTLogInfo[] createDefaultFallbackLogs() {
- CTLogInfo[] logs = new CTLogInfo[KnownLogs.LOG_COUNT];
- for (int i = 0; i < KnownLogs.LOG_COUNT; i++) {
- try {
- PublicKey key = InternalUtil.logKeyToPublicKey(KnownLogs.LOG_KEYS[i]);
-
- logs[i] = new CTLogInfo(key,
- KnownLogs.LOG_DESCRIPTIONS[i],
- KnownLogs.LOG_URLS[i]);
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(e);
- }
- }
-
- defaultFallbackLogs = logs;
- return logs;
- }
-
- /**
- * Load a CTLogInfo from a file.
- * @throws FileNotFoundException if the file does not exist
- * @throws InvalidLogFileException if the file could not be parsed properly
- * @return a CTLogInfo or null if the file is empty
- */
- public static CTLogInfo loadLog(File file) throws FileNotFoundException,
- InvalidLogFileException {
- return loadLog(new FileInputStream(file));
- }
-
- /**
- * Load a CTLogInfo from a textual representation. Closes {@code input} upon completion
- * of loading.
- *
- * @throws InvalidLogFileException if the input could not be parsed properly
- * @return a CTLogInfo or null if the input is empty
- */
- public static CTLogInfo loadLog(InputStream input) throws InvalidLogFileException {
- final Scanner scan = new Scanner(input, "UTF-8");
- scan.useDelimiter("\n");
-
- String description = null;
- String url = null;
- String key = null;
- try {
- // If the scanner can't even read one token then the file must be empty/blank
- if (!scan.hasNext()) {
- return null;
- }
-
- while (scan.hasNext()) {
- String[] parts = scan.next().split(":", 2);
- if (parts.length < 2) {
- continue;
- }
-
- String name = parts[0];
- String value = parts[1];
- switch (name) {
- case "description":
- description = value;
- break;
- case "url":
- url = value;
- break;
- case "key":
- key = value;
- break;
- }
- }
- } finally {
- scan.close();
- }
-
- if (description == null || url == null || key == null) {
- throw new InvalidLogFileException("Missing one of 'description', 'url' or 'key'");
- }
-
- PublicKey pubkey;
- try {
- pubkey = InternalUtil.readPublicKeyPem(new ByteArrayInputStream(
- ("-----BEGIN PUBLIC KEY-----\n" +
- key + "\n" +
- "-----END PUBLIC KEY-----").getBytes(US_ASCII)));
- } catch (InvalidKeyException e) {
- throw new InvalidLogFileException(e);
- } catch (NoSuchAlgorithmException e) {
- throw new InvalidLogFileException(e);
- }
-
- return new CTLogInfo(pubkey, description, url);
- }
-
- private final static char[] HEX_DIGITS = new char[] {
- '0', '1', '2', '3', '4', '5', '6', '7',
- '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
- };
-
- private static String hexEncode(byte[] data) {
- StringBuilder sb = new StringBuilder(data.length * 2);
- for (byte b: data) {
- sb.append(HEX_DIGITS[(b >> 4) & 0x0f]);
- sb.append(HEX_DIGITS[b & 0x0f]);
- }
- return sb.toString();
- }
-}
diff --git a/platform/src/main/java/org/conscrypt/ct/CTPolicyImpl.java b/platform/src/main/java/org/conscrypt/ct/CTPolicyImpl.java
deleted file mode 100644
index 3faca6f..0000000
--- a/platform/src/main/java/org/conscrypt/ct/CTPolicyImpl.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2015 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 java.security.cert.X509Certificate;
-import java.util.HashSet;
-import java.util.Set;
-import org.conscrypt.Internal;
-
-@Internal
-public class CTPolicyImpl implements CTPolicy {
- private final CTLogStore logStore;
- private final int minimumLogCount;
-
- public CTPolicyImpl(CTLogStore logStore, int minimumLogCount) {
- this.logStore = logStore;
- this.minimumLogCount = minimumLogCount;
- }
-
- @Override
- public boolean doesResultConformToPolicy(CTVerificationResult result, String hostname,
- X509Certificate[] chain) {
- Set<CTLogInfo> logSet = new HashSet<>();
- for (VerifiedSCT verifiedSCT: result.getValidSCTs()) {
- CTLogInfo log = logStore.getKnownLog(verifiedSCT.sct.getLogID());
- if (log != null) {
- logSet.add(log);
- }
- }
-
- return logSet.size() >= minimumLogCount;
- }
-}
diff --git a/platform/src/main/java/org/conscrypt/ct/KnownLogs.java b/platform/src/main/java/org/conscrypt/ct/KnownLogs.java
deleted file mode 100644
index dba00cb..0000000
--- a/platform/src/main/java/org/conscrypt/ct/KnownLogs.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2015 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.
- */
-
-/* This file is generated by print_log_list.py
- * https://github.com/google/certificate-transparency/blob/master/python/utilities/log_list/print_log_list.py */
-
-package org.conscrypt.ct;
-
-import org.conscrypt.Internal;
-
-@Internal
-public final class KnownLogs {
- public static final int LOG_COUNT = 8;
- public static final String[] LOG_DESCRIPTIONS = new String[] {
- "Google 'Pilot' log",
- "Google 'Aviator' log",
- "DigiCert Log Server",
- "Google 'Rocketeer' log",
- "Certly.IO log",
- "Izenpe log",
- "Symantec log",
- "Venafi log",
- };
- public static final String[] LOG_URLS = new String[] {
- "ct.googleapis.com/pilot",
- "ct.googleapis.com/aviator",
- "ct1.digicert-ct.com/log",
- "ct.googleapis.com/rocketeer",
- "log.certly.io",
- "ct.izenpe.com",
- "ct.ws.symantec.com",
- "ctlog.api.venafi.com",
- };
- public static final byte[][] LOG_KEYS = new byte[][] {
- // Google 'Pilot' log
- new byte[] {
- 48, 89, 48, 19, 6, 7, 42, -122, 72, -50, 61, 2, 1, 6, 8, 42, -122, 72,
- -50, 61, 3, 1, 7, 3, 66, 0, 4, 125, -88, 75, 18, 41, -128, -93, 61, -83,
- -45, 90, 119, -72, -52, -30, -120, -77, -91, -3, -15, -45, 12, -51, 24,
- 12, -24, 65, 70, -24, -127, 1, 27, 21, -31, 75, -15, 27, 98, -35, 54, 10,
- 8, 24, -70, -19, 11, 53, -124, -48, -98, 64, 60, 45, -98, -101, -126,
- 101, -67, 31, 4, 16, 65, 76, -96
- },
- // Google 'Aviator' log
- new byte[] {
- 48, 89, 48, 19, 6, 7, 42, -122, 72, -50, 61, 2, 1, 6, 8, 42, -122, 72,
- -50, 61, 3, 1, 7, 3, 66, 0, 4, -41, -12, -52, 105, -78, -28, 14, -112,
- -93, -118, -22, 90, 112, 9, 79, -17, 19, 98, -48, -115, 73, 96, -1, 27,
- 64, 80, 7, 12, 109, 113, -122, -38, 37, 73, -115, 101, -31, 8, 13, 71,
- 52, 107, -67, 39, -68, -106, 33, 62, 52, -11, -121, 118, 49, -79, 127,
- 29, -55, -123, 59, 13, -9, 31, 63, -23
- },
- // DigiCert Log Server
- new byte[] {
- 48, 89, 48, 19, 6, 7, 42, -122, 72, -50, 61, 2, 1, 6, 8, 42, -122, 72,
- -50, 61, 3, 1, 7, 3, 66, 0, 4, 2, 70, -59, -66, 27, -69, -126, 64, 22,
- -24, -63, -46, -84, 25, 105, 19, 89, -8, -8, 112, -123, 70, 64, -71, 56,
- -80, 35, -126, -88, 100, 76, 127, -65, -69, 52, -97, 74, 95, 40, -118,
- -49, 25, -60, 0, -10, 54, 6, -109, 101, -19, 76, -11, -87, 33, 98, 90,
- -40, -111, -21, 56, 36, 64, -84, -24
- },
- // Google 'Rocketeer' log
- new byte[] {
- 48, 89, 48, 19, 6, 7, 42, -122, 72, -50, 61, 2, 1, 6, 8, 42, -122, 72,
- -50, 61, 3, 1, 7, 3, 66, 0, 4, 32, 91, 24, -56, 60, -63, -117, -77, 49,
- 8, 0, -65, -96, -112, 87, 43, -73, 71, -116, 111, -75, 104, -80, -114,
- -112, 120, -23, -96, 115, -22, 79, 40, 33, 46, -100, -64, -12, 22, 27,
- -86, -7, -43, -41, -87, -128, -61, 78, 47, 82, 60, -104, 1, 37, 70, 36,
- 37, 40, 35, 119, 45, 5, -62, 64, 122
- },
- // Certly.IO log
- new byte[] {
- 48, 89, 48, 19, 6, 7, 42, -122, 72, -50, 61, 2, 1, 6, 8, 42, -122, 72,
- -50, 61, 3, 1, 7, 3, 66, 0, 4, 11, 35, -53, -123, 98, -104, 97, 72, 4,
- 115, -21, 84, 93, -13, -48, 7, -116, 45, 25, 45, -116, 54, -11, -21,
- -113, 1, 66, 10, 124, -104, 38, 39, -63, -75, -35, -110, -109, -80, -82,
- -8, -101, 61, 12, -40, 76, 78, 29, -7, 21, -5, 71, 104, 123, -70, 102,
- -73, 37, -100, -48, 74, -62, 102, -37, 72
- },
- // Izenpe log
- new byte[] {
- 48, 89, 48, 19, 6, 7, 42, -122, 72, -50, 61, 2, 1, 6, 8, 42, -122, 72,
- -50, 61, 3, 1, 7, 3, 66, 0, 4, 39, 100, 57, 12, 45, -36, 80, 24, -8, 33,
- 0, -94, 14, -19, 44, -22, 62, 117, -70, -97, -109, 100, 9, 0, 17, -60,
- 17, 23, -85, 92, -49, 15, 116, -84, -75, -105, -112, -109, 0, 91, -72,
- -21, -9, 39, 61, -39, -78, 10, -127, 95, 47, 13, 117, 56, -108, 55, -103,
- 30, -10, 7, 118, -32, -18, -66
- },
- // Symantec log
- new byte[] {
- 48, 89, 48, 19, 6, 7, 42, -122, 72, -50, 61, 2, 1, 6, 8, 42, -122, 72,
- -50, 61, 3, 1, 7, 3, 66, 0, 4, -106, -22, -84, 28, 70, 12, 27, 85, -36,
- 13, -4, -75, -108, 39, 70, 87, 66, 112, 58, 105, 24, -30, -65, 59, -60,
- -37, -85, -96, -12, -74, 108, -64, 83, 63, 77, 66, 16, 51, -16, 88, -105,
- -113, 107, -66, 114, -12, 42, -20, 28, 66, -86, 3, 47, 26, 126, 40, 53,
- 118, -103, 8, 61, 33, 20, -122
- },
- // Venafi log
- new byte[] {
- 48, -126, 1, 34, 48, 13, 6, 9, 42, -122, 72, -122, -9, 13, 1, 1, 1, 5, 0,
- 3, -126, 1, 15, 0, 48, -126, 1, 10, 2, -126, 1, 1, 0, -94, 90, 72, 31,
- 23, 82, -107, 53, -53, -93, 91, 58, 31, 83, -126, 118, -108, -93, -1,
- -128, -14, 28, 55, 60, -64, -79, -67, -63, 89, -117, -85, 45, 101, -109,
- -41, -13, -32, 4, -43, -102, 111, -65, -42, 35, 118, 54, 79, 35, -103,
- -53, 84, 40, -83, -116, 21, 75, 101, 89, 118, 65, 74, -100, -90, -9, -77,
- 59, 126, -79, -91, 73, -92, 23, 81, 108, -128, -36, 42, -112, 80, 75,
- -120, 36, -23, -91, 18, 50, -109, 4, 72, -112, 2, -6, 95, 14, 48, -121,
- -114, 85, 118, 5, -18, 42, 76, -50, -93, 106, 105, 9, 110, 37, -83, -126,
- 118, 15, -124, -110, -6, 56, -42, -122, 78, 36, -113, -101, -80, 114,
- -53, -98, -30, 107, 63, -31, 109, -55, 37, 117, 35, -120, -95, 24, 88, 6,
- 35, 51, 120, -38, 0, -48, 56, -111, 103, -46, -90, 125, 39, -105, 103,
- 90, -63, -13, 47, 23, -26, -22, -46, 91, -24, -127, -51, -3, -110, 104,
- -25, -13, 6, -16, -23, 114, -124, -18, 1, -91, -79, -40, 51, -38, -50,
- -125, -91, -37, -57, -49, -42, 22, 126, -112, 117, 24, -65, 22, -36, 50,
- 59, 109, -115, -85, -126, 23, 31, -119, 32, -115, 29, -102, -26, 77, 35,
- 8, -33, 120, 111, -58, 5, -65, 95, -82, -108, -105, -37, 95, 100, -44,
- -18, 22, -117, -93, -124, 108, 113, 43, -15, -85, 127, 93, 13, 50, -18,
- 4, -30, -112, -20, 65, -97, -5, 57, -63, 2, 3, 1, 0, 1
- },
- };
-}
diff --git a/platform/src/main/java/org/conscrypt/ct/LogStoreImpl.java b/platform/src/main/java/org/conscrypt/ct/LogStoreImpl.java
new file mode 100644
index 0000000..b7141d4
--- /dev/null
+++ b/platform/src/main/java/org/conscrypt/ct/LogStoreImpl.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2015 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 java.nio.charset.StandardCharsets.US_ASCII;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import org.conscrypt.ByteArray;
+import org.conscrypt.Internal;
+import org.conscrypt.OpenSSLKey;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+@Internal
+public class LogStoreImpl implements LogStore {
+ private static final Logger logger = Logger.getLogger(LogStoreImpl.class.getName());
+ public static final String V3_PATH = "/misc/keychain/ct/v3/log_list.json";
+ private static final Path defaultLogList;
+
+ static {
+ String ANDROID_DATA = System.getenv("ANDROID_DATA");
+ defaultLogList = Paths.get(ANDROID_DATA, V3_PATH);
+ }
+
+ private final Path logList;
+ private State state;
+ private Policy policy;
+ private String version;
+ private long timestamp;
+ private Map<ByteArray, LogInfo> logs;
+
+ public LogStoreImpl() {
+ this(defaultLogList);
+ }
+
+ public LogStoreImpl(Path logList) {
+ this.state = State.UNINITIALIZED;
+ this.logList = logList;
+ }
+
+ @Override
+ public State getState() {
+ ensureLogListIsLoaded();
+ return state;
+ }
+
+ @Override
+ public long getTimestamp() {
+ return timestamp;
+ }
+
+ @Override
+ public void setPolicy(Policy policy) {
+ this.policy = policy;
+ }
+
+ @Override
+ public LogInfo getKnownLog(byte[] logId) {
+ if (logId == null) {
+ return null;
+ }
+ if (!ensureLogListIsLoaded()) {
+ return null;
+ }
+ ByteArray buf = new ByteArray(logId);
+ LogInfo log = logs.get(buf);
+ if (log != null) {
+ return log;
+ }
+ return null;
+ }
+
+ /* Ensures the log list is loaded.
+ * Returns true if the log list is usable.
+ */
+ private boolean ensureLogListIsLoaded() {
+ synchronized (this) {
+ if (state == State.UNINITIALIZED) {
+ state = loadLogList();
+ }
+ if (state == State.LOADED && policy != null) {
+ state = policy.isLogStoreCompliant(this) ? State.COMPLIANT : State.NON_COMPLIANT;
+ }
+ return state == State.COMPLIANT;
+ }
+ }
+
+ private State loadLogList() {
+ byte[] content;
+ try {
+ content = Files.readAllBytes(logList);
+ } catch (IOException e) {
+ return State.NOT_FOUND;
+ }
+ if (content == null) {
+ return State.NOT_FOUND;
+ }
+ JSONObject json;
+ try {
+ json = new JSONObject(new String(content, UTF_8));
+ } catch (JSONException e) {
+ logger.log(Level.WARNING, "Unable to parse log list", e);
+ return State.MALFORMED;
+ }
+ HashMap<ByteArray, LogInfo> logsMap = new HashMap<>();
+ try {
+ version = json.getString("version");
+ timestamp = parseTimestamp(json.getString("log_list_timestamp"));
+ JSONArray operators = json.getJSONArray("operators");
+ for (int i = 0; i < operators.length(); i++) {
+ JSONObject operator = operators.getJSONObject(i);
+ String operatorName = operator.getString("name");
+ JSONArray logs = operator.getJSONArray("logs");
+ for (int j = 0; j < logs.length(); j++) {
+ JSONObject log = logs.getJSONObject(j);
+
+ LogInfo.Builder builder =
+ new LogInfo.Builder()
+ .setDescription(log.getString("description"))
+ .setPublicKey(parsePubKey(log.getString("key")))
+ .setUrl(log.getString("url"))
+ .setOperator(operatorName);
+
+ JSONObject stateObject = log.optJSONObject("state");
+ if (stateObject != null) {
+ String state = stateObject.keys().next();
+ String stateTimestamp =
+ stateObject.getJSONObject(state).getString("timestamp");
+ builder.setState(parseState(state), parseTimestamp(stateTimestamp));
+ }
+
+ LogInfo logInfo = builder.build();
+ byte[] logId = Base64.getDecoder().decode(log.getString("log_id"));
+
+ // The logId computed using the public key should match the log_id field.
+ if (!Arrays.equals(logInfo.getID(), logId)) {
+ throw new IllegalArgumentException("logId does not match publicKey");
+ }
+
+ logsMap.put(new ByteArray(logId), logInfo);
+ }
+ }
+ } catch (JSONException | IllegalArgumentException e) {
+ logger.log(Level.WARNING, "Unable to parse log list", e);
+ return State.MALFORMED;
+ }
+ this.logs = Collections.unmodifiableMap(logsMap);
+ return State.LOADED;
+ }
+
+ private static int parseState(String state) {
+ switch (state) {
+ case "pending":
+ return LogInfo.STATE_PENDING;
+ case "qualified":
+ return LogInfo.STATE_QUALIFIED;
+ case "usable":
+ return LogInfo.STATE_USABLE;
+ case "readonly":
+ return LogInfo.STATE_READONLY;
+ case "retired":
+ return LogInfo.STATE_RETIRED;
+ case "rejected":
+ return LogInfo.STATE_REJECTED;
+ default:
+ throw new IllegalArgumentException("Unknown log state: " + state);
+ }
+ }
+
+ // ISO 8601
+ private static DateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
+
+ @SuppressWarnings("JavaUtilDate")
+ private static long parseTimestamp(String timestamp) {
+ try {
+ Date date = dateFormatter.parse(timestamp);
+ return date.getTime();
+ } catch (ParseException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ private static PublicKey parsePubKey(String key) {
+ byte[] pem = ("-----BEGIN PUBLIC KEY-----\n" + key + "\n-----END PUBLIC KEY-----")
+ .getBytes(US_ASCII);
+ PublicKey pubkey;
+ try {
+ pubkey = OpenSSLKey.fromPublicKeyPemInputStream(new ByteArrayInputStream(pem))
+ .getPublicKey();
+ } catch (InvalidKeyException | NoSuchAlgorithmException e) {
+ throw new IllegalArgumentException(e);
+ }
+ return pubkey;
+ }
+}
diff --git a/platform/src/main/java/org/conscrypt/ct/PolicyImpl.java b/platform/src/main/java/org/conscrypt/ct/PolicyImpl.java
new file mode 100644
index 0000000..8bcd463
--- /dev/null
+++ b/platform/src/main/java/org/conscrypt/ct/PolicyImpl.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2015 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 org.conscrypt.Internal;
+
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+@Internal
+public class PolicyImpl implements Policy {
+ @Override
+ public boolean isLogStoreCompliant(LogStore store) {
+ long now = System.currentTimeMillis();
+ return isLogStoreCompliantAt(store, now);
+ }
+
+ public boolean isLogStoreCompliantAt(LogStore store, long atTime) {
+ long storeTimestamp = store.getTimestamp();
+ long seventyDaysInMs = 70L * 24 * 60 * 60 * 1000;
+ if (storeTimestamp + seventyDaysInMs < atTime) {
+ // Expired log list.
+ return false;
+ } else if (storeTimestamp > atTime) {
+ // Log list from the future. It is likely that the device has an
+ // incorrect time.
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public PolicyCompliance doesResultConformToPolicy(
+ VerificationResult result, X509Certificate leaf) {
+ long now = System.currentTimeMillis();
+ return doesResultConformToPolicyAt(result, leaf, now);
+ }
+
+ public PolicyCompliance doesResultConformToPolicyAt(
+ VerificationResult result, X509Certificate leaf, long atTime) {
+ List<VerifiedSCT> validSCTs = new ArrayList<VerifiedSCT>(result.getValidSCTs());
+ /* While the log list supports logs without a state, these entries are
+ * not supported by the log policy. Filter them out. */
+ filterOutUnknown(validSCTs);
+ /* Filter out any SCT issued after a log was retired */
+ filterOutAfterRetired(validSCTs);
+
+ Set<VerifiedSCT> embeddedValidSCTs = new HashSet<>();
+ Set<VerifiedSCT> ocspOrTLSValidSCTs = new HashSet<>();
+ for (VerifiedSCT vsct : validSCTs) {
+ if (vsct.getSct().getOrigin() == SignedCertificateTimestamp.Origin.EMBEDDED) {
+ embeddedValidSCTs.add(vsct);
+ } else {
+ ocspOrTLSValidSCTs.add(vsct);
+ }
+ }
+ if (embeddedValidSCTs.size() > 0) {
+ return conformEmbeddedSCTs(embeddedValidSCTs, leaf, atTime);
+ }
+ return PolicyCompliance.NOT_ENOUGH_SCTS;
+ }
+
+ private void filterOutUnknown(List<VerifiedSCT> scts) {
+ Iterator<VerifiedSCT> it = scts.iterator();
+ while (it.hasNext()) {
+ VerifiedSCT vsct = it.next();
+ if (vsct.getLogInfo().getState() == LogInfo.STATE_UNKNOWN) {
+ it.remove();
+ }
+ }
+ }
+
+ private void filterOutAfterRetired(List<VerifiedSCT> scts) {
+ /* From the policy:
+ *
+ * In order to contribute to a certificate’s CT Compliance, an SCT must
+ * have been issued before the Log’s Retired timestamp, if one exists.
+ * Chrome uses the earliest SCT among all SCTs presented to evaluate CT
+ * compliance against CT Log Retired timestamps. This accounts for edge
+ * cases in which a CT Log becomes Retired during the process of
+ * submitting certificate logging requests.
+ */
+
+ if (scts.size() < 1) {
+ return;
+ }
+ long minTimestamp = scts.get(0).getSct().getTimestamp();
+ for (VerifiedSCT vsct : scts) {
+ long ts = vsct.getSct().getTimestamp();
+ if (ts < minTimestamp) {
+ minTimestamp = ts;
+ }
+ }
+ Iterator<VerifiedSCT> it = scts.iterator();
+ while (it.hasNext()) {
+ VerifiedSCT vsct = it.next();
+ if (vsct.getLogInfo().getState() == LogInfo.STATE_RETIRED
+ && minTimestamp > vsct.getLogInfo().getStateTimestamp()) {
+ it.remove();
+ }
+ }
+ }
+
+ private PolicyCompliance conformEmbeddedSCTs(
+ Set<VerifiedSCT> embeddedValidSCTs, X509Certificate leaf, long atTime) {
+ /* 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.getStateAt(atTime)) {
+ case LogInfo.STATE_QUALIFIED:
+ case LogInfo.STATE_USABLE:
+ case LogInfo.STATE_READONLY:
+ found = true;
+ }
+ }
+ if (!found) {
+ return PolicyCompliance.NOT_ENOUGH_SCTS;
+ }
+
+ /* 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.getStateAt(atTime)) {
+ case LogInfo.STATE_QUALIFIED:
+ case LogInfo.STATE_USABLE:
+ case LogInfo.STATE_READONLY:
+ case LogInfo.STATE_RETIRED:
+ validLogs.add(log);
+ }
+ }
+ if (validLogs.size() < numberSCTsRequired) {
+ return PolicyCompliance.NOT_ENOUGH_SCTS;
+ }
+
+ /* 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 PolicyCompliance.NOT_ENOUGH_DIVERSE_SCTS;
+ }
+
+ return PolicyCompliance.COMPLY;
+ }
+}
diff --git a/platform/src/test/java/org/conscrypt/CertBlocklistTest.java b/platform/src/test/java/org/conscrypt/CertBlocklistTest.java
index 39561e5..4d9a5c1 100644
--- a/platform/src/test/java/org/conscrypt/CertBlocklistTest.java
+++ b/platform/src/test/java/org/conscrypt/CertBlocklistTest.java
@@ -29,6 +29,7 @@
public class CertBlocklistTest extends TestCase {
private static final String BLOCKLIST_CA = "test_blocklist_ca.pem";
+ private static final String BLOCKLIST_CA2 = "test_blocklist_ca2.pem";
private static final String BLOCKLISTED_CHAIN = "blocklist_test_chain.pem";
private static final String BLOCKLIST_FALLBACK_VALID_CA = "blocklist_test_valid_ca.pem";
private static final String BLOCKLISTED_VALID_CHAIN = "blocklist_test_valid_chain.pem";
@@ -43,6 +44,15 @@
}
/**
+ * Ensure that the test blocklisted CA 2 is actually blocklisted by default.
+ */
+ public void testBlocklistedPublicKeySHA256() throws Exception {
+ X509Certificate blocklistedCa = loadCertificate(BLOCKLIST_CA2);
+ CertBlocklist blocklist = CertBlocklistImpl.getDefault();
+ assertTrue(blocklist.isPublicKeyBlockListed(blocklistedCa.getPublicKey()));
+ }
+
+ /**
* Check that the blocklisted CA is rejected even if it used as a root of trust
*/
public void testBlocklistedCaUntrusted() throws Exception {
diff --git a/platform/src/test/java/org/conscrypt/ct/CTLogStoreImplTest.java b/platform/src/test/java/org/conscrypt/ct/CTLogStoreImplTest.java
deleted file mode 100644
index f95a3e6..0000000
--- a/platform/src/test/java/org/conscrypt/ct/CTLogStoreImplTest.java
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Copyright (C) 2015 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 java.nio.charset.StandardCharsets.UTF_8;
-
-import java.io.BufferedWriter;
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.nio.charset.StandardCharsets;
-import java.security.PublicKey;
-import junit.framework.TestCase;
-import org.conscrypt.InternalUtil;
-
-public class CTLogStoreImplTest extends TestCase {
- private static final String[] LOG_KEYS = new String[] {
- "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEmXg8sUUzwBYaWrRb+V0IopzQ6o3U" +
- "yEJ04r5ZrRXGdpYM8K+hB0pXrGRLI0eeWz+3skXrS0IO83AhA3GpRL6s6w==",
-
- "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAErEULmlBnX9L/+AK20hLYzPMFozYx" +
- "pP0Wm1ylqGkPEwuDKn9DSpNSOym49SN77BLGuAXu9twOW/qT+ddIYVBEIw==",
-
- "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEP6PGcXmjlyCBz2ZFUuUjrgbZLaEF" +
- "gfLUkt2cEqlSbb4vTuB6WWmgC9h0L6PN6JF0CPcajpBKGlTI15242a8d4g==",
-
- "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAER3qB0NADsP1szXxe4EagrD/ryPVh" +
- "Y/azWbKyXcK12zhXnO8WH2U4QROVUMctFXLflIzw0EivdRN9t7UH1Od30w==",
-
- "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEY0ww9JqeJvzVtKNTPVb3JZa7s0ZV" +
- "duH3PpshpMS5XVoPRSjSQCph6f3HjUcM3c4N2hpa8OFbrFFy37ttUrgD+A=="
- };
- private static final String[] LOG_FILENAMES = new String[] {
- "df1c2ec11500945247a96168325ddc5c7959e8f7c6d388fc002e0bbd3f74d764",
- "84f8ae3f613b13407a75fa2893b93ab03b18d86c455fe7c241ae020033216446",
- "89baa01a445100009d8f9a238947115b30702275aafee675a7d94b6b09287619",
- "57456bffe268e49a190dce4318456034c2b4958f3c0201bed5a366737d1e74ca",
- "896c898ced4b8e6547fa351266caae4ca304f1c1ec2b623c2ee259c5452147b0"
- };
-
- private static final CTLogInfo[] LOGS;
- private static final String[] LOGS_SERIALIZED;
-
- static {
- try {
- int logCount = LOG_KEYS.length;
- LOGS = new CTLogInfo[logCount];
- LOGS_SERIALIZED = new String[logCount];
- for (int i = 0; i < logCount; i++) {
- PublicKey key = InternalUtil.readPublicKeyPem(new ByteArrayInputStream(
- ("-----BEGIN PUBLIC KEY-----\n" +
- LOG_KEYS[i] + "\n" +
- "-----END PUBLIC KEY-----\n").getBytes(StandardCharsets.US_ASCII)));
- String description = String.format("Test Log %d", i);
- String url = String.format("log%d.example.com", i);
- LOGS[i] = new CTLogInfo(key, description, url);
- LOGS_SERIALIZED[i] = String.format("description:%s\nurl:%s\nkey:%s",
- description, url, LOG_KEYS[i]);
- }
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- /* CTLogStoreImpl loads the list of logs lazily when they are first needed
- * to avoid any overhead when CT is disabled.
- * This test simply forces the logs to be loaded to make sure it doesn't
- * fail, as all of the other tests use a different log store.
- */
- public void test_getDefaultFallbackLogs() {
- CTLogInfo[] knownLogs = CTLogStoreImpl.getDefaultFallbackLogs();
- assertEquals(KnownLogs.LOG_COUNT, knownLogs.length);
- }
-
- public void test_loadLog() throws Exception {
- CTLogInfo log = CTLogStoreImpl.loadLog(
- new ByteArrayInputStream(LOGS_SERIALIZED[0].getBytes(StandardCharsets.US_ASCII)));
- assertEquals(LOGS[0], log);
-
- File testFile = writeFile(LOGS_SERIALIZED[0]);
- log = CTLogStoreImpl.loadLog(testFile);
- assertEquals(LOGS[0], log);
-
- // Empty log file, used to mask fallback logs
- assertEquals(null, CTLogStoreImpl.loadLog(new ByteArrayInputStream(new byte[0])));
- try {
- CTLogStoreImpl.loadLog(new ByteArrayInputStream(
- "randomgarbage".getBytes(StandardCharsets.US_ASCII)));
- fail("InvalidLogFileException not thrown");
- } catch (CTLogStoreImpl.InvalidLogFileException e) {}
-
- try {
- CTLogStoreImpl.loadLog(new File("/nonexistent"));
- fail("FileNotFoundException not thrown");
- } catch (FileNotFoundException e) {}
- }
-
- public void test_getKnownLog() throws Exception {
- File userDir = createTempDirectory();
- userDir.deleteOnExit();
-
- File systemDir = createTempDirectory();
- systemDir.deleteOnExit();
-
- CTLogInfo[] fallback = new CTLogInfo[] { LOGS[2], LOGS[3] };
-
- CTLogStore store = new CTLogStoreImpl(userDir, systemDir, fallback);
-
- /* Add logs 0 and 1 to the user and system directories respectively
- * Log 2 & 3 are part of the fallbacks
- * But mask log 3 with an empty file in the user directory.
- * Log 4 is not in the store
- */
- File log0File = new File(userDir, LOG_FILENAMES[0]);
- File log1File = new File(systemDir, LOG_FILENAMES[1]);
- File log3File = new File(userDir, LOG_FILENAMES[3]);
- File log4File = new File(userDir, LOG_FILENAMES[4]);
-
- writeFile(log0File, LOGS_SERIALIZED[0]);
- writeFile(log1File, LOGS_SERIALIZED[1]);
- writeFile(log3File, "");
-
- // Logs 01 are present, log 2 is in the fallback and unused, log 3 is present but masked,
- // log 4 is missing
- assertEquals(LOGS[0], store.getKnownLog(LOGS[0].getID()));
- assertEquals(LOGS[1], store.getKnownLog(LOGS[1].getID()));
- // Fallback logs are not used if the userDir is present.
- assertEquals(null, store.getKnownLog(LOGS[2].getID()));
- assertEquals(null, store.getKnownLog(LOGS[3].getID()));
- assertEquals(null, store.getKnownLog(LOGS[4].getID()));
-
- /* Test whether CTLogStoreImpl caches properly
- * Modify the files on the disk, the result of the store should not change
- * Delete log 0, mask log 1, add log 4
- */
- log0File.delete();
- writeFile(log1File, "");
- writeFile(log4File, LOGS_SERIALIZED[4]);
-
- assertEquals(LOGS[0], store.getKnownLog(LOGS[0].getID()));
- assertEquals(LOGS[1], store.getKnownLog(LOGS[1].getID()));
- assertEquals(null, store.getKnownLog(LOGS[4].getID()));
-
- // Test that fallback logs are used when the userDir doesn't exist.
- File doesntExist = new File("/doesnt/exist/");
- store = new CTLogStoreImpl(doesntExist, doesntExist, fallback);
- assertEquals(LOGS[2], store.getKnownLog(LOGS[2].getID()));
- assertEquals(LOGS[3], store.getKnownLog(LOGS[3].getID()));
- }
-
- /**
- * Create a temporary file and write to it.
- * The file will be deleted on exit.
- * @param contents The data to be written to the file
- * @return A reference to the temporary file
- */
- private File writeFile(String contents) throws IOException {
- File file = File.createTempFile("test", null);
- file.deleteOnExit();
- writeFile(file, contents);
- return file;
- }
-
- private static void writeFile(File file, String contents) throws FileNotFoundException {
- PrintWriter writer = new PrintWriter(
- new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), UTF_8)),
- false);
- try {
- writer.write(contents);
- } finally {
- writer.close();
- }
- }
-
- /*
- * This is NOT safe, as another process could create a file between delete() and mkdir()
- * It should be fine for tests though
- */
- private static File createTempDirectory() throws IOException {
- File folder = File.createTempFile("test", "");
- folder.delete();
- folder.mkdir();
- return folder;
- }
-}
-
diff --git a/platform/src/test/java/org/conscrypt/ct/LogStoreImplTest.java b/platform/src/test/java/org/conscrypt/ct/LogStoreImplTest.java
new file mode 100644
index 0000000..81b6cd6
--- /dev/null
+++ b/platform/src/test/java/org/conscrypt/ct/LogStoreImplTest.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2015 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 java.nio.charset.StandardCharsets.US_ASCII;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.security.PublicKey;
+import java.util.Base64;
+import junit.framework.TestCase;
+import org.conscrypt.OpenSSLKey;
+
+public class LogStoreImplTest extends TestCase {
+ public void test_loadLogList() throws Exception {
+ // clang-format off
+ String content = "" +
+"{" +
+" \"version\": \"1.1\"," +
+" \"log_list_timestamp\": \"2024-01-01T11:55:12Z\"," +
+" \"operators\": [" +
+" {" +
+" \"name\": \"Operator 1\"," +
+" \"email\": [\"ct@operator1.com\"]," +
+" \"logs\": [" +
+" {" +
+" \"description\": \"Operator 1 'Test2024' log\"," +
+" \"log_id\": \"7s3QZNXbGs7FXLedtM0TojKHRny87N7DUUhZRnEftZs=\"," +
+" \"key\": \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHblsqctplMVc5ramA7vSuNxUQxcomQwGAVAdnWTAWUYr3MgDHQW0LagJ95lB7QT75Ve6JgT2EVLOFGU7L3YrwA==\"," +
+" \"url\": \"https://operator1.example.com/logs/test2024/\"," +
+" \"mmd\": 86400," +
+" \"state\": {" +
+" \"usable\": {" +
+" \"timestamp\": \"2022-11-01T18:54:00Z\"" +
+" }" +
+" }," +
+" \"temporal_interval\": {" +
+" \"start_inclusive\": \"2024-01-01T00:00:00Z\"," +
+" \"end_exclusive\": \"2025-01-01T00:00:00Z\"" +
+" }" +
+" }," +
+" {" +
+" \"description\": \"Operator 1 'Test2025' log\"," +
+" \"log_id\": \"TnWjJ1yaEMM4W2zU3z9S6x3w4I4bjWnAsfpksWKaOd8=\"," +
+" \"key\": \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIIKh+WdoqOTblJji4WiH5AltIDUzODyvFKrXCBjw/Rab0/98J4LUh7dOJEY7+66+yCNSICuqRAX+VPnV8R1Fmg==\"," +
+" \"url\": \"https://operator1.example.com/logs/test2025/\"," +
+" \"mmd\": 86400," +
+" \"state\": {" +
+" \"usable\": {" +
+" \"timestamp\": \"2023-11-26T12:00:00Z\"" +
+" }" +
+" }," +
+" \"temporal_interval\": {" +
+" \"start_inclusive\": \"2025-01-01T00:00:00Z\"," +
+" \"end_exclusive\": \"2025-07-01T00:00:00Z\"" +
+" }" +
+" }" +
+" ]" +
+" }," +
+" {" +
+" \"name\": \"Operator 2\"," +
+" \"email\": [\"ct@operator2.com\"]," +
+" \"logs\": [" +
+" {" +
+" \"description\": \"Operator 2 'Test2024' Log\"," +
+" \"log_id\": \"2ra/az+1tiKfm8K7XGvocJFxbLtRhIU0vaQ9MEjX+6s=\"," +
+" \"key\": \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEd7Gbe4/mizX+OpIpLayKjVGKJfyTttegiyk3cR0zyswz6ii5H+Ksw6ld3Ze+9p6UJd02gdHrXSnDK0TxW8oVSA==\"," +
+" \"url\": \"https://operator2.example.com/logs/test2024/\"," +
+" \"mmd\": 86400," +
+" \"state\": {" +
+" \"usable\": {" +
+" \"timestamp\": \"2022-11-30T17:00:00Z\"" +
+" }" +
+" }," +
+" \"temporal_interval\": {" +
+" \"start_inclusive\": \"2024-01-01T00:00:00Z\"," +
+" \"end_exclusive\": \"2025-01-01T00:00:00Z\"" +
+" }" +
+" }" +
+" ]" +
+" }" +
+" ]" +
+"}";
+ // clang-format on
+
+ File logList = writeFile(content);
+ LogStore store = new LogStoreImpl(logList.toPath());
+ store.setPolicy(new PolicyImpl() {
+ @Override
+ public boolean isLogStoreCompliant(LogStore store) {
+ return true;
+ }
+ });
+
+ assertNull("A null logId should return null", store.getKnownLog(null));
+
+ byte[] pem = ("-----BEGIN PUBLIC KEY-----\n"
+ + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHblsqctplMVc5ramA7vSuNxUQxcomQwGAVAdnWTAWUYr"
+ + "3MgDHQW0LagJ95lB7QT75Ve6JgT2EVLOFGU7L3YrwA=="
+ + "\n-----END PUBLIC KEY-----\n")
+ .getBytes(US_ASCII);
+ ByteArrayInputStream is = new ByteArrayInputStream(pem);
+
+ LogInfo log1 =
+ new LogInfo.Builder()
+ .setPublicKey(OpenSSLKey.fromPublicKeyPemInputStream(is).getPublicKey())
+ .setDescription("Operator 1 'Test2024' log")
+ .setUrl("https://operator1.example.com/logs/test2024/")
+ .setState(LogInfo.STATE_USABLE, 1667328840000L)
+ .setOperator("Operator 1")
+ .build();
+ byte[] log1Id = Base64.getDecoder().decode("7s3QZNXbGs7FXLedtM0TojKHRny87N7DUUhZRnEftZs=");
+ assertEquals("An existing logId should be returned", log1, store.getKnownLog(log1Id));
+ }
+
+ private File writeFile(String content) throws IOException {
+ File file = File.createTempFile("test", null);
+ file.deleteOnExit();
+ try (FileWriter fw = new FileWriter(file)) {
+ fw.write(content);
+ }
+ return file;
+ }
+}
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..af1d464
--- /dev/null
+++ b/platform/src/test/java/org/conscrypt/ct/PolicyImplTest.java
@@ -0,0 +1,314 @@
+/*
+ * 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.assertEquals;
+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 final String OPERATOR1 = "operator 1";
+ private static final String OPERATOR2 = "operator 2";
+ private static LogInfo usableOp1Log1;
+ private static LogInfo usableOp1Log2;
+ private static LogInfo retiredOp1LogOld;
+ private static LogInfo retiredOp1LogNew;
+ private static LogInfo usableOp2Log;
+ private static LogInfo retiredOp2Log;
+ private static SignedCertificateTimestamp embeddedSCT;
+
+ /* Some test dates. By default:
+ * - The verification is occurring in January 2024;
+ * - The log list was created in December 2023;
+ * - The SCTs were generated in January 2023; and
+ * - The logs got into their state in January 2022.
+ * Other dates are used to exercise edge cases.
+ */
+ private static final long JAN2025 = 1735725600000L;
+ private static final long JAN2024 = 1704103200000L;
+ private static final long DEC2023 = 1701424800000L;
+ private static final long JUN2023 = 1672999200000L;
+ private static final long JAN2023 = 1672567200000L;
+ private static final long JAN2022 = 1641031200000L;
+
+ 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(OPERATOR1)
+ .setState(LogInfo.STATE_USABLE, JAN2022)
+ .build();
+ usableOp1Log2 = new LogInfo.Builder()
+ .setPublicKey(new FakePublicKey(new byte[] {0x02}))
+ .setUrl("")
+ .setOperator(OPERATOR1)
+ .setState(LogInfo.STATE_USABLE, JAN2022)
+ .build();
+ retiredOp1LogOld = new LogInfo.Builder()
+ .setPublicKey(new FakePublicKey(new byte[] {0x03}))
+ .setUrl("")
+ .setOperator(OPERATOR1)
+ .setState(LogInfo.STATE_RETIRED, JAN2022)
+ .build();
+ retiredOp1LogNew = new LogInfo.Builder()
+ .setPublicKey(new FakePublicKey(new byte[] {0x06}))
+ .setUrl("")
+ .setOperator(OPERATOR1)
+ .setState(LogInfo.STATE_RETIRED, JUN2023)
+ .build();
+ usableOp2Log = new LogInfo.Builder()
+ .setPublicKey(new FakePublicKey(new byte[] {0x04}))
+ .setUrl("")
+ .setOperator(OPERATOR2)
+ .setState(LogInfo.STATE_USABLE, JAN2022)
+ .build();
+ retiredOp2Log = new LogInfo.Builder()
+ .setPublicKey(new FakePublicKey(new byte[] {0x05}))
+ .setUrl("")
+ .setOperator(OPERATOR2)
+ .setState(LogInfo.STATE_RETIRED, JAN2022)
+ .build();
+ /* The origin of the SCT and its timestamp are 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,
+ JAN2023, null, null, SignedCertificateTimestamp.Origin.EMBEDDED);
+ }
+
+ @Test
+ public void emptyVerificationResult() throws Exception {
+ PolicyImpl p = new PolicyImpl();
+ VerificationResult result = new VerificationResult();
+
+ X509Certificate leaf = new FakeX509Certificate();
+ assertEquals("An empty VerificationResult", PolicyCompliance.NOT_ENOUGH_SCTS,
+ p.doesResultConformToPolicyAt(result, leaf, JAN2024));
+ }
+
+ @Test
+ public void validVerificationResult() throws Exception {
+ PolicyImpl 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();
+ assertEquals("Two valid SCTs from different operators", PolicyCompliance.COMPLY,
+ p.doesResultConformToPolicyAt(result, leaf, JAN2024));
+ }
+
+ @Test
+ public void validWithRetiredVerificationResult() throws Exception {
+ PolicyImpl p = new PolicyImpl();
+
+ VerifiedSCT vsct1 = new VerifiedSCT.Builder(embeddedSCT)
+ .setStatus(VerifiedSCT.Status.VALID)
+ .setLogInfo(retiredOp1LogNew)
+ .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();
+ assertEquals("One valid, one retired SCTs from different operators",
+ PolicyCompliance.COMPLY, p.doesResultConformToPolicyAt(result, leaf, JAN2024));
+ }
+
+ @Test
+ public void invalidWithRetiredVerificationResult() throws Exception {
+ PolicyImpl p = new PolicyImpl();
+
+ VerifiedSCT vsct1 = new VerifiedSCT.Builder(embeddedSCT)
+ .setStatus(VerifiedSCT.Status.VALID)
+ .setLogInfo(retiredOp1LogOld)
+ .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();
+ assertEquals("One valid, one retired (before SCT timestamp) SCTs from different operators",
+ PolicyCompliance.NOT_ENOUGH_SCTS,
+ p.doesResultConformToPolicyAt(result, leaf, JAN2024));
+ }
+
+ @Test
+ public void invalidOneSctVerificationResult() throws Exception {
+ PolicyImpl 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();
+ assertEquals("One valid SCT", PolicyCompliance.NOT_ENOUGH_SCTS,
+ p.doesResultConformToPolicyAt(result, leaf, JAN2024));
+ }
+
+ @Test
+ public void invalidTwoSctsVerificationResult() throws Exception {
+ PolicyImpl p = new PolicyImpl();
+
+ VerifiedSCT vsct1 = new VerifiedSCT.Builder(embeddedSCT)
+ .setStatus(VerifiedSCT.Status.VALID)
+ .setLogInfo(retiredOp1LogNew)
+ .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();
+ assertEquals("Two retired SCTs from different operators", PolicyCompliance.NOT_ENOUGH_SCTS,
+ p.doesResultConformToPolicyAt(result, leaf, JAN2024));
+ }
+
+ @Test
+ public void invalidTwoSctsSameOperatorVerificationResult() throws Exception {
+ PolicyImpl 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(usableOp1Log2)
+ .build();
+
+ VerificationResult result = new VerificationResult();
+ result.add(vsct1);
+ result.add(vsct2);
+
+ X509Certificate leaf = new FakeX509Certificate();
+ assertEquals("Two SCTs from the same operator", PolicyCompliance.NOT_ENOUGH_DIVERSE_SCTS,
+ p.doesResultConformToPolicyAt(result, leaf, JAN2024));
+ }
+
+ @Test
+ public void validRecentLogStore() throws Exception {
+ PolicyImpl p = new PolicyImpl();
+
+ LogStore store = new LogStoreImpl() {
+ @Override
+ public long getTimestamp() {
+ return DEC2023;
+ }
+ };
+ assertTrue("A recent log list is compliant", p.isLogStoreCompliantAt(store, JAN2024));
+ }
+
+ @Test
+ public void invalidFutureLogStore() throws Exception {
+ PolicyImpl p = new PolicyImpl();
+
+ LogStore store = new LogStoreImpl() {
+ @Override
+ public long getTimestamp() {
+ return JAN2025;
+ }
+ };
+ assertFalse("A future log list is non-compliant", p.isLogStoreCompliantAt(store, JAN2024));
+ }
+
+ @Test
+ public void invalidOldLogStore() throws Exception {
+ PolicyImpl p = new PolicyImpl();
+
+ LogStore store = new LogStoreImpl() {
+ @Override
+ public long getTimestamp() {
+ return JAN2023;
+ }
+ };
+ assertFalse("A expired log list is non-compliant", p.isLogStoreCompliantAt(store, JAN2024));
+ }
+}
diff --git a/testing/src/main/java/org/conscrypt/ChannelType.java b/testing/src/main/java/org/conscrypt/ChannelType.java
index 09dd582..23e09a0 100644
--- a/testing/src/main/java/org/conscrypt/ChannelType.java
+++ b/testing/src/main/java/org/conscrypt/ChannelType.java
@@ -38,24 +38,24 @@
public enum ChannelType {
NONE {
@Override
- SSLSocket newClientSocket(SSLSocketFactory factory, InetAddress address, int port)
+ public SSLSocket newClientSocket(SSLSocketFactory factory, InetAddress address, int port)
throws IOException {
return clientMode(factory.createSocket(address, port));
}
@Override
- ServerSocket newServerSocket(SSLServerSocketFactory factory) throws IOException {
+ public ServerSocket newServerSocket(SSLServerSocketFactory factory) throws IOException {
return factory.createServerSocket(0, 50, InetAddress.getLoopbackAddress());
}
@Override
- SSLSocket accept(ServerSocket socket, SSLSocketFactory unused) throws IOException {
+ public SSLSocket accept(ServerSocket socket, SSLSocketFactory unused) throws IOException {
return serverMode(socket.accept());
}
},
NO_CHANNEL {
@Override
- SSLSocket newClientSocket(SSLSocketFactory factory, InetAddress address, int port)
+ public SSLSocket newClientSocket(SSLSocketFactory factory, InetAddress address, int port)
throws IOException {
Socket wrapped = new Socket(address, port);
assertNull(wrapped.getChannel());
@@ -64,13 +64,13 @@
}
@Override
- ServerSocket newServerSocket(SSLServerSocketFactory unused) throws IOException {
+ public ServerSocket newServerSocket(SSLServerSocketFactory unused) throws IOException {
return ServerSocketFactory.getDefault().createServerSocket(
0, 50, InetAddress.getLoopbackAddress());
}
@Override
- SSLSocket accept(ServerSocket serverSocket, SSLSocketFactory factory) throws IOException {
+ public SSLSocket accept(ServerSocket serverSocket, SSLSocketFactory factory) throws IOException {
assertFalse(serverSocket instanceof SSLServerSocket);
Socket wrapped = serverSocket.accept();
assertNull(wrapped.getChannel());
@@ -81,21 +81,21 @@
},
CHANNEL {
@Override
- SSLSocket newClientSocket(SSLSocketFactory factory, InetAddress address, int port)
+ public SSLSocket newClientSocket(SSLSocketFactory factory, InetAddress address, int port)
throws IOException {
Socket wrapped = SocketChannel.open(new InetSocketAddress(address, port)).socket();
return clientMode(factory.createSocket(wrapped, address.getHostName(), port, true));
}
@Override
- ServerSocket newServerSocket(SSLServerSocketFactory unused) throws IOException {
+ public ServerSocket newServerSocket(SSLServerSocketFactory unused) throws IOException {
return ServerSocketChannel.open()
.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0))
.socket();
}
@Override
- SSLSocket accept(ServerSocket serverSocket, SSLSocketFactory factory) throws IOException {
+ public SSLSocket accept(ServerSocket serverSocket, SSLSocketFactory factory) throws IOException {
assertFalse(serverSocket instanceof SSLServerSocket);
ServerSocketChannel serverChannel = serverSocket.getChannel();
@@ -111,10 +111,10 @@
}
};
- abstract SSLSocket newClientSocket(SSLSocketFactory factory, InetAddress address, int port)
+ public abstract SSLSocket newClientSocket(SSLSocketFactory factory, InetAddress address, int port)
throws IOException;
- abstract ServerSocket newServerSocket(SSLServerSocketFactory factory) throws IOException;
- abstract SSLSocket accept(ServerSocket socket, SSLSocketFactory factory) throws IOException;
+ public abstract ServerSocket newServerSocket(SSLServerSocketFactory factory) throws IOException;
+ public abstract SSLSocket accept(ServerSocket socket, SSLSocketFactory factory) throws IOException;
private static SSLSocket clientMode(Socket socket) {
SSLSocket sslSocket = (SSLSocket) socket;
diff --git a/testing/src/main/java/org/conscrypt/TestUtils.java b/testing/src/main/java/org/conscrypt/TestUtils.java
index 503102d..08f21a3 100644
--- a/testing/src/main/java/org/conscrypt/TestUtils.java
+++ b/testing/src/main/java/org/conscrypt/TestUtils.java
@@ -27,6 +27,7 @@
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.ServerSocket;
@@ -355,7 +356,7 @@
}
}
- static SSLSocketFactory setUseEngineSocket(
+ public static SSLSocketFactory setUseEngineSocket(
SSLSocketFactory conscryptFactory, boolean useEngineSocket) {
try {
Class<?> clazz = conscryptClass("Conscrypt");
@@ -368,7 +369,7 @@
}
}
- static SSLServerSocketFactory setUseEngineSocket(
+ public static SSLServerSocketFactory setUseEngineSocket(
SSLServerSocketFactory conscryptFactory, boolean useEngineSocket) {
try {
Class<?> clazz = conscryptClass("Conscrypt");
@@ -513,12 +514,12 @@
return msg;
}
- static SSLContext newClientSslContext(Provider provider) {
+ public static SSLContext newClientSslContext(Provider provider) {
SSLContext context = newContext(provider);
return initClientSslContext(context);
}
- static SSLContext newServerSslContext(Provider provider) {
+ public static SSLContext newServerSslContext(Provider provider) {
SSLContext context = newContext(provider);
return initServerSslContext(context);
}
@@ -871,4 +872,33 @@
throw new RuntimeException(e);
}
}
+
+ // Find base method via reflection due to possible version skew on Android
+ // and visibility issues when building with Gradle.
+ public static boolean isTlsV1Filtered() {
+ try {
+ return (Boolean) conscryptClass("Platform")
+ .getDeclaredMethod("isTlsV1Filtered")
+ .invoke(null);
+ } catch (NoSuchMethodException e) {
+ return true;
+ } catch (ClassNotFoundException | IllegalAccessException | InvocationTargetException e) {
+ throw new IllegalStateException("Reflection failure", e);
+ }
+ }
+
+ // Find base method via reflection due to possible version skew on Android
+ // and visibility issues when building with Gradle.
+ public static boolean isTlsV1Supported() {
+ try {
+ return (Boolean) conscryptClass("Platform")
+ .getDeclaredMethod("isTlsV1Supported")
+ .invoke(null);
+ } catch (NoSuchMethodException e) {
+ return false;
+ } catch (ClassNotFoundException | IllegalAccessException | InvocationTargetException e) {
+ throw new IllegalStateException("Reflection failure", e);
+ }
+ }
+
}
diff --git a/testing/src/main/java/org/conscrypt/java/security/StandardNames.java b/testing/src/main/java/org/conscrypt/java/security/StandardNames.java
index 54a26d0..777ec6b 100644
--- a/testing/src/main/java/org/conscrypt/java/security/StandardNames.java
+++ b/testing/src/main/java/org/conscrypt/java/security/StandardNames.java
@@ -172,6 +172,10 @@
SSL_CONTEXT_PROTOCOLS_DEPRECATED.add("TLSv1");
SSL_CONTEXT_PROTOCOLS_DEPRECATED.add("TLSv1.1");
}
+ if (!TestUtils.isTlsV1Supported()) {
+ assertTrue("Can't have this without that", TestUtils.isTlsV1Deprecated());
+ SSL_CONTEXT_PROTOCOLS.removeAll(SSL_CONTEXT_PROTOCOLS_DEPRECATED);
+ }
}
public static final Set<String> KEY_TYPES = new HashSet<String>(
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;
+ }
+}