diff --git a/okhttp-tests/src/test/java/okhttp3/CertificateChainCleanerTest.java b/okhttp-tests/src/test/java/okhttp3/CertificateChainCleanerTest.java index 647ee1a327b6..a7a1747f6dcf 100644 --- a/okhttp-tests/src/test/java/okhttp3/CertificateChainCleanerTest.java +++ b/okhttp-tests/src/test/java/okhttp3/CertificateChainCleanerTest.java @@ -23,7 +23,6 @@ import javax.net.ssl.SSLPeerUnverifiedException; import okhttp3.internal.HeldCertificate; import okhttp3.internal.tls.CertificateChainCleaner; -import okhttp3.internal.tls.RealTrustRootIndex; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -34,19 +33,18 @@ public final class CertificateChainCleanerTest { HeldCertificate root = new HeldCertificate.Builder() .serialNumber("1") .build(); - CertificateChainCleaner council = new CertificateChainCleaner( - new RealTrustRootIndex(root.certificate)); - assertEquals(list(root), council.clean(list(root))); + CertificateChainCleaner cleaner = CertificateChainCleaner.get(root.certificate); + assertEquals(list(root), cleaner.clean(list(root))); } @Test public void normalizeUnknownSelfSignedCertificate() throws Exception { HeldCertificate root = new HeldCertificate.Builder() .serialNumber("1") .build(); - CertificateChainCleaner council = new CertificateChainCleaner(new RealTrustRootIndex()); + CertificateChainCleaner cleaner = CertificateChainCleaner.get(); try { - council.clean(list(root)); + cleaner.clean(list(root)); fail(); } catch (SSLPeerUnverifiedException expected) { } @@ -65,9 +63,8 @@ public final class CertificateChainCleanerTest { .issuedBy(certA) .build(); - CertificateChainCleaner council = new CertificateChainCleaner( - new RealTrustRootIndex(root.certificate)); - assertEquals(list(certB, certA, root), council.clean(list(certB, certA, root))); + CertificateChainCleaner cleaner = CertificateChainCleaner.get(root.certificate); + assertEquals(list(certB, certA, root), cleaner.clean(list(certB, certA, root))); } @Test public void orderedChainOfCertificatesWithoutRoot() throws Exception { @@ -83,9 +80,8 @@ public final class CertificateChainCleanerTest { .issuedBy(certA) .build(); - CertificateChainCleaner council = new CertificateChainCleaner( - new RealTrustRootIndex(root.certificate)); - assertEquals(list(certB, certA, root), council.clean(list(certB, certA))); // Root is added! + CertificateChainCleaner cleaner = CertificateChainCleaner.get(root.certificate); + assertEquals(list(certB, certA, root), cleaner.clean(list(certB, certA))); // Root is added! } @Test public void unorderedChainOfCertificatesWithRoot() throws Exception { @@ -105,9 +101,8 @@ public final class CertificateChainCleanerTest { .issuedBy(certB) .build(); - CertificateChainCleaner council = new CertificateChainCleaner( - new RealTrustRootIndex(root.certificate)); - assertEquals(list(certC, certB, certA, root), council.clean(list(certC, certA, root, certB))); + CertificateChainCleaner cleaner = CertificateChainCleaner.get(root.certificate); + assertEquals(list(certC, certB, certA, root), cleaner.clean(list(certC, certA, root, certB))); } @Test public void unorderedChainOfCertificatesWithoutRoot() throws Exception { @@ -127,9 +122,8 @@ public final class CertificateChainCleanerTest { .issuedBy(certB) .build(); - CertificateChainCleaner council = new CertificateChainCleaner( - new RealTrustRootIndex(root.certificate)); - assertEquals(list(certC, certB, certA, root), council.clean(list(certC, certA, certB))); + CertificateChainCleaner cleaner = CertificateChainCleaner.get(root.certificate); + assertEquals(list(certC, certB, certA, root), cleaner.clean(list(certC, certA, certB))); } @Test public void unrelatedCertificatesAreOmitted() throws Exception { @@ -148,10 +142,9 @@ public final class CertificateChainCleanerTest { .serialNumber("4") .build(); - CertificateChainCleaner council = new CertificateChainCleaner( - new RealTrustRootIndex(root.certificate)); + CertificateChainCleaner cleaner = CertificateChainCleaner.get(root.certificate); assertEquals(list(certB, certA, root), - council.clean(list(certB, certUnnecessary, certA, root))); + cleaner.clean(list(certB, certUnnecessary, certA, root))); } @Test public void chainGoesAllTheWayToSelfSignedRoot() throws Exception { @@ -171,14 +164,14 @@ public final class CertificateChainCleanerTest { .issuedBy(certA) .build(); - CertificateChainCleaner council = new CertificateChainCleaner( - new RealTrustRootIndex(selfSigned.certificate, trusted.certificate)); + CertificateChainCleaner cleaner = CertificateChainCleaner.get( + selfSigned.certificate, trusted.certificate); assertEquals(list(certB, certA, trusted, selfSigned), - council.clean(list(certB, certA))); + cleaner.clean(list(certB, certA))); assertEquals(list(certB, certA, trusted, selfSigned), - council.clean(list(certB, certA, trusted))); + cleaner.clean(list(certB, certA, trusted))); assertEquals(list(certB, certA, trusted, selfSigned), - council.clean(list(certB, certA, trusted, selfSigned))); + cleaner.clean(list(certB, certA, trusted, selfSigned))); } @Test public void trustedRootNotSelfSigned() throws Exception { @@ -198,12 +191,11 @@ public final class CertificateChainCleanerTest { .serialNumber("4") .build(); - CertificateChainCleaner council = new CertificateChainCleaner( - new RealTrustRootIndex(trusted.certificate)); + CertificateChainCleaner cleaner = CertificateChainCleaner.get(trusted.certificate); assertEquals(list(certificate, intermediateCa, trusted), - council.clean(list(certificate, intermediateCa))); + cleaner.clean(list(certificate, intermediateCa))); assertEquals(list(certificate, intermediateCa, trusted), - council.clean(list(certificate, intermediateCa, trusted))); + cleaner.clean(list(certificate, intermediateCa, trusted))); } @Test public void chainMaxLength() throws Exception { @@ -214,9 +206,9 @@ public final class CertificateChainCleanerTest { } X509Certificate root = heldCertificates.get(heldCertificates.size() - 1).certificate; - CertificateChainCleaner council = new CertificateChainCleaner(new RealTrustRootIndex(root)); - assertEquals(certificates, council.clean(certificates)); - assertEquals(certificates, council.clean(certificates.subList(0, 9))); + CertificateChainCleaner cleaner = CertificateChainCleaner.get(root); + assertEquals(certificates, cleaner.clean(certificates)); + assertEquals(certificates, cleaner.clean(certificates.subList(0, 9))); } @Test public void chainTooLong() throws Exception { @@ -227,9 +219,9 @@ public final class CertificateChainCleanerTest { } X509Certificate root = heldCertificates.get(heldCertificates.size() - 1).certificate; - CertificateChainCleaner council = new CertificateChainCleaner(new RealTrustRootIndex(root)); + CertificateChainCleaner cleaner = CertificateChainCleaner.get(root); try { - council.clean(certificates); + cleaner.clean(certificates); fail(); } catch (SSLPeerUnverifiedException expected) { } diff --git a/okhttp/src/main/java/okhttp3/CertificatePinner.java b/okhttp/src/main/java/okhttp3/CertificatePinner.java index d55e5eeb9740..f3344d00dd4d 100644 --- a/okhttp/src/main/java/okhttp3/CertificatePinner.java +++ b/okhttp/src/main/java/okhttp3/CertificatePinner.java @@ -24,7 +24,6 @@ import javax.net.ssl.SSLPeerUnverifiedException; import okhttp3.internal.Util; import okhttp3.internal.tls.CertificateChainCleaner; -import okhttp3.internal.tls.TrustRootIndex; import okio.ByteString; /** @@ -125,11 +124,11 @@ public final class CertificatePinner { public static final CertificatePinner DEFAULT = new Builder().build(); private final List pins; - private final TrustRootIndex trustRootIndex; + private final CertificateChainCleaner certificateChainCleaner; private CertificatePinner(Builder builder) { this.pins = Util.immutableList(builder.pins); - this.trustRootIndex = builder.trustRootIndex; + this.certificateChainCleaner = builder.certificateChainCleaner; } /** @@ -145,8 +144,8 @@ public void check(String hostname, List peerCertificates) List pins = findMatchingPins(hostname); if (pins.isEmpty()) return; - if (trustRootIndex != null) { - peerCertificates = new CertificateChainCleaner(trustRootIndex).clean(peerCertificates); + if (certificateChainCleaner != null) { + peerCertificates = certificateChainCleaner.clean(peerCertificates); } for (int c = 0, certsSize = peerCertificates.size(); c < certsSize; c++) { @@ -289,18 +288,18 @@ boolean matches(String hostname) { /** Builds a configured certificate pinner. */ public static final class Builder { private final List pins = new ArrayList<>(); - private TrustRootIndex trustRootIndex; + private CertificateChainCleaner certificateChainCleaner; public Builder() { } Builder(CertificatePinner certificatePinner) { this.pins.addAll(certificatePinner.pins); - this.trustRootIndex = certificatePinner.trustRootIndex; + this.certificateChainCleaner = certificatePinner.certificateChainCleaner; } - public Builder trustRootIndex(TrustRootIndex trustRootIndex) { - this.trustRootIndex = trustRootIndex; + public Builder certificateChainCleaner(CertificateChainCleaner certificateChainCleaner) { + this.certificateChainCleaner = certificateChainCleaner; return this; } diff --git a/okhttp/src/main/java/okhttp3/OkHttpClient.java b/okhttp/src/main/java/okhttp3/OkHttpClient.java index 3930c75c1006..194aaa6c3260 100644 --- a/okhttp/src/main/java/okhttp3/OkHttpClient.java +++ b/okhttp/src/main/java/okhttp3/OkHttpClient.java @@ -36,8 +36,8 @@ import okhttp3.internal.Util; import okhttp3.internal.http.StreamAllocation; import okhttp3.internal.io.RealConnection; +import okhttp3.internal.tls.CertificateChainCleaner; import okhttp3.internal.tls.OkHostnameVerifier; -import okhttp3.internal.tls.TrustRootIndex; /** * Factory for {@linkplain Call calls}, which can be used to send HTTP requests and read their @@ -133,7 +133,7 @@ public void apply(ConnectionSpec tlsConfiguration, SSLSocket sslSocket, boolean final InternalCache internalCache; final SocketFactory socketFactory; final SSLSocketFactory sslSocketFactory; - final TrustRootIndex trustRootIndex; + final CertificateChainCleaner certificateChainCleaner; final HostnameVerifier hostnameVerifier; final CertificatePinner certificatePinner; final Authenticator proxyAuthenticator; @@ -180,18 +180,18 @@ private OkHttpClient(Builder builder) { throw new AssertionError(); // The system has no TLS. Just give up. } } - if (sslSocketFactory != null && builder.trustRootIndex == null) { + if (sslSocketFactory != null && builder.certificateChainCleaner == null) { X509TrustManager trustManager = Platform.get().trustManager(sslSocketFactory); if (trustManager == null) { throw new IllegalStateException("Unable to extract the trust manager on " + Platform.get() + ", sslSocketFactory is " + sslSocketFactory.getClass()); } - this.trustRootIndex = Platform.get().trustRootIndex(trustManager); + this.certificateChainCleaner = CertificateChainCleaner.get(trustManager); this.certificatePinner = builder.certificatePinner.newBuilder() - .trustRootIndex(trustRootIndex) + .certificateChainCleaner(certificateChainCleaner) .build(); } else { - this.trustRootIndex = builder.trustRootIndex; + this.certificateChainCleaner = builder.certificateChainCleaner; this.certificatePinner = builder.certificatePinner; } this.hostnameVerifier = builder.hostnameVerifier; @@ -340,7 +340,7 @@ public static final class Builder { InternalCache internalCache; SocketFactory socketFactory; SSLSocketFactory sslSocketFactory; - TrustRootIndex trustRootIndex; + CertificateChainCleaner certificateChainCleaner; HostnameVerifier hostnameVerifier; CertificatePinner certificatePinner; Authenticator proxyAuthenticator; @@ -388,7 +388,7 @@ public Builder() { this.cache = okHttpClient.cache; this.socketFactory = okHttpClient.socketFactory; this.sslSocketFactory = okHttpClient.sslSocketFactory; - this.trustRootIndex = okHttpClient.trustRootIndex; + this.certificateChainCleaner = okHttpClient.certificateChainCleaner; this.hostnameVerifier = okHttpClient.hostnameVerifier; this.certificatePinner = okHttpClient.certificatePinner; this.proxyAuthenticator = okHttpClient.proxyAuthenticator; @@ -526,7 +526,7 @@ public Builder socketFactory(SocketFactory socketFactory) { public Builder sslSocketFactory(SSLSocketFactory sslSocketFactory) { if (sslSocketFactory == null) throw new NullPointerException("sslSocketFactory == null"); this.sslSocketFactory = sslSocketFactory; - this.trustRootIndex = null; + this.certificateChainCleaner = null; return this; } diff --git a/okhttp/src/main/java/okhttp3/internal/Platform.java b/okhttp/src/main/java/okhttp3/internal/Platform.java index 91568ab4ff9b..2125c991d440 100644 --- a/okhttp/src/main/java/okhttp3/internal/Platform.java +++ b/okhttp/src/main/java/okhttp3/internal/Platform.java @@ -32,9 +32,6 @@ import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.X509TrustManager; import okhttp3.Protocol; -import okhttp3.internal.tls.AndroidTrustRootIndex; -import okhttp3.internal.tls.RealTrustRootIndex; -import okhttp3.internal.tls.TrustRootIndex; import okio.Buffer; import static okhttp3.internal.Internal.logger; @@ -96,10 +93,6 @@ public X509TrustManager trustManager(SSLSocketFactory sslSocketFactory) { } } - public TrustRootIndex trustRootIndex(X509TrustManager trustManager) { - return new RealTrustRootIndex(trustManager.getAcceptedIssuers()); - } - /** * Configure TLS extensions on {@code sslSocket} for {@code route}. * @@ -243,12 +236,6 @@ public Android(Class sslParametersClass, OptionalMethod setUseSession return readFieldOrNull(context, X509TrustManager.class, "trustManager"); } - @Override public TrustRootIndex trustRootIndex(X509TrustManager trustManager) { - TrustRootIndex result = AndroidTrustRootIndex.get(trustManager); - if (result != null) return result; - return super.trustRootIndex(trustManager); - } - @Override public void configureTlsExtensions( SSLSocket sslSocket, String hostname, List protocols) { // Enable SNI and session tickets. diff --git a/okhttp/src/main/java/okhttp3/internal/tls/AndroidTrustRootIndex.java b/okhttp/src/main/java/okhttp3/internal/tls/AndroidTrustRootIndex.java deleted file mode 100644 index 3074a1e98ddf..000000000000 --- a/okhttp/src/main/java/okhttp3/internal/tls/AndroidTrustRootIndex.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2016 Square, Inc. - * - * 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 okhttp3.internal.tls; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.security.cert.TrustAnchor; -import java.security.cert.X509Certificate; -import javax.net.ssl.X509TrustManager; - -/** - * A index of trusted root certificates that exploits knowledge of Android implementation details. - * This class is potentially much faster to initialize than {@link RealTrustRootIndex} because - * it doesn't need to load and index trusted CA certificates. - */ -public final class AndroidTrustRootIndex implements TrustRootIndex { - private final X509TrustManager trustManager; - private final Method findByIssuerAndSignatureMethod; - - public AndroidTrustRootIndex( - X509TrustManager trustManager, Method findByIssuerAndSignatureMethod) { - this.findByIssuerAndSignatureMethod = findByIssuerAndSignatureMethod; - this.trustManager = trustManager; - } - - @Override public X509Certificate findByIssuerAndSignature(X509Certificate cert) { - try { - TrustAnchor trustAnchor = (TrustAnchor) findByIssuerAndSignatureMethod.invoke( - trustManager, cert); - return trustAnchor != null - ? trustAnchor.getTrustedCert() - : null; - } catch (IllegalAccessException e) { - throw new AssertionError(); - } catch (InvocationTargetException e) { - return null; - } - } - - public static TrustRootIndex get(X509TrustManager trustManager) { - // From org.conscrypt.TrustManagerImpl, we want the method with this signature: - // private TrustAnchor findTrustAnchorByIssuerAndSignature(X509Certificate lastCert); - try { - Method method = trustManager.getClass().getDeclaredMethod( - "findTrustAnchorByIssuerAndSignature", X509Certificate.class); - method.setAccessible(true); - return new AndroidTrustRootIndex(trustManager, method); - } catch (NoSuchMethodException e) { - return null; - } - } -} diff --git a/okhttp/src/main/java/okhttp3/internal/tls/CertificateChainCleaner.java b/okhttp/src/main/java/okhttp3/internal/tls/CertificateChainCleaner.java index d887df615cc5..0e056cb5dc96 100644 --- a/okhttp/src/main/java/okhttp3/internal/tls/CertificateChainCleaner.java +++ b/okhttp/src/main/java/okhttp3/internal/tls/CertificateChainCleaner.java @@ -25,6 +25,7 @@ import java.util.Iterator; import java.util.List; import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.X509TrustManager; /** * Computes the effective certificate chain from the raw array returned by Java's built in TLS APIs. @@ -35,83 +36,102 @@ *

Use of the chain cleaner is necessary to omit unexpected certificates that aren't relevant to * the TLS handshake and to extract the trusted CA certificate for the benefit of certificate * pinning. - * - *

This class includes code from Conscrypt's {@code - * TrustManagerImpl} and {@code TrustedCertificateIndex}. */ -public final class CertificateChainCleaner { - /** The maximum number of signers in a chain. We use 9 for consistency with OpenSSL. */ - private static final int MAX_SIGNERS = 9; +public abstract class CertificateChainCleaner { + public abstract List clean(List chain) + throws SSLPeerUnverifiedException; - private final TrustRootIndex trustRootIndex; + public static CertificateChainCleaner get(X509TrustManager trustManager) { + return new BasicCertificateChainCleaner(TrustRootIndex.get(trustManager)); + } - public CertificateChainCleaner(TrustRootIndex trustRootIndex) { - this.trustRootIndex = trustRootIndex; + public static CertificateChainCleaner get(X509Certificate... caCerts) { + return new BasicCertificateChainCleaner(TrustRootIndex.get(caCerts)); } /** - * Returns a cleaned chain for {@code chain}. + * A certificate chain cleaner that uses a set of trusted root certificates to build the trusted + * chain. * - *

This method throws if the complete chain to a trusted CA certificate cannot be constructed. - * This is unexpected unless the trust root index in this class has a different trust manager than - * what was used to establish {@code chain}. + *

This class includes code from Conscrypt's {@code + * TrustManagerImpl} and {@code TrustedCertificateIndex}. */ - public List clean(List chain) throws SSLPeerUnverifiedException { - Deque queue = new ArrayDeque<>(chain); - List result = new ArrayList<>(); - result.add(queue.removeFirst()); - boolean foundTrustedCertificate = false; + static final class BasicCertificateChainCleaner extends CertificateChainCleaner { + /** The maximum number of signers in a chain. We use 9 for consistency with OpenSSL. */ + private static final int MAX_SIGNERS = 9; + + private final TrustRootIndex trustRootIndex; + + public BasicCertificateChainCleaner(TrustRootIndex trustRootIndex) { + this.trustRootIndex = trustRootIndex; + } + + /** + * Returns a cleaned chain for {@code chain}. + * + *

This method throws if the complete chain to a trusted CA certificate cannot be + * constructed. This is unexpected unless the trust root index in this class has a different + * trust manager than what was used to establish {@code chain}. + */ + @Override public List clean(List chain) + throws SSLPeerUnverifiedException { + Deque queue = new ArrayDeque<>(chain); + List result = new ArrayList<>(); + result.add(queue.removeFirst()); + boolean foundTrustedCertificate = false; - followIssuerChain: - for (int c = 0; c < MAX_SIGNERS; c++) { - X509Certificate toVerify = (X509Certificate) result.get(result.size() - 1); + followIssuerChain: + for (int c = 0; c < MAX_SIGNERS; c++) { + X509Certificate toVerify = (X509Certificate) result.get(result.size() - 1); - // If this cert has been signed by a trusted cert, use that. Add the trusted certificate to - // the end of the chain unless it's already present. (That would happen if the first - // certificate in the chain is itself a self-signed and trusted CA certificate.) - X509Certificate trustedCert = trustRootIndex.findByIssuerAndSignature(toVerify); - if (trustedCert != null) { - if (result.size() > 1 || !toVerify.equals(trustedCert)) { - result.add(trustedCert); + // If this cert has been signed by a trusted cert, use that. Add the trusted certificate to + // the end of the chain unless it's already present. (That would happen if the first + // certificate in the chain is itself a self-signed and trusted CA certificate.) + X509Certificate trustedCert = trustRootIndex.findByIssuerAndSignature(toVerify); + if (trustedCert != null) { + if (result.size() > 1 || !toVerify.equals(trustedCert)) { + result.add(trustedCert); + } + if (verifySignature(trustedCert, trustedCert)) { + return result; // The self-signed cert is a root CA. We're done. + } + foundTrustedCertificate = true; + continue; } - if (verifySignature(trustedCert, trustedCert)) { - return result; // The self-signed cert is a root CA. We're done. + + // Search for the certificate in the chain that signed this certificate. This is typically + // the next element in the chain, but it could be any element. + for (Iterator i = queue.iterator(); i.hasNext(); ) { + X509Certificate signingCert = (X509Certificate) i.next(); + if (verifySignature(toVerify, signingCert)) { + i.remove(); + result.add(signingCert); + continue followIssuerChain; + } } - foundTrustedCertificate = true; - continue; - } - // Search for the certificate in the chain that signed this certificate. This is typically the - // next element in the chain, but it could be any element. - for (Iterator i = queue.iterator(); i.hasNext(); ) { - X509Certificate signingCert = (X509Certificate) i.next(); - if (verifySignature(toVerify, signingCert)) { - i.remove(); - result.add(signingCert); - continue followIssuerChain; + // We've reached the end of the chain. If any cert in the chain is trusted, we're done. + if (foundTrustedCertificate) { + return result; } - } - // We've reached the end of the chain. If any cert in the chain is trusted, we're done. - if (foundTrustedCertificate) { - return result; + // The last link isn't trusted. Fail. + throw new SSLPeerUnverifiedException( + "Failed to find a trusted cert that signed " + toVerify); } - // The last link isn't trusted. Fail. - throw new SSLPeerUnverifiedException("Failed to find a trusted cert that signed " + toVerify); + throw new SSLPeerUnverifiedException("Certificate chain too long: " + result); } - throw new SSLPeerUnverifiedException("Certificate chain too long: " + result); - } - - /** Returns true if {@code toVerify} was signed by {@code signingCert}'s public key. */ - private boolean verifySignature(X509Certificate toVerify, X509Certificate signingCert) { - if (!toVerify.getIssuerDN().equals(signingCert.getSubjectDN())) return false; - try { - toVerify.verify(signingCert.getPublicKey()); - return true; - } catch (GeneralSecurityException verifyFailed) { - return false; + /** Returns true if {@code toVerify} was signed by {@code signingCert}'s public key. */ + private boolean verifySignature(X509Certificate toVerify, X509Certificate signingCert) { + if (!toVerify.getIssuerDN().equals(signingCert.getSubjectDN())) return false; + try { + toVerify.verify(signingCert.getPublicKey()); + return true; + } catch (GeneralSecurityException verifyFailed) { + return false; + } } } } diff --git a/okhttp/src/main/java/okhttp3/internal/tls/RealTrustRootIndex.java b/okhttp/src/main/java/okhttp3/internal/tls/RealTrustRootIndex.java deleted file mode 100644 index fb688328f503..000000000000 --- a/okhttp/src/main/java/okhttp3/internal/tls/RealTrustRootIndex.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2016 Square, Inc. - * - * 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 okhttp3.internal.tls; - -import java.security.PublicKey; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import javax.security.auth.x500.X500Principal; - -public final class RealTrustRootIndex implements TrustRootIndex { - private final Map> subjectToCaCerts; - - public RealTrustRootIndex(X509Certificate... caCerts) { - subjectToCaCerts = new LinkedHashMap<>(); - for (X509Certificate caCert : caCerts) { - X500Principal subject = caCert.getSubjectX500Principal(); - List subjectCaCerts = subjectToCaCerts.get(subject); - if (subjectCaCerts == null) { - subjectCaCerts = new ArrayList<>(1); - subjectToCaCerts.put(subject, subjectCaCerts); - } - subjectCaCerts.add(caCert); - } - } - - @Override public X509Certificate findByIssuerAndSignature(X509Certificate cert) { - X500Principal issuer = cert.getIssuerX500Principal(); - List subjectCaCerts = subjectToCaCerts.get(issuer); - if (subjectCaCerts == null) return null; - - for (X509Certificate caCert : subjectCaCerts) { - PublicKey publicKey = caCert.getPublicKey(); - try { - cert.verify(publicKey); - return caCert; - } catch (Exception ignored) { - } - } - - return null; - } -} diff --git a/okhttp/src/main/java/okhttp3/internal/tls/TrustRootIndex.java b/okhttp/src/main/java/okhttp3/internal/tls/TrustRootIndex.java index 499f120e3337..3c314d70742c 100644 --- a/okhttp/src/main/java/okhttp3/internal/tls/TrustRootIndex.java +++ b/okhttp/src/main/java/okhttp3/internal/tls/TrustRootIndex.java @@ -15,9 +15,100 @@ */ package okhttp3.internal.tls; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.security.PublicKey; +import java.security.cert.TrustAnchor; import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import javax.net.ssl.X509TrustManager; +import javax.security.auth.x500.X500Principal; -public interface TrustRootIndex { +public abstract class TrustRootIndex { /** Returns the trusted CA certificate that signed {@code cert}. */ - X509Certificate findByIssuerAndSignature(X509Certificate cert); + abstract X509Certificate findByIssuerAndSignature(X509Certificate cert); + + public static TrustRootIndex get(X509TrustManager trustManager) { + try { + // From org.conscrypt.TrustManagerImpl, we want the method with this signature: + // private TrustAnchor findTrustAnchorByIssuerAndSignature(X509Certificate lastCert); + Method method = trustManager.getClass().getDeclaredMethod( + "findTrustAnchorByIssuerAndSignature", X509Certificate.class); + method.setAccessible(true); + return new AndroidTrustRootIndex(trustManager, method); + } catch (NoSuchMethodException e) { + return get(trustManager.getAcceptedIssuers()); + } + } + + public static TrustRootIndex get(X509Certificate... caCerts) { + return new BasicTrustRootIndex(caCerts); + } + + /** + * An index of trusted root certificates that exploits knowledge of Android implementation + * details. This class is potentially much faster to initialize than {@link BasicTrustRootIndex} + * because it doesn't need to load and index trusted CA certificates. + */ + static final class AndroidTrustRootIndex extends TrustRootIndex { + private final X509TrustManager trustManager; + private final Method findByIssuerAndSignatureMethod; + + AndroidTrustRootIndex(X509TrustManager trustManager, Method findByIssuerAndSignatureMethod) { + this.findByIssuerAndSignatureMethod = findByIssuerAndSignatureMethod; + this.trustManager = trustManager; + } + + @Override public X509Certificate findByIssuerAndSignature(X509Certificate cert) { + try { + TrustAnchor trustAnchor = (TrustAnchor) findByIssuerAndSignatureMethod.invoke( + trustManager, cert); + return trustAnchor != null + ? trustAnchor.getTrustedCert() + : null; + } catch (IllegalAccessException e) { + throw new AssertionError(); + } catch (InvocationTargetException e) { + return null; + } + } + } + + /** A simple index that of trusted root certificates that have been loaded into memory. */ + static final class BasicTrustRootIndex extends TrustRootIndex { + private final Map> subjectToCaCerts; + + public BasicTrustRootIndex(X509Certificate... caCerts) { + subjectToCaCerts = new LinkedHashMap<>(); + for (X509Certificate caCert : caCerts) { + X500Principal subject = caCert.getSubjectX500Principal(); + List subjectCaCerts = subjectToCaCerts.get(subject); + if (subjectCaCerts == null) { + subjectCaCerts = new ArrayList<>(1); + subjectToCaCerts.put(subject, subjectCaCerts); + } + subjectCaCerts.add(caCert); + } + } + + @Override public X509Certificate findByIssuerAndSignature(X509Certificate cert) { + X500Principal issuer = cert.getIssuerX500Principal(); + List subjectCaCerts = subjectToCaCerts.get(issuer); + if (subjectCaCerts == null) return null; + + for (X509Certificate caCert : subjectCaCerts) { + PublicKey publicKey = caCert.getPublicKey(); + try { + cert.verify(publicKey); + return caCert; + } catch (Exception ignored) { + } + } + + return null; + } + } }