1
0
mirror of https://github.com/square/okhttp.git synced 2026-01-17 08:42:25 +03:00

Merge pull request #4144 from square/jwilson.0712.platform_trust

New API, HandshakeCertificates.addPlatformTrustedCertificates()
This commit is contained in:
Jesse Wilson
2018-07-12 20:45:48 -04:00
committed by GitHub
11 changed files with 116 additions and 75 deletions

View File

@@ -530,7 +530,7 @@ public final class MockWebServerTest {
.certificateAuthority(0)
.build();
HeldCertificate serverCertificate = new HeldCertificate.Builder()
.issuedBy(serverCa)
.signedBy(serverCa)
.addSubjectAlternativeName(server.getHostName())
.build();
HandshakeCertificates serverHandshakeCertificates = new HandshakeCertificates.Builder()
@@ -543,7 +543,7 @@ public final class MockWebServerTest {
server.requestClientAuth();
HeldCertificate clientCertificate = new HeldCertificate.Builder()
.issuedBy(clientCa)
.signedBy(clientCa)
.build();
HandshakeCertificates clientHandshakeCertificates = new HandshakeCertificates.Builder()
.addTrustedCertificate(serverCa.certificate())

View File

@@ -77,11 +77,11 @@ public final class CertificateChainCleanerTest {
.build();
HeldCertificate certA = new HeldCertificate.Builder()
.serialNumber(2L)
.issuedBy(root)
.signedBy(root)
.build();
HeldCertificate certB = new HeldCertificate.Builder()
.serialNumber(3L)
.issuedBy(certA)
.signedBy(certA)
.build();
CertificateChainCleaner cleaner = CertificateChainCleaner.get(root.certificate());
@@ -94,11 +94,11 @@ public final class CertificateChainCleanerTest {
.build();
HeldCertificate certA = new HeldCertificate.Builder()
.serialNumber(2L)
.issuedBy(root)
.signedBy(root)
.build();
HeldCertificate certB = new HeldCertificate.Builder()
.serialNumber(3L)
.issuedBy(certA)
.signedBy(certA)
.build();
CertificateChainCleaner cleaner = CertificateChainCleaner.get(root.certificate());
@@ -112,15 +112,15 @@ public final class CertificateChainCleanerTest {
.build();
HeldCertificate certA = new HeldCertificate.Builder()
.serialNumber(2L)
.issuedBy(root)
.signedBy(root)
.build();
HeldCertificate certB = new HeldCertificate.Builder()
.serialNumber(3L)
.issuedBy(certA)
.signedBy(certA)
.build();
HeldCertificate certC = new HeldCertificate.Builder()
.serialNumber(4L)
.issuedBy(certB)
.signedBy(certB)
.build();
CertificateChainCleaner cleaner = CertificateChainCleaner.get(root.certificate());
@@ -134,15 +134,15 @@ public final class CertificateChainCleanerTest {
.build();
HeldCertificate certA = new HeldCertificate.Builder()
.serialNumber(2L)
.issuedBy(root)
.signedBy(root)
.build();
HeldCertificate certB = new HeldCertificate.Builder()
.serialNumber(3L)
.issuedBy(certA)
.signedBy(certA)
.build();
HeldCertificate certC = new HeldCertificate.Builder()
.serialNumber(4L)
.issuedBy(certB)
.signedBy(certB)
.build();
CertificateChainCleaner cleaner = CertificateChainCleaner.get(root.certificate());
@@ -156,11 +156,11 @@ public final class CertificateChainCleanerTest {
.build();
HeldCertificate certA = new HeldCertificate.Builder()
.serialNumber(2L)
.issuedBy(root)
.signedBy(root)
.build();
HeldCertificate certB = new HeldCertificate.Builder()
.serialNumber(3L)
.issuedBy(certA)
.signedBy(certA)
.build();
HeldCertificate certUnnecessary = new HeldCertificate.Builder()
.serialNumber(4L)
@@ -177,15 +177,15 @@ public final class CertificateChainCleanerTest {
.build();
HeldCertificate trusted = new HeldCertificate.Builder()
.serialNumber(2L)
.issuedBy(selfSigned)
.signedBy(selfSigned)
.build();
HeldCertificate certA = new HeldCertificate.Builder()
.serialNumber(3L)
.issuedBy(trusted)
.signedBy(trusted)
.build();
HeldCertificate certB = new HeldCertificate.Builder()
.serialNumber(4L)
.issuedBy(certA)
.signedBy(certA)
.build();
CertificateChainCleaner cleaner = CertificateChainCleaner.get(
@@ -203,15 +203,15 @@ public final class CertificateChainCleanerTest {
.serialNumber(1L)
.build();
HeldCertificate trusted = new HeldCertificate.Builder()
.issuedBy(unknownSigner)
.signedBy(unknownSigner)
.serialNumber(2L)
.build();
HeldCertificate intermediateCa = new HeldCertificate.Builder()
.issuedBy(trusted)
.signedBy(trusted)
.serialNumber(3L)
.build();
HeldCertificate certificate = new HeldCertificate.Builder()
.issuedBy(intermediateCa)
.signedBy(intermediateCa)
.serialNumber(4L)
.build();
@@ -256,7 +256,7 @@ public final class CertificateChainCleanerTest {
List<HeldCertificate> result = new ArrayList<>();
for (int i = 1; i <= length; i++) {
result.add(0, new HeldCertificate.Builder()
.issuedBy(!result.isEmpty() ? result.get(0) : null)
.signedBy(!result.isEmpty() ? result.get(0) : null)
.serialNumber(i)
.build());
}

View File

@@ -56,7 +56,7 @@ public final class ConnectionCoalescingTest {
.commonName("root")
.build();
certificate = new HeldCertificate.Builder()
.issuedBy(rootCa)
.signedBy(rootCa)
.serialNumber(2L)
.commonName(server.getHostName())
.addSubjectAlternativeName(server.getHostName())

View File

@@ -61,13 +61,13 @@ public final class CertificatePinnerChainValidationTest {
.commonName("root")
.build();
HeldCertificate intermediateCa = new HeldCertificate.Builder()
.issuedBy(rootCa)
.signedBy(rootCa)
.certificateAuthority(0)
.serialNumber(2L)
.commonName("intermediate_ca")
.build();
HeldCertificate certificate = new HeldCertificate.Builder()
.issuedBy(intermediateCa)
.signedBy(intermediateCa)
.serialNumber(3L)
.commonName(server.getHostName())
.build();
@@ -118,13 +118,13 @@ public final class CertificatePinnerChainValidationTest {
.commonName("root")
.build();
HeldCertificate intermediateCa = new HeldCertificate.Builder()
.issuedBy(rootCa)
.signedBy(rootCa)
.certificateAuthority(0)
.serialNumber(2L)
.commonName("intermediate_ca")
.build();
HeldCertificate certificate = new HeldCertificate.Builder()
.issuedBy(intermediateCa)
.signedBy(intermediateCa)
.serialNumber(3L)
.commonName(server.getHostName())
.build();
@@ -184,13 +184,13 @@ public final class CertificatePinnerChainValidationTest {
// SSL context for an HTTP client under attack. It includes the trusted CA and a pinned
// certificate.
HeldCertificate goodIntermediateCa = new HeldCertificate.Builder()
.issuedBy(rootCa)
.signedBy(rootCa)
.certificateAuthority(0)
.serialNumber(2L)
.commonName("good_intermediate_ca")
.build();
HeldCertificate goodCertificate = new HeldCertificate.Builder()
.issuedBy(goodIntermediateCa)
.signedBy(goodIntermediateCa)
.serialNumber(3L)
.commonName(server.getHostName())
.build();
@@ -212,14 +212,14 @@ public final class CertificatePinnerChainValidationTest {
// trusted good certificate above. The attack is that by including the good certificate in the
// chain, we may trick the certificate pinner into accepting the rouge certificate.
HeldCertificate compromisedIntermediateCa = new HeldCertificate.Builder()
.issuedBy(rootCa)
.signedBy(rootCa)
.certificateAuthority(0)
.serialNumber(4L)
.commonName("bad_intermediate_ca")
.build();
HeldCertificate rogueCertificate = new HeldCertificate.Builder()
.serialNumber(5L)
.issuedBy(compromisedIntermediateCa)
.signedBy(compromisedIntermediateCa)
.commonName(server.getHostName())
.build();
@@ -264,7 +264,7 @@ public final class CertificatePinnerChainValidationTest {
// SSL context for an HTTP client under attack. It includes the trusted CA and a pinned
// certificate.
HeldCertificate goodIntermediateCa = new HeldCertificate.Builder()
.issuedBy(rootCa)
.signedBy(rootCa)
.certificateAuthority(0)
.serialNumber(3L)
.commonName("intermediate_ca")
@@ -288,14 +288,14 @@ public final class CertificatePinnerChainValidationTest {
// serves the good CAs certificate in the chain, which means the certificate pinner sees a
// different set of certificates than the SSL verifier.
HeldCertificate compromisedIntermediateCa = new HeldCertificate.Builder()
.issuedBy(compromisedRootCa)
.signedBy(compromisedRootCa)
.certificateAuthority(0)
.serialNumber(4L)
.commonName("intermediate_ca")
.build();
HeldCertificate rogueCertificate = new HeldCertificate.Builder()
.serialNumber(5L)
.issuedBy(compromisedIntermediateCa)
.signedBy(compromisedIntermediateCa)
.commonName(server.getHostName())
.build();

View File

@@ -67,7 +67,7 @@ public final class ClientAuthTest {
.addSubjectAlternativeName("root_ca.com")
.build();
serverIntermediateCa = new HeldCertificate.Builder()
.issuedBy(serverRootCa)
.signedBy(serverRootCa)
.certificateAuthority(0)
.serialNumber(2L)
.commonName("intermediate_ca")
@@ -75,7 +75,7 @@ public final class ClientAuthTest {
.build();
serverCert = new HeldCertificate.Builder()
.issuedBy(serverIntermediateCa)
.signedBy(serverIntermediateCa)
.serialNumber(3L)
.commonName("Local Host")
.addSubjectAlternativeName(server.getHostName())
@@ -88,7 +88,7 @@ public final class ClientAuthTest {
.addSubjectAlternativeName("root_ca.com")
.build();
clientIntermediateCa = new HeldCertificate.Builder()
.issuedBy(serverRootCa)
.signedBy(serverRootCa)
.certificateAuthority(0)
.serialNumber(2L)
.commonName("intermediate_ca")
@@ -96,7 +96,7 @@ public final class ClientAuthTest {
.build();
clientCert = new HeldCertificate.Builder()
.issuedBy(clientIntermediateCa)
.signedBy(clientIntermediateCa)
.serialNumber(4L)
.commonName("Jethro Willis")
.addSubjectAlternativeName("jethrowillis.com")
@@ -188,7 +188,7 @@ public final class ClientAuthTest {
@Test public void commonNameIsNotTrusted() throws Exception {
serverCert = new HeldCertificate.Builder()
.issuedBy(serverIntermediateCa)
.signedBy(serverIntermediateCa)
.serialNumber(3L)
.commonName(server.getHostName())
.addSubjectAlternativeName("different-host.com")

View File

@@ -20,6 +20,7 @@ import java.security.KeyManagementException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
@@ -27,6 +28,8 @@ import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;
import okhttp3.CertificatePinner;
import okhttp3.internal.Util;
import okhttp3.internal.platform.Platform;
import static okhttp3.tls.internal.TlsUtil.newKeyManager;
@@ -137,6 +140,24 @@ public final class HandshakeCertificates {
return this;
}
/**
* Add all of the host platform's trusted root certificates. This set varies by platform
* (Android vs. Java), by platform release (Android 4.4 vs. Android 9), and with user
* customizations.
*
* <p>Most TLS clients that connect to hosts on the public Internet should call this method.
* Otherwise it is necessary to manually prepare a comprehensive set of trusted roots.
*
* <p>If the host platform is compromised or misconfigured this may contain untrustworthy root
* certificates. Applications that connect to a known set of servers may be able to mitigate
* this problem with {@linkplain CertificatePinner certificate pinning}.
*/
public Builder addPlatformTrustedCertificates() {
X509TrustManager platformTrustManager = Util.platformTrustManager();
Collections.addAll(trustedCertificates, platformTrustManager.getAcceptedIssuers());
return this;
}
public HandshakeCertificates build() {
try {
X509KeyManager keyManager = newKeyManager(null, heldCertificate, intermediates);

View File

@@ -208,7 +208,7 @@ public final class HeldCertificate {
private final List<String> altNames = new ArrayList<>();
private BigInteger serialNumber;
private KeyPair keyPair;
private HeldCertificate issuedBy;
private HeldCertificate signedBy;
private int maxIntermediateCas = -1;
private String keyAlgorithm;
private int keySize;
@@ -298,11 +298,11 @@ public final class HeldCertificate {
}
/**
* Set the certificate that will sign this certificate. If unset the certificate will be
* Set the certificate that will issue this certificate. If unset the certificate will be
* self-signed.
*/
public Builder issuedBy(HeldCertificate issuedBy) {
this.issuedBy = issuedBy;
public Builder signedBy(HeldCertificate signedBy) {
this.signedBy = signedBy;
return this;
}
@@ -357,9 +357,9 @@ public final class HeldCertificate {
// Subject, public & private keys for this certificate's signer. It may be self signed!
KeyPair signedByKeyPair;
X500Principal signedByPrincipal;
if (issuedBy != null) {
signedByKeyPair = issuedBy.keyPair;
signedByPrincipal = issuedBy.certificate.getSubjectX500Principal();
if (signedBy != null) {
signedByKeyPair = signedBy.keyPair;
signedByPrincipal = signedBy.certificate.getSubjectX500Principal();
} else {
signedByKeyPair = heldKeyPair;
signedByPrincipal = subject;

View File

@@ -21,7 +21,10 @@ import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -37,6 +40,7 @@ import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public final class HandshakeCertificatesTest {
private ExecutorService executorService;
@@ -57,10 +61,10 @@ public final class HandshakeCertificatesTest {
.build();
HeldCertificate clientIntermediate = new HeldCertificate.Builder()
.certificateAuthority(0)
.issuedBy(clientRoot)
.signedBy(clientRoot)
.build();
HeldCertificate clientCertificate = new HeldCertificate.Builder()
.issuedBy(clientIntermediate)
.signedBy(clientIntermediate)
.build();
HeldCertificate serverRoot = new HeldCertificate.Builder()
@@ -68,10 +72,10 @@ public final class HandshakeCertificatesTest {
.build();
HeldCertificate serverIntermediate = new HeldCertificate.Builder()
.certificateAuthority(0)
.issuedBy(serverRoot)
.signedBy(serverRoot)
.build();
HeldCertificate serverCertificate = new HeldCertificate.Builder()
.issuedBy(serverIntermediate)
.signedBy(serverIntermediate)
.build();
HandshakeCertificates server = new HandshakeCertificates.Builder()
@@ -108,10 +112,10 @@ public final class HandshakeCertificatesTest {
.build();
HeldCertificate intermediate = new HeldCertificate.Builder()
.certificateAuthority(0)
.issuedBy(root)
.signedBy(root)
.build();
HeldCertificate certificate = new HeldCertificate.Builder()
.issuedBy(intermediate)
.signedBy(intermediate)
.build();
HandshakeCertificates handshakeCertificates = new HandshakeCertificates.Builder()
@@ -123,6 +127,20 @@ public final class HandshakeCertificatesTest {
Arrays.asList(handshakeCertificates.keyManager().getCertificateChain("private")));
}
@Test public void platformTrustedCertificates() {
HandshakeCertificates handshakeCertificates = new HandshakeCertificates.Builder()
.addPlatformTrustedCertificates()
.build();
Set<String> names = new LinkedHashSet<>();
for (X509Certificate certificate : handshakeCertificates.trustManager().getAcceptedIssuers()) {
// Abbreviate a long name like "CN=Entrust Root Certification Authority - G2, OU=..."
String name = certificate.getSubjectDN().getName();
names.add(name.substring(0, name.indexOf(" ")));
}
// It's safe to assume all platforms will have a major Internet certificate issuer.
assertTrue(names.toString(), names.contains("CN=Entrust"));
}
private InetSocketAddress startTlsServer() throws IOException {
ServerSocketFactory serverSocketFactory = ServerSocketFactory.getDefault();
serverSocket = serverSocketFactory.createServerSocket();

View File

@@ -202,7 +202,7 @@ public final class HeldCertificateTest {
HeldCertificate leaf = new HeldCertificate.Builder()
.certificateAuthority(0)
.ecdsa256()
.issuedBy(root)
.signedBy(root)
.build();
assertEquals("SHA256WITHRSA", root.certificate().getSigAlgName());
@@ -217,7 +217,7 @@ public final class HeldCertificateTest {
HeldCertificate leaf = new HeldCertificate.Builder()
.certificateAuthority(0)
.rsa2048()
.issuedBy(root)
.signedBy(root)
.build();
assertEquals("SHA256WITHECDSA", root.certificate().getSigAlgName());

View File

@@ -19,9 +19,7 @@ import java.net.Proxy;
import java.net.ProxySelector;
import java.net.Socket;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
@@ -34,7 +32,6 @@ import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import okhttp3.internal.Internal;
import okhttp3.internal.Util;
@@ -248,8 +245,8 @@ public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory
this.sslSocketFactory = builder.sslSocketFactory;
this.certificateChainCleaner = builder.certificateChainCleaner;
} else {
X509TrustManager trustManager = systemDefaultTrustManager();
this.sslSocketFactory = systemDefaultSslSocketFactory(trustManager);
X509TrustManager trustManager = Util.platformTrustManager();
this.sslSocketFactory = newSslSocketFactory(trustManager);
this.certificateChainCleaner = CertificateChainCleaner.get(trustManager);
}
@@ -280,23 +277,7 @@ public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory
}
}
private X509TrustManager systemDefaultTrustManager() {
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:"
+ Arrays.toString(trustManagers));
}
return (X509TrustManager) trustManagers[0];
} catch (GeneralSecurityException e) {
throw assertionError("No System TLS", e); // The system has no TLS. Just give up.
}
}
private SSLSocketFactory systemDefaultSslSocketFactory(X509TrustManager trustManager) {
private static SSLSocketFactory newSslSocketFactory(X509TrustManager trustManager) {
try {
SSLContext sslContext = Platform.get().getSSLContext();
sslContext.init(null, new TrustManager[] { trustManager }, null);

View File

@@ -26,6 +26,8 @@ import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -37,6 +39,9 @@ import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import okhttp3.HttpUrl;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
@@ -642,4 +647,20 @@ public final class Util {
}
return result.readUtf8();
}
public static X509TrustManager platformTrustManager() {
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:"
+ Arrays.toString(trustManagers));
}
return (X509TrustManager) trustManagers[0];
} catch (GeneralSecurityException e) {
throw assertionError("No System TLS", e); // The system has no TLS. Just give up.
}
}
}