mirror of
https://github.com/square/okhttp.git
synced 2025-11-24 18:41:06 +03:00
HandshakeCertificates.Builder.addInsecureHost()
This API continues the work started here: https://github.com/square/okhttp/pull/5872
This commit is contained in:
@@ -13,6 +13,7 @@ dependencies {
|
|||||||
implementation deps.bouncycastlepkix
|
implementation deps.bouncycastlepkix
|
||||||
implementation deps.bouncycastletls
|
implementation deps.bouncycastletls
|
||||||
compileOnly deps.jsr305
|
compileOnly deps.jsr305
|
||||||
|
compileOnly deps.animalSniffer
|
||||||
|
|
||||||
testImplementation project(':okhttp-testing-support')
|
testImplementation project(':okhttp-testing-support')
|
||||||
testImplementation deps.junit
|
testImplementation deps.junit
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package okhttp3.tls
|
|||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
|
import javax.net.ssl.HostnameVerifier
|
||||||
import javax.net.ssl.KeyManager
|
import javax.net.ssl.KeyManager
|
||||||
import javax.net.ssl.SSLContext
|
import javax.net.ssl.SSLContext
|
||||||
import javax.net.ssl.SSLSocketFactory
|
import javax.net.ssl.SSLSocketFactory
|
||||||
@@ -26,6 +27,7 @@ import javax.net.ssl.X509KeyManager
|
|||||||
import javax.net.ssl.X509TrustManager
|
import javax.net.ssl.X509TrustManager
|
||||||
import okhttp3.CertificatePinner
|
import okhttp3.CertificatePinner
|
||||||
import okhttp3.internal.platform.Platform
|
import okhttp3.internal.platform.Platform
|
||||||
|
import okhttp3.internal.toImmutableList
|
||||||
import okhttp3.tls.internal.TlsUtil.newKeyManager
|
import okhttp3.tls.internal.TlsUtil.newKeyManager
|
||||||
import okhttp3.tls.internal.TlsUtil.newTrustManager
|
import okhttp3.tls.internal.TlsUtil.newTrustManager
|
||||||
|
|
||||||
@@ -97,6 +99,7 @@ class HandshakeCertificates private constructor(
|
|||||||
private var heldCertificate: HeldCertificate? = null
|
private var heldCertificate: HeldCertificate? = null
|
||||||
private var intermediates: Array<X509Certificate>? = null
|
private var intermediates: Array<X509Certificate>? = null
|
||||||
private val trustedCertificates = mutableListOf<X509Certificate>()
|
private val trustedCertificates = mutableListOf<X509Certificate>()
|
||||||
|
private val insecureHosts = mutableListOf<String>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configure the certificate chain to use when being authenticated. The first certificate is
|
* Configure the certificate chain to use when being authenticated. The first certificate is
|
||||||
@@ -140,9 +143,37 @@ class HandshakeCertificates private constructor(
|
|||||||
Collections.addAll(trustedCertificates, *platformTrustManager.acceptedIssuers)
|
Collections.addAll(trustedCertificates, *platformTrustManager.acceptedIssuers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures this to not authenticate the HTTPS server on to [hostname]. This makes the user
|
||||||
|
* vulnerable to man-in-the-middle attacks and should only be used only in private development
|
||||||
|
* environments and only to carry test data.
|
||||||
|
*
|
||||||
|
* The server’s TLS certificate **does not need to be signed** by a trusted certificate
|
||||||
|
* authority. Instead, it will trust any well-formed certificate, even if it is self-signed.
|
||||||
|
* This is necessary for testing against localhost or in development environments where a
|
||||||
|
* certificate authority is not possible.
|
||||||
|
*
|
||||||
|
* The server’s TLS certificate still must match the requested hostname. For example, if the
|
||||||
|
* certificate is issued to `example.com` and the request is to `localhost`, the connection will
|
||||||
|
* fail. Use a custom [HostnameVerifier] to ignore such problems.
|
||||||
|
*
|
||||||
|
* Other TLS features are still used but provide no security benefits in absence of the above
|
||||||
|
* gaps. For example, an insecure TLS connection is capable of negotiating HTTP/2 with ALPN and
|
||||||
|
* it also has a regular-looking handshake.
|
||||||
|
*
|
||||||
|
* **This feature is not supported on Android API levels less than 24.** Prior releases lacked
|
||||||
|
* a mechanism to trust some hosts and not others.
|
||||||
|
*
|
||||||
|
* @param hostname the exact hostname from the URL for insecure connections.
|
||||||
|
*/
|
||||||
|
fun addInsecureHost(hostname: String) = apply {
|
||||||
|
insecureHosts += hostname
|
||||||
|
}
|
||||||
|
|
||||||
fun build(): HandshakeCertificates {
|
fun build(): HandshakeCertificates {
|
||||||
|
val immutableInsecureHosts = insecureHosts.toImmutableList()
|
||||||
val keyManager = newKeyManager(null, heldCertificate, *(intermediates ?: emptyArray()))
|
val keyManager = newKeyManager(null, heldCertificate, *(intermediates ?: emptyArray()))
|
||||||
val trustManager = newTrustManager(null, trustedCertificates)
|
val trustManager = newTrustManager(null, trustedCertificates, immutableInsecureHosts)
|
||||||
return HandshakeCertificates(keyManager, trustManager)
|
return HandshakeCertificates(keyManager, trustManager)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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.tls.internal
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException
|
||||||
|
import java.lang.reflect.Method
|
||||||
|
import java.security.cert.Certificate
|
||||||
|
import java.security.cert.CertificateException
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
|
import javax.net.ssl.X509TrustManager
|
||||||
|
|
||||||
|
/** This extends [X509TrustManager] for Android to disable verification for a set of hosts. */
|
||||||
|
internal class InsecureAndroidTrustManager(
|
||||||
|
private val delegate: X509TrustManager,
|
||||||
|
private val insecureHosts: List<String>
|
||||||
|
) : X509TrustManager {
|
||||||
|
private val checkServerTrustedMethod: Method? = try {
|
||||||
|
delegate::class.java.getMethod("checkServerTrusted",
|
||||||
|
Array<X509Certificate>::class.java, String::class.java, String::class.java)
|
||||||
|
} catch (_: NoSuchMethodException) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Android method to clean and sort certificates, called via reflection. */
|
||||||
|
@Suppress("unused", "UNCHECKED_CAST")
|
||||||
|
fun checkServerTrusted(
|
||||||
|
chain: Array<out X509Certificate>,
|
||||||
|
authType: String,
|
||||||
|
host: String
|
||||||
|
): List<Certificate> {
|
||||||
|
if (host in insecureHosts) return listOf()
|
||||||
|
try {
|
||||||
|
val method = checkServerTrustedMethod
|
||||||
|
?: throw CertificateException("Failed to call checkServerTrusted")
|
||||||
|
return method.invoke(this, chain, authType, host) as List<Certificate>
|
||||||
|
} catch (e: InvocationTargetException) {
|
||||||
|
throw e.targetException
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAcceptedIssuers(): Array<X509Certificate> = delegate.acceptedIssuers
|
||||||
|
|
||||||
|
override fun checkClientTrusted(chain: Array<out X509Certificate>, authType: String?) =
|
||||||
|
throw CertificateException("Unsupported operation")
|
||||||
|
|
||||||
|
override fun checkServerTrusted(chain: Array<out X509Certificate>, authType: String) =
|
||||||
|
throw CertificateException("Unsupported operation")
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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.tls.internal
|
||||||
|
|
||||||
|
import java.net.Socket
|
||||||
|
import java.security.cert.CertificateException
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
|
import javax.net.ssl.SSLEngine
|
||||||
|
import javax.net.ssl.X509ExtendedTrustManager
|
||||||
|
import okhttp3.internal.peerName
|
||||||
|
import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This extends [X509ExtendedTrustManager] to disable verification for a set of hosts.
|
||||||
|
*
|
||||||
|
* Note that the superclass [X509ExtendedTrustManager] isn't available on Android until version 7
|
||||||
|
* (API level 24).
|
||||||
|
*/
|
||||||
|
@IgnoreJRERequirement
|
||||||
|
internal class InsecureExtendedTrustManager(
|
||||||
|
private val delegate: X509ExtendedTrustManager,
|
||||||
|
private val insecureHosts: List<String>
|
||||||
|
) : X509ExtendedTrustManager() {
|
||||||
|
override fun getAcceptedIssuers(): Array<X509Certificate> = delegate.acceptedIssuers
|
||||||
|
|
||||||
|
override fun checkServerTrusted(
|
||||||
|
chain: Array<out X509Certificate>,
|
||||||
|
authType: String,
|
||||||
|
socket: Socket
|
||||||
|
) {
|
||||||
|
if (socket.peerName() !in insecureHosts) {
|
||||||
|
delegate.checkServerTrusted(chain, authType, socket)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun checkServerTrusted(
|
||||||
|
chain: Array<out X509Certificate>,
|
||||||
|
authType: String,
|
||||||
|
engine: SSLEngine
|
||||||
|
) {
|
||||||
|
if (engine.peerHost !in insecureHosts) {
|
||||||
|
delegate.checkServerTrusted(chain, authType, engine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun checkServerTrusted(chain: Array<out X509Certificate>, authType: String) =
|
||||||
|
throw CertificateException("Unsupported operation")
|
||||||
|
|
||||||
|
override fun checkClientTrusted(chain: Array<out X509Certificate>, authType: String?) =
|
||||||
|
throw CertificateException("Unsupported operation")
|
||||||
|
|
||||||
|
override fun checkClientTrusted(
|
||||||
|
chain: Array<out X509Certificate>,
|
||||||
|
authType: String,
|
||||||
|
engine: SSLEngine?
|
||||||
|
) = throw CertificateException("Unsupported operation")
|
||||||
|
|
||||||
|
override fun checkClientTrusted(
|
||||||
|
chain: Array<out X509Certificate>,
|
||||||
|
authType: String,
|
||||||
|
socket: Socket?
|
||||||
|
) = throw CertificateException("Unsupported operation")
|
||||||
|
}
|
||||||
@@ -22,10 +22,13 @@ import java.security.cert.Certificate
|
|||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
import javax.net.ssl.KeyManagerFactory
|
import javax.net.ssl.KeyManagerFactory
|
||||||
import javax.net.ssl.TrustManagerFactory
|
import javax.net.ssl.TrustManagerFactory
|
||||||
|
import javax.net.ssl.X509ExtendedTrustManager
|
||||||
import javax.net.ssl.X509KeyManager
|
import javax.net.ssl.X509KeyManager
|
||||||
import javax.net.ssl.X509TrustManager
|
import javax.net.ssl.X509TrustManager
|
||||||
|
import okhttp3.internal.platform.AndroidPlatform
|
||||||
import okhttp3.tls.HandshakeCertificates
|
import okhttp3.tls.HandshakeCertificates
|
||||||
import okhttp3.tls.HeldCertificate
|
import okhttp3.tls.HeldCertificate
|
||||||
|
import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
|
||||||
|
|
||||||
object TlsUtil {
|
object TlsUtil {
|
||||||
val password = "password".toCharArray()
|
val password = "password".toCharArray()
|
||||||
@@ -47,10 +50,11 @@ object TlsUtil {
|
|||||||
fun localhost(): HandshakeCertificates = localhost
|
fun localhost(): HandshakeCertificates = localhost
|
||||||
|
|
||||||
/** Returns a trust manager that trusts `trustedCertificates`. */
|
/** Returns a trust manager that trusts `trustedCertificates`. */
|
||||||
@JvmStatic
|
@JvmStatic @IgnoreJRERequirement
|
||||||
fun newTrustManager(
|
fun newTrustManager(
|
||||||
keyStoreType: String?,
|
keyStoreType: String?,
|
||||||
trustedCertificates: List<X509Certificate>
|
trustedCertificates: List<X509Certificate>,
|
||||||
|
insecureHosts: List<String>
|
||||||
): X509TrustManager {
|
): X509TrustManager {
|
||||||
val trustStore = newEmptyKeyStore(keyStoreType)
|
val trustStore = newEmptyKeyStore(keyStoreType)
|
||||||
for (i in trustedCertificates.indices) {
|
for (i in trustedCertificates.indices) {
|
||||||
@@ -64,7 +68,13 @@ object TlsUtil {
|
|||||||
"Unexpected trust managers: ${result.contentToString()}"
|
"Unexpected trust managers: ${result.contentToString()}"
|
||||||
}
|
}
|
||||||
|
|
||||||
return result[0] as X509TrustManager
|
val trustManager = result[0] as X509TrustManager
|
||||||
|
|
||||||
|
return when {
|
||||||
|
insecureHosts.isEmpty() -> trustManager
|
||||||
|
AndroidPlatform.isAndroid -> InsecureAndroidTrustManager(trustManager, insecureHosts)
|
||||||
|
else -> InsecureExtendedTrustManager(trustManager as X509ExtendedTrustManager, insecureHosts)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
114
okhttp/src/test/java/okhttp3/InsecureForHostTest.kt
Normal file
114
okhttp/src/test/java/okhttp3/InsecureForHostTest.kt
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLException
|
||||||
|
import okhttp3.mockwebserver.MockResponse
|
||||||
|
import okhttp3.mockwebserver.MockWebServer
|
||||||
|
import okhttp3.testing.PlatformRule
|
||||||
|
import okhttp3.tls.HandshakeCertificates
|
||||||
|
import okhttp3.tls.HeldCertificate
|
||||||
|
import okhttp3.tls.internal.TlsUtil.localhost
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.junit.Assert.fail
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class InsecureForHostTest {
|
||||||
|
@JvmField @Rule val platform = PlatformRule()
|
||||||
|
@JvmField @Rule val server = MockWebServer()
|
||||||
|
@JvmField @Rule val clientTestRule = OkHttpClientTestRule()
|
||||||
|
|
||||||
|
@Test fun `untrusted host in insecureHosts connects successfully`() {
|
||||||
|
val serverCertificates = localhost()
|
||||||
|
server.useHttps(serverCertificates.sslSocketFactory(), false)
|
||||||
|
server.enqueue(MockResponse())
|
||||||
|
|
||||||
|
val clientCertificates = HandshakeCertificates.Builder()
|
||||||
|
.addPlatformTrustedCertificates()
|
||||||
|
.addInsecureHost(server.hostName)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val client = clientTestRule.newClientBuilder()
|
||||||
|
.sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val call = client.newCall(Request.Builder()
|
||||||
|
.url(server.url("/"))
|
||||||
|
.build())
|
||||||
|
val response = call.execute()
|
||||||
|
assertThat(response.code).isEqualTo(200)
|
||||||
|
assertThat(response.handshake!!.cipherSuite).isNotNull()
|
||||||
|
assertThat(response.handshake!!.tlsVersion).isNotNull()
|
||||||
|
assertThat(response.handshake!!.localCertificates).isEmpty()
|
||||||
|
assertThat(response.handshake!!.localPrincipal).isNull()
|
||||||
|
assertThat(response.handshake!!.peerCertificates).isEmpty()
|
||||||
|
assertThat(response.handshake!!.peerPrincipal).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test fun `bad certificates host in insecureHosts fails with SSLException`() {
|
||||||
|
val heldCertificate = HeldCertificate.Builder()
|
||||||
|
.addSubjectAlternativeName("example.com")
|
||||||
|
.build()
|
||||||
|
val serverCertificates = HandshakeCertificates.Builder()
|
||||||
|
.heldCertificate(heldCertificate)
|
||||||
|
.build()
|
||||||
|
server.useHttps(serverCertificates.sslSocketFactory(), false)
|
||||||
|
server.enqueue(MockResponse())
|
||||||
|
|
||||||
|
val clientCertificates = HandshakeCertificates.Builder()
|
||||||
|
.addPlatformTrustedCertificates()
|
||||||
|
.addInsecureHost(server.hostName)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val client = clientTestRule.newClientBuilder()
|
||||||
|
.sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val call = client.newCall(Request.Builder()
|
||||||
|
.url(server.url("/"))
|
||||||
|
.build())
|
||||||
|
try {
|
||||||
|
call.execute()
|
||||||
|
fail()
|
||||||
|
} catch (expected: SSLException) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test fun `untrusted host not in insecureHosts fails with SSLException`() {
|
||||||
|
val serverCertificates = localhost()
|
||||||
|
server.useHttps(serverCertificates.sslSocketFactory(), false)
|
||||||
|
server.enqueue(MockResponse())
|
||||||
|
|
||||||
|
val clientCertificates = HandshakeCertificates.Builder()
|
||||||
|
.addPlatformTrustedCertificates()
|
||||||
|
.addInsecureHost("${server.hostName}2")
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val client = clientTestRule.newClientBuilder()
|
||||||
|
.sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val call = client.newCall(Request.Builder()
|
||||||
|
.url(server.url("/"))
|
||||||
|
.build())
|
||||||
|
try {
|
||||||
|
call.execute()
|
||||||
|
fail()
|
||||||
|
} catch (expected: SSLException) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -355,7 +355,8 @@ public final class CertificatePinnerChainValidationTest {
|
|||||||
// http://hg.openjdk.java.net/jdk9/jdk9/jdk/file/2c1c21d11e58/src/share/classes/sun/security/pkcs12/PKCS12KeyStore.java#l596
|
// http://hg.openjdk.java.net/jdk9/jdk9/jdk/file/2c1c21d11e58/src/share/classes/sun/security/pkcs12/PKCS12KeyStore.java#l596
|
||||||
String keystoreType = platform.isJdk9() ? "JKS" : null;
|
String keystoreType = platform.isJdk9() ? "JKS" : null;
|
||||||
X509KeyManager x509KeyManager = newKeyManager(keystoreType, heldCertificate, intermediates);
|
X509KeyManager x509KeyManager = newKeyManager(keystoreType, heldCertificate, intermediates);
|
||||||
X509TrustManager trustManager = newTrustManager(keystoreType, Collections.emptyList());
|
X509TrustManager trustManager = newTrustManager(
|
||||||
|
keystoreType, Collections.emptyList(), Collections.emptyList());
|
||||||
SSLContext sslContext = Platform.get().newSSLContext();
|
SSLContext sslContext = Platform.get().newSSLContext();
|
||||||
sslContext.init(new KeyManager[] {x509KeyManager}, new TrustManager[] {trustManager},
|
sslContext.init(new KeyManager[] {x509KeyManager}, new TrustManager[] {trustManager},
|
||||||
new SecureRandom());
|
new SecureRandom());
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import java.net.SocketException;
|
|||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Collections;
|
||||||
import javax.net.ssl.KeyManager;
|
import javax.net.ssl.KeyManager;
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
import javax.net.ssl.SSLException;
|
import javax.net.ssl.SSLException;
|
||||||
@@ -286,8 +287,8 @@ public final class ClientAuthTest {
|
|||||||
try {
|
try {
|
||||||
X509KeyManager keyManager = newKeyManager(
|
X509KeyManager keyManager = newKeyManager(
|
||||||
null, serverCert, serverIntermediateCa.certificate());
|
null, serverCert, serverIntermediateCa.certificate());
|
||||||
X509TrustManager trustManager = newTrustManager(
|
X509TrustManager trustManager = newTrustManager(null,
|
||||||
null, asList(serverRootCa.certificate(), clientRootCa.certificate()));
|
asList(serverRootCa.certificate(), clientRootCa.certificate()), Collections.emptyList());
|
||||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||||
sslContext.init(new KeyManager[] {keyManager}, new TrustManager[] {trustManager},
|
sslContext.init(new KeyManager[] {keyManager}, new TrustManager[] {trustManager},
|
||||||
new SecureRandom());
|
new SecureRandom());
|
||||||
|
|||||||
66
samples/guide/src/main/java/okhttp3/recipes/kt/DevServer.kt
Normal file
66
samples/guide/src/main/java/okhttp3/recipes/kt/DevServer.kt
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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.recipes.kt
|
||||||
|
|
||||||
|
import java.io.IOException
|
||||||
|
import java.net.HttpURLConnection.HTTP_MOVED_TEMP
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.mockwebserver.MockResponse
|
||||||
|
import okhttp3.mockwebserver.MockWebServer
|
||||||
|
import okhttp3.tls.HandshakeCertificates
|
||||||
|
import okhttp3.tls.internal.TlsUtil
|
||||||
|
|
||||||
|
class DevServer {
|
||||||
|
val handshakeCertificates = TlsUtil.localhost()
|
||||||
|
|
||||||
|
val server = MockWebServer().apply {
|
||||||
|
useHttps(handshakeCertificates.sslSocketFactory(), false)
|
||||||
|
|
||||||
|
enqueue(MockResponse()
|
||||||
|
.setResponseCode(HTTP_MOVED_TEMP)
|
||||||
|
.setHeader("Location", "https://www.google.com/robots.txt"))
|
||||||
|
}
|
||||||
|
|
||||||
|
val clientCertificates = HandshakeCertificates.Builder()
|
||||||
|
.addPlatformTrustedCertificates()
|
||||||
|
.addInsecureHost(server.hostName)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val client = OkHttpClient.Builder()
|
||||||
|
.sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
fun run() {
|
||||||
|
try {
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url(server.url("/"))
|
||||||
|
.build()
|
||||||
|
|
||||||
|
client.newCall(request).execute().use { response ->
|
||||||
|
if (!response.isSuccessful) throw IOException("Unexpected code $response")
|
||||||
|
|
||||||
|
println(response.request.url)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
server.shutdown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
DevServer().run()
|
||||||
|
}
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2020 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.recipes.kt
|
|
||||||
|
|
||||||
import java.io.IOException
|
|
||||||
import java.lang.reflect.InvocationTargetException
|
|
||||||
import java.lang.reflect.Method
|
|
||||||
import java.net.HttpURLConnection.HTTP_MOVED_TEMP
|
|
||||||
import java.security.KeyStore
|
|
||||||
import java.security.cert.Certificate
|
|
||||||
import java.security.cert.CertificateException
|
|
||||||
import java.security.cert.X509Certificate
|
|
||||||
import javax.net.ssl.TrustManagerFactory
|
|
||||||
import javax.net.ssl.X509TrustManager
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.Request
|
|
||||||
import okhttp3.internal.platform.Platform
|
|
||||||
import okhttp3.mockwebserver.MockResponse
|
|
||||||
import okhttp3.mockwebserver.MockWebServer
|
|
||||||
import okhttp3.tls.internal.TlsUtil
|
|
||||||
|
|
||||||
class AndroidAllowlistedTrustManager(
|
|
||||||
private val delegate: X509TrustManager,
|
|
||||||
private vararg val hosts: String
|
|
||||||
) : X509TrustManager {
|
|
||||||
val delegateMethod = lookupDelegateMethod()
|
|
||||||
|
|
||||||
override fun checkClientTrusted(chain: Array<out X509Certificate>, authType: String?) {
|
|
||||||
delegate.checkClientTrusted(chain, authType)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun checkServerTrusted(chain: Array<out X509Certificate>, authType: String) {
|
|
||||||
throw CertificateException("Unsupported operation")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Android method to clean and sort certificates, called via reflection.
|
|
||||||
*/
|
|
||||||
fun checkServerTrusted(
|
|
||||||
chain: Array<out X509Certificate>,
|
|
||||||
authType: String,
|
|
||||||
host: String
|
|
||||||
): List<Certificate> {
|
|
||||||
|
|
||||||
if (isAllowed(host)) {
|
|
||||||
println("Skipping security checks for $host")
|
|
||||||
println(chain.map { it.subjectDN.name })
|
|
||||||
|
|
||||||
return listOf()
|
|
||||||
}
|
|
||||||
|
|
||||||
println("Running security checks for $host")
|
|
||||||
println(chain.map { it.subjectDN.name }.take(1))
|
|
||||||
|
|
||||||
if (delegateMethod != null) {
|
|
||||||
return invokeDelegateMethod(delegateMethod, chain, authType, host)
|
|
||||||
}
|
|
||||||
|
|
||||||
throw CertificateException("Failed to call checkServerTrusted")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isAllowed(host: String): Boolean = hosts.contains(host)
|
|
||||||
|
|
||||||
override fun getAcceptedIssuers(): Array<X509Certificate> = delegate.acceptedIssuers
|
|
||||||
|
|
||||||
private fun lookupDelegateMethod(): Method? {
|
|
||||||
return try {
|
|
||||||
delegate.javaClass.getMethod("checkServerTrusted",
|
|
||||||
Array<X509Certificate>::class.java, String::class.java, String::class.java)
|
|
||||||
} catch (nsme: NoSuchMethodException) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
private fun invokeDelegateMethod(
|
|
||||||
delegateMethod: Method,
|
|
||||||
chain: Array<out X509Certificate>,
|
|
||||||
authType: String,
|
|
||||||
host: String
|
|
||||||
): List<Certificate> {
|
|
||||||
try {
|
|
||||||
return delegateMethod.invoke(delegate, chain, authType, host) as List<Certificate>
|
|
||||||
} catch (ite: InvocationTargetException) {
|
|
||||||
throw ite.targetException
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DevServerAndroid {
|
|
||||||
val handshakeCertificates = TlsUtil.localhost()
|
|
||||||
|
|
||||||
val server = MockWebServer().apply {
|
|
||||||
useHttps(handshakeCertificates.sslSocketFactory(), false)
|
|
||||||
|
|
||||||
enqueue(MockResponse()
|
|
||||||
.setResponseCode(HTTP_MOVED_TEMP)
|
|
||||||
.setHeader("Location", "https://www.google.com/robots.txt"))
|
|
||||||
}
|
|
||||||
|
|
||||||
val hosts = arrayOf(server.hostName)
|
|
||||||
|
|
||||||
val platformTrustManager = platformTrustManager()
|
|
||||||
val trustManager = AndroidAllowlistedTrustManager(platformTrustManager, *hosts)
|
|
||||||
val sslSocketFactory = Platform.get().newSSLContext().apply {
|
|
||||||
init(null, arrayOf(trustManager), null)
|
|
||||||
}.socketFactory
|
|
||||||
|
|
||||||
val client = OkHttpClient.Builder()
|
|
||||||
.sslSocketFactory(sslSocketFactory, trustManager)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
fun platformTrustManager(): X509TrustManager {
|
|
||||||
val factory = TrustManagerFactory.getInstance(
|
|
||||||
TrustManagerFactory.getDefaultAlgorithm())
|
|
||||||
factory.init(null as KeyStore?)
|
|
||||||
return factory.trustManagers!![0] as X509TrustManager
|
|
||||||
}
|
|
||||||
|
|
||||||
fun run() {
|
|
||||||
try {
|
|
||||||
val request = Request.Builder()
|
|
||||||
.url(server.url("/"))
|
|
||||||
.build()
|
|
||||||
|
|
||||||
client.newCall(request).execute().use { response ->
|
|
||||||
if (!response.isSuccessful) throw IOException("Unexpected code $response")
|
|
||||||
|
|
||||||
println(response.request.url)
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
server.shutdown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun main() {
|
|
||||||
DevServerAndroid().run()
|
|
||||||
}
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2020 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.recipes.kt
|
|
||||||
|
|
||||||
import java.io.IOException
|
|
||||||
import java.net.HttpURLConnection.HTTP_MOVED_TEMP
|
|
||||||
import java.net.Socket
|
|
||||||
import java.security.KeyStore
|
|
||||||
import java.security.cert.CertificateException
|
|
||||||
import java.security.cert.X509Certificate
|
|
||||||
import javax.net.ssl.SSLEngine
|
|
||||||
import javax.net.ssl.TrustManagerFactory
|
|
||||||
import javax.net.ssl.X509ExtendedTrustManager
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.Request
|
|
||||||
import okhttp3.internal.platform.Platform
|
|
||||||
import okhttp3.mockwebserver.MockResponse
|
|
||||||
import okhttp3.mockwebserver.MockWebServer
|
|
||||||
import okhttp3.tls.internal.TlsUtil
|
|
||||||
import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
|
|
||||||
|
|
||||||
@IgnoreJRERequirement
|
|
||||||
class JvmAllowlistedTrustManager(
|
|
||||||
private val delegate: X509ExtendedTrustManager,
|
|
||||||
private vararg val hosts: String
|
|
||||||
) : X509ExtendedTrustManager() {
|
|
||||||
override fun checkClientTrusted(chain: Array<out X509Certificate>, authType: String?) {
|
|
||||||
throw CertificateException("Unsupported client operation")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun checkClientTrusted(
|
|
||||||
chain: Array<out X509Certificate>?,
|
|
||||||
authType: String?,
|
|
||||||
engine: SSLEngine?
|
|
||||||
) {
|
|
||||||
throw CertificateException("Unsupported client operation")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun checkClientTrusted(
|
|
||||||
chain: Array<out X509Certificate>?,
|
|
||||||
authType: String?,
|
|
||||||
socket: Socket?
|
|
||||||
) {
|
|
||||||
throw CertificateException("Unsupported client operation")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun checkServerTrusted(chain: Array<out X509Certificate>, authType: String) {
|
|
||||||
throw CertificateException("Unsupported operation")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun checkServerTrusted(
|
|
||||||
chain: Array<out X509Certificate>,
|
|
||||||
authType: String,
|
|
||||||
socket: Socket
|
|
||||||
) {
|
|
||||||
val host = socket.inetAddress.hostName
|
|
||||||
|
|
||||||
if (isAllowed(host)) {
|
|
||||||
println("Skipping security checks for $host")
|
|
||||||
println(chain.map { it.subjectDN.name })
|
|
||||||
} else {
|
|
||||||
println("Running security checks for $host")
|
|
||||||
println(chain.map { it.subjectDN.name }.take(1))
|
|
||||||
delegate.checkServerTrusted(chain, authType, socket)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun checkServerTrusted(
|
|
||||||
chain: Array<out X509Certificate>,
|
|
||||||
authType: String,
|
|
||||||
engine: SSLEngine
|
|
||||||
) {
|
|
||||||
val host = engine.peerHost
|
|
||||||
|
|
||||||
if (isAllowed(host)) {
|
|
||||||
println("Skipping security checks for $host")
|
|
||||||
println(chain.map { it.subjectDN.name })
|
|
||||||
} else {
|
|
||||||
println("Running security checks for $host")
|
|
||||||
println(chain.map { it.subjectDN.name }.take(1))
|
|
||||||
|
|
||||||
delegate.checkServerTrusted(chain, authType, engine)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isAllowed(host: String): Boolean = hosts.contains(host)
|
|
||||||
|
|
||||||
override fun getAcceptedIssuers(): Array<X509Certificate> = delegate.acceptedIssuers
|
|
||||||
}
|
|
||||||
|
|
||||||
@IgnoreJRERequirement
|
|
||||||
class DevServerJvm {
|
|
||||||
val handshakeCertificates = TlsUtil.localhost()
|
|
||||||
|
|
||||||
val server = MockWebServer().apply {
|
|
||||||
useHttps(handshakeCertificates.sslSocketFactory(), false)
|
|
||||||
|
|
||||||
enqueue(MockResponse()
|
|
||||||
.setResponseCode(HTTP_MOVED_TEMP)
|
|
||||||
.setHeader("Location", "https://www.google.com/robots.txt"))
|
|
||||||
}
|
|
||||||
|
|
||||||
val hosts = arrayOf(server.hostName)
|
|
||||||
|
|
||||||
val platformTrustManager = platformTrustManager()
|
|
||||||
val trustManager = JvmAllowlistedTrustManager(platformTrustManager, *hosts)
|
|
||||||
val sslSocketFactory = Platform.get().newSSLContext().apply {
|
|
||||||
init(null, arrayOf(trustManager), null)
|
|
||||||
}.socketFactory
|
|
||||||
|
|
||||||
val client = OkHttpClient.Builder()
|
|
||||||
.sslSocketFactory(sslSocketFactory, trustManager)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
fun platformTrustManager(): X509ExtendedTrustManager {
|
|
||||||
val factory = TrustManagerFactory.getInstance(
|
|
||||||
TrustManagerFactory.getDefaultAlgorithm())
|
|
||||||
factory.init(null as KeyStore?)
|
|
||||||
return factory.trustManagers!![0] as X509ExtendedTrustManager
|
|
||||||
}
|
|
||||||
|
|
||||||
fun run() {
|
|
||||||
try {
|
|
||||||
val request = Request.Builder()
|
|
||||||
.url(server.url("/"))
|
|
||||||
.build()
|
|
||||||
|
|
||||||
client.newCall(request).execute().use { response ->
|
|
||||||
if (!response.isSuccessful) throw IOException("Unexpected code $response")
|
|
||||||
|
|
||||||
println(response.request.url)
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
server.shutdown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun main() {
|
|
||||||
DevServerJvm().run()
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user