1
0
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:
Jesse Wilson
2020-05-09 22:04:50 -04:00
parent e31cb03d86
commit 1364ea44ae
11 changed files with 368 additions and 313 deletions

View File

@@ -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

View File

@@ -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 servers 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 servers 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)
} }
} }

View File

@@ -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")
}

View File

@@ -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")
}

View File

@@ -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)
}
} }
/** /**

View 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) {
}
}
}

View File

@@ -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());

View File

@@ -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());

View 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()
}

View File

@@ -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()
}

View File

@@ -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()
}