diff --git a/okhttp/src/main/java/okhttp3/internal/AndroidPlatform.java b/okhttp/src/main/java/okhttp3/internal/AndroidPlatform.java index 65e97020b24d..c0096c6cd1ff 100644 --- a/okhttp/src/main/java/okhttp3/internal/AndroidPlatform.java +++ b/okhttp/src/main/java/okhttp3/internal/AndroidPlatform.java @@ -17,15 +17,20 @@ import android.util.Log; import java.io.IOException; +import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.InetSocketAddress; import java.net.Socket; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; import java.util.List; +import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.X509TrustManager; import okhttp3.Protocol; +import okhttp3.internal.tls.CertificateChainCleaner; /** Android 2.3 or better. */ class AndroidPlatform extends Platform { @@ -144,6 +149,19 @@ public AndroidPlatform(Class sslParametersClass, OptionalMethod setUs } } + public CertificateChainCleaner buildCertificateChainCleaner(X509TrustManager trustManager) { + try { + Class extensionsClass = Class.forName("android.net.http.X509TrustManagerExtensions"); + Constructor constructor = extensionsClass.getConstructor(X509TrustManager.class); + Object extensions = constructor.newInstance(trustManager); + Method checkServerTrusted = extensionsClass.getMethod( + "checkServerTrusted", X509Certificate[].class, String.class, String.class); + return new AndroidCertificateChainCleaner(extensions, checkServerTrusted); + } catch (Exception e) { + return super.buildCertificateChainCleaner(trustManager); + } + } + public static Platform buildIfSupported() { // Attempt to find Android 2.3+ APIs. try { @@ -179,4 +197,35 @@ public static Platform buildIfSupported() { return null; } + + /** + * X509TrustManagerExtensions was added to Android in API 17 (Android 4.2, released in late 2012). + * This is the best way to get a clean chain on Android because it uses the same code as the TLS + * handshake. + */ + static final class AndroidCertificateChainCleaner extends CertificateChainCleaner { + private final Object x509TrustManagerExtensions; + private final Method checkServerTrusted; + + AndroidCertificateChainCleaner(Object x509TrustManagerExtensions, Method checkServerTrusted) { + this.x509TrustManagerExtensions = x509TrustManagerExtensions; + this.checkServerTrusted = checkServerTrusted; + } + + @SuppressWarnings({"unchecked", "SuspiciousToArrayCall"}) // Reflection on List. + @Override public List clean(List chain, String hostname) + throws SSLPeerUnverifiedException { + try { + X509Certificate[] certificates = chain.toArray(new X509Certificate[chain.size()]); + return (List) checkServerTrusted.invoke( + x509TrustManagerExtensions, certificates, "RSA", hostname); + } catch (InvocationTargetException e) { + SSLPeerUnverifiedException exception = new SSLPeerUnverifiedException(e.getMessage()); + exception.initCause(e); + throw exception; + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } + } + } } diff --git a/okhttp/src/main/java/okhttp3/internal/Platform.java b/okhttp/src/main/java/okhttp3/internal/Platform.java index 34da83c0d2c2..1245b6328c27 100644 --- a/okhttp/src/main/java/okhttp3/internal/Platform.java +++ b/okhttp/src/main/java/okhttp3/internal/Platform.java @@ -29,6 +29,9 @@ import javax.net.ssl.X509TrustManager; import okhttp3.OkHttpClient; import okhttp3.Protocol; +import okhttp3.internal.tls.BasicCertificateChainCleaner; +import okhttp3.internal.tls.CertificateChainCleaner; +import okhttp3.internal.tls.TrustRootIndex; import okio.Buffer; /** @@ -140,6 +143,10 @@ public static List alpnProtocolNames(List protocols) { return names; } + public CertificateChainCleaner buildCertificateChainCleaner(X509TrustManager trustManager) { + return new BasicCertificateChainCleaner(TrustRootIndex.get(trustManager)); + } + /** Attempt to match the host runtime to a capable Platform implementation. */ private static Platform findPlatform() { Platform android = AndroidPlatform.buildIfSupported(); diff --git a/okhttp/src/main/java/okhttp3/internal/tls/BasicCertificateChainCleaner.java b/okhttp/src/main/java/okhttp3/internal/tls/BasicCertificateChainCleaner.java new file mode 100644 index 000000000000..be8cab093d7e --- /dev/null +++ b/okhttp/src/main/java/okhttp3/internal/tls/BasicCertificateChainCleaner.java @@ -0,0 +1,114 @@ +/* + * 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.GeneralSecurityException; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.Iterator; +import java.util.List; +import javax.net.ssl.SSLPeerUnverifiedException; + +/** + * A certificate chain cleaner that uses a set of trusted root certificates to build the trusted + * chain. This class duplicates the clean chain building performed during the TLS handshake. We + * prefer other mechanisms where they exist, such as with + * {@link okhttp3.internal.AndroidPlatform.AndroidCertificateChainCleaner}. + * + *

This class includes code from Conscrypt's {@code + * TrustManagerImpl} and {@code TrustedCertificateIndex}. + */ +public 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, String hostname) + 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); + + // 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; + } + + // 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; + } + + // 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); + } + + /** 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/CertificateChainCleaner.java b/okhttp/src/main/java/okhttp3/internal/tls/CertificateChainCleaner.java index 672e48651b0d..9b7c2e2a36c3 100644 --- a/okhttp/src/main/java/okhttp3/internal/tls/CertificateChainCleaner.java +++ b/okhttp/src/main/java/okhttp3/internal/tls/CertificateChainCleaner.java @@ -16,19 +16,12 @@ */ package okhttp3.internal.tls; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.security.GeneralSecurityException; import java.security.cert.Certificate; import java.security.cert.X509Certificate; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Deque; -import java.util.Iterator; import java.util.List; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.X509TrustManager; +import okhttp3.internal.Platform; /** * Computes the effective certificate chain from the raw array returned by Java's built in TLS APIs. @@ -45,137 +38,10 @@ public abstract List clean(List chain, String hostname throws SSLPeerUnverifiedException; public static CertificateChainCleaner get(X509TrustManager trustManager) { - try { - Class extensionsClass = Class.forName("android.net.http.X509TrustManagerExtensions"); - Constructor constructor = extensionsClass.getConstructor(X509TrustManager.class); - Object extensions = constructor.newInstance(trustManager); - Method checkServerTrusted = extensionsClass.getMethod( - "checkServerTrusted", X509Certificate[].class, String.class, String.class); - return new AndroidCertificateChainCleaner(extensions, checkServerTrusted); - } catch (Exception e) { - return new BasicCertificateChainCleaner(TrustRootIndex.get(trustManager)); - } + return Platform.get().buildCertificateChainCleaner(trustManager); } public static CertificateChainCleaner get(X509Certificate... caCerts) { return new BasicCertificateChainCleaner(TrustRootIndex.get(caCerts)); } - - /** - * A certificate chain cleaner that uses a set of trusted root certificates to build the trusted - * chain. This class duplicates the clean chain building performed during the TLS handshake. We - * prefer other mechanisms where they exist, such as with {@link AndroidCertificateChainCleaner}. - * - *

This class includes code from Conscrypt's {@code - * TrustManagerImpl} and {@code TrustedCertificateIndex}. - */ - 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, String hostname) - 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); - - // 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; - } - - // 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; - } - - // 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); - } - - /** 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; - } - } - } - - /** - * X509TrustManagerExtensions was added to Android in API 17 (Android 4.2, released in late 2012). - * This is the best way to get a clean chain on Android because it uses the same code as the TLS - * handshake. - */ - static final class AndroidCertificateChainCleaner extends CertificateChainCleaner { - private final Object x509TrustManagerExtensions; - private final Method checkServerTrusted; - - AndroidCertificateChainCleaner(Object x509TrustManagerExtensions, Method checkServerTrusted) { - this.x509TrustManagerExtensions = x509TrustManagerExtensions; - this.checkServerTrusted = checkServerTrusted; - } - - @SuppressWarnings({"unchecked", "SuspiciousToArrayCall"}) // Reflection on List. - @Override public List clean(List chain, String hostname) - throws SSLPeerUnverifiedException { - try { - X509Certificate[] certificates = chain.toArray(new X509Certificate[chain.size()]); - return (List) checkServerTrusted.invoke( - x509TrustManagerExtensions, certificates, "RSA", hostname); - } catch (InvocationTargetException e) { - SSLPeerUnverifiedException exception = new SSLPeerUnverifiedException(e.getMessage()); - exception.initCause(e); - throw exception; - } catch (IllegalAccessException e) { - throw new AssertionError(e); - } - } - } } diff --git a/okhttp/src/main/java/okhttp3/internal/tls/TrustRootIndex.java b/okhttp/src/main/java/okhttp3/internal/tls/TrustRootIndex.java index fcc7468e0e01..8b5439b182e8 100644 --- a/okhttp/src/main/java/okhttp3/internal/tls/TrustRootIndex.java +++ b/okhttp/src/main/java/okhttp3/internal/tls/TrustRootIndex.java @@ -29,7 +29,7 @@ public abstract class TrustRootIndex { /** Returns the trusted CA certificate that signed {@code cert}. */ - abstract X509Certificate findByIssuerAndSignature(X509Certificate cert); + public abstract X509Certificate findByIssuerAndSignature(X509Certificate cert); public static TrustRootIndex get(X509TrustManager trustManager) { try { @@ -55,7 +55,7 @@ public static TrustRootIndex get(X509Certificate... caCerts) { * *

This class uses APIs added to Android in API 14 (Android 4.0, released October 2011). This * class shouldn't be used in Android API 17 or better because those releases are better served by - * {@link CertificateChainCleaner.AndroidCertificateChainCleaner}. + * {@link okhttp3.internal.AndroidPlatform.AndroidCertificateChainCleaner}. */ static final class AndroidTrustRootIndex extends TrustRootIndex { private final X509TrustManager trustManager;