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.bouncycastletls
|
||||
compileOnly deps.jsr305
|
||||
compileOnly deps.animalSniffer
|
||||
|
||||
testImplementation project(':okhttp-testing-support')
|
||||
testImplementation deps.junit
|
||||
|
||||
@@ -18,6 +18,7 @@ package okhttp3.tls
|
||||
import java.security.SecureRandom
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.Collections
|
||||
import javax.net.ssl.HostnameVerifier
|
||||
import javax.net.ssl.KeyManager
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.SSLSocketFactory
|
||||
@@ -26,6 +27,7 @@ import javax.net.ssl.X509KeyManager
|
||||
import javax.net.ssl.X509TrustManager
|
||||
import okhttp3.CertificatePinner
|
||||
import okhttp3.internal.platform.Platform
|
||||
import okhttp3.internal.toImmutableList
|
||||
import okhttp3.tls.internal.TlsUtil.newKeyManager
|
||||
import okhttp3.tls.internal.TlsUtil.newTrustManager
|
||||
|
||||
@@ -97,6 +99,7 @@ class HandshakeCertificates private constructor(
|
||||
private var heldCertificate: HeldCertificate? = null
|
||||
private var intermediates: Array<X509Certificate>? = null
|
||||
private val trustedCertificates = mutableListOf<X509Certificate>()
|
||||
private val insecureHosts = mutableListOf<String>()
|
||||
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
val immutableInsecureHosts = insecureHosts.toImmutableList()
|
||||
val keyManager = newKeyManager(null, heldCertificate, *(intermediates ?: emptyArray()))
|
||||
val trustManager = newTrustManager(null, trustedCertificates)
|
||||
val trustManager = newTrustManager(null, trustedCertificates, immutableInsecureHosts)
|
||||
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 javax.net.ssl.KeyManagerFactory
|
||||
import javax.net.ssl.TrustManagerFactory
|
||||
import javax.net.ssl.X509ExtendedTrustManager
|
||||
import javax.net.ssl.X509KeyManager
|
||||
import javax.net.ssl.X509TrustManager
|
||||
import okhttp3.internal.platform.AndroidPlatform
|
||||
import okhttp3.tls.HandshakeCertificates
|
||||
import okhttp3.tls.HeldCertificate
|
||||
import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
|
||||
|
||||
object TlsUtil {
|
||||
val password = "password".toCharArray()
|
||||
@@ -47,10 +50,11 @@ object TlsUtil {
|
||||
fun localhost(): HandshakeCertificates = localhost
|
||||
|
||||
/** Returns a trust manager that trusts `trustedCertificates`. */
|
||||
@JvmStatic
|
||||
@JvmStatic @IgnoreJRERequirement
|
||||
fun newTrustManager(
|
||||
keyStoreType: String?,
|
||||
trustedCertificates: List<X509Certificate>
|
||||
trustedCertificates: List<X509Certificate>,
|
||||
insecureHosts: List<String>
|
||||
): X509TrustManager {
|
||||
val trustStore = newEmptyKeyStore(keyStoreType)
|
||||
for (i in trustedCertificates.indices) {
|
||||
@@ -64,7 +68,13 @@ object TlsUtil {
|
||||
"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
|
||||
String keystoreType = platform.isJdk9() ? "JKS" : null;
|
||||
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.init(new KeyManager[] {x509KeyManager}, new TrustManager[] {trustManager},
|
||||
new SecureRandom());
|
||||
|
||||
@@ -19,6 +19,7 @@ import java.net.SocketException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collections;
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLException;
|
||||
@@ -286,8 +287,8 @@ public final class ClientAuthTest {
|
||||
try {
|
||||
X509KeyManager keyManager = newKeyManager(
|
||||
null, serverCert, serverIntermediateCa.certificate());
|
||||
X509TrustManager trustManager = newTrustManager(
|
||||
null, asList(serverRootCa.certificate(), clientRootCa.certificate()));
|
||||
X509TrustManager trustManager = newTrustManager(null,
|
||||
asList(serverRootCa.certificate(), clientRootCa.certificate()), Collections.emptyList());
|
||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||
sslContext.init(new KeyManager[] {keyManager}, new TrustManager[] {trustManager},
|
||||
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