mirror of
https://github.com/square/okhttp.git
synced 2025-08-08 23:42:08 +03:00
Assert on effective cipher suite order (#6410)
This commit is contained in:
@@ -311,6 +311,10 @@ open class PlatformRule @JvmOverloads constructor(
|
|||||||
assumeThat(Platform.isAndroid).isFalse
|
assumeThat(Platform.isAndroid).isFalse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun assumeJdkVersion(majorVersion: Int) {
|
||||||
|
assumeThat(PlatformVersion.majorVersion).isEqualTo(majorVersion)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val PROPERTY_NAME = "okhttp.platform"
|
const val PROPERTY_NAME = "okhttp.platform"
|
||||||
const val CONSCRYPT_PROPERTY = "conscrypt"
|
const val CONSCRYPT_PROPERTY = "conscrypt"
|
||||||
|
@@ -20,6 +20,7 @@ import java.util.Objects
|
|||||||
import javax.net.ssl.SSLSocket
|
import javax.net.ssl.SSLSocket
|
||||||
import okhttp3.ConnectionSpec.Builder
|
import okhttp3.ConnectionSpec.Builder
|
||||||
import okhttp3.internal.concat
|
import okhttp3.internal.concat
|
||||||
|
import okhttp3.internal.effectiveCipherSuites
|
||||||
import okhttp3.internal.hasIntersection
|
import okhttp3.internal.hasIntersection
|
||||||
import okhttp3.internal.indexOf
|
import okhttp3.internal.indexOf
|
||||||
import okhttp3.internal.intersect
|
import okhttp3.internal.intersect
|
||||||
@@ -46,7 +47,7 @@ import okhttp3.internal.intersect
|
|||||||
class ConnectionSpec internal constructor(
|
class ConnectionSpec internal constructor(
|
||||||
@get:JvmName("isTls") val isTls: Boolean,
|
@get:JvmName("isTls") val isTls: Boolean,
|
||||||
@get:JvmName("supportsTlsExtensions") val supportsTlsExtensions: Boolean,
|
@get:JvmName("supportsTlsExtensions") val supportsTlsExtensions: Boolean,
|
||||||
private val cipherSuitesAsString: Array<String>?,
|
internal val cipherSuitesAsString: Array<String>?,
|
||||||
private val tlsVersionsAsString: Array<String>?
|
private val tlsVersionsAsString: Array<String>?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@@ -106,11 +107,8 @@ class ConnectionSpec internal constructor(
|
|||||||
* Returns a copy of this that omits cipher suites and TLS versions not enabled by [sslSocket].
|
* Returns a copy of this that omits cipher suites and TLS versions not enabled by [sslSocket].
|
||||||
*/
|
*/
|
||||||
private fun supportedSpec(sslSocket: SSLSocket, isFallback: Boolean): ConnectionSpec {
|
private fun supportedSpec(sslSocket: SSLSocket, isFallback: Boolean): ConnectionSpec {
|
||||||
var cipherSuitesIntersection = if (cipherSuitesAsString != null) {
|
val socketEnabledCipherSuites = sslSocket.enabledCipherSuites
|
||||||
sslSocket.enabledCipherSuites.intersect(cipherSuitesAsString, CipherSuite.ORDER_BY_NAME)
|
var cipherSuitesIntersection: Array<String> = effectiveCipherSuites(socketEnabledCipherSuites)
|
||||||
} else {
|
|
||||||
sslSocket.enabledCipherSuites
|
|
||||||
}
|
|
||||||
|
|
||||||
val tlsVersionsIntersection = if (tlsVersionsAsString != null) {
|
val tlsVersionsIntersection = if (tlsVersionsAsString != null) {
|
||||||
sslSocket.enabledProtocols.intersect(tlsVersionsAsString, naturalOrder())
|
sslSocket.enabledProtocols.intersect(tlsVersionsAsString, naturalOrder())
|
||||||
|
@@ -21,6 +21,7 @@ package okhttp3.internal
|
|||||||
|
|
||||||
import javax.net.ssl.SSLSocket
|
import javax.net.ssl.SSLSocket
|
||||||
import okhttp3.Cache
|
import okhttp3.Cache
|
||||||
|
import okhttp3.CipherSuite
|
||||||
import okhttp3.ConnectionSpec
|
import okhttp3.ConnectionSpec
|
||||||
import okhttp3.Cookie
|
import okhttp3.Cookie
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
@@ -43,3 +44,11 @@ fun cacheGet(cache: Cache, request: Request) = cache.get(request)
|
|||||||
|
|
||||||
fun applyConnectionSpec(connectionSpec: ConnectionSpec, sslSocket: SSLSocket, isFallback: Boolean) =
|
fun applyConnectionSpec(connectionSpec: ConnectionSpec, sslSocket: SSLSocket, isFallback: Boolean) =
|
||||||
connectionSpec.apply(sslSocket, isFallback)
|
connectionSpec.apply(sslSocket, isFallback)
|
||||||
|
|
||||||
|
fun ConnectionSpec.effectiveCipherSuites(socketEnabledCipherSuites: Array<String>): Array<String> {
|
||||||
|
return if (cipherSuitesAsString != null) {
|
||||||
|
socketEnabledCipherSuites.intersect(cipherSuitesAsString, CipherSuite.ORDER_BY_NAME)
|
||||||
|
} else {
|
||||||
|
socketEnabledCipherSuites
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -19,8 +19,16 @@ import mockwebserver3.MockResponse
|
|||||||
import mockwebserver3.MockWebServer
|
import mockwebserver3.MockWebServer
|
||||||
import okhttp3.CipherSuite.Companion.TLS_AES_128_GCM_SHA256
|
import okhttp3.CipherSuite.Companion.TLS_AES_128_GCM_SHA256
|
||||||
import okhttp3.CipherSuite.Companion.TLS_AES_256_GCM_SHA384
|
import okhttp3.CipherSuite.Companion.TLS_AES_256_GCM_SHA384
|
||||||
|
import okhttp3.CipherSuite.Companion.TLS_CHACHA20_POLY1305_SHA256
|
||||||
|
import okhttp3.CipherSuite.Companion.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
|
||||||
import okhttp3.CipherSuite.Companion.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
|
import okhttp3.CipherSuite.Companion.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
|
||||||
import okhttp3.CipherSuite.Companion.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
|
import okhttp3.CipherSuite.Companion.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
|
||||||
|
import okhttp3.CipherSuite.Companion.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
|
||||||
|
import okhttp3.CipherSuite.Companion.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
|
||||||
|
import okhttp3.CipherSuite.Companion.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
|
||||||
|
import okhttp3.CipherSuite.Companion.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
|
||||||
|
import okhttp3.internal.effectiveCipherSuites
|
||||||
|
import okhttp3.internal.platform.Platform
|
||||||
import okhttp3.testing.PlatformRule
|
import okhttp3.testing.PlatformRule
|
||||||
import okhttp3.tls.internal.TlsUtil.localhost
|
import okhttp3.tls.internal.TlsUtil.localhost
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
@@ -46,7 +54,8 @@ class CallHandshakeTest {
|
|||||||
private lateinit var defaultEnabledCipherSuites: List<String>
|
private lateinit var defaultEnabledCipherSuites: List<String>
|
||||||
private lateinit var defaultSupportedCipherSuites: List<String>
|
private lateinit var defaultSupportedCipherSuites: List<String>
|
||||||
|
|
||||||
val expectedModernTls12CipherSuites = listOf(TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384)
|
val expectedModernTls12CipherSuites =
|
||||||
|
listOf(TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384)
|
||||||
val expectedModernTls13CipherSuites = listOf(TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384)
|
val expectedModernTls13CipherSuites = listOf(TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384)
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
@@ -62,8 +71,10 @@ class CallHandshakeTest {
|
|||||||
.build()
|
.build()
|
||||||
server.useHttps(handshakeCertificates.sslSocketFactory(), false)
|
server.useHttps(handshakeCertificates.sslSocketFactory(), false)
|
||||||
|
|
||||||
defaultEnabledCipherSuites = handshakeCertificates.sslSocketFactory().defaultCipherSuites.toList()
|
defaultEnabledCipherSuites =
|
||||||
defaultSupportedCipherSuites = handshakeCertificates.sslSocketFactory().supportedCipherSuites.toList()
|
handshakeCertificates.sslSocketFactory().defaultCipherSuites.toList()
|
||||||
|
defaultSupportedCipherSuites =
|
||||||
|
handshakeCertificates.sslSocketFactory().supportedCipherSuites.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -81,7 +92,8 @@ class CallHandshakeTest {
|
|||||||
// TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
|
// TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
|
||||||
// TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
|
// TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
|
||||||
// TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
|
// TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
|
||||||
assertThat(handshakeEnabledCipherSuites).containsExactlyElementsOf(expectedConnectionCipherSuites(client))
|
assertThat(handshakeEnabledCipherSuites).containsExactlyElementsOf(
|
||||||
|
expectedConnectionCipherSuites(client))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -105,7 +117,8 @@ class CallHandshakeTest {
|
|||||||
// TLS_RSA_WITH_AES_256_CBC_SHA
|
// TLS_RSA_WITH_AES_256_CBC_SHA
|
||||||
// TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
|
// TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
|
||||||
// TLS_RSA_WITH_AES_128_CBC_SHA
|
// TLS_RSA_WITH_AES_128_CBC_SHA
|
||||||
assertThat(handshakeEnabledCipherSuites).containsExactlyElementsOf(expectedConnectionCipherSuites(client))
|
assertThat(handshakeEnabledCipherSuites).containsExactlyElementsOf(
|
||||||
|
expectedConnectionCipherSuites(client))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -133,12 +146,14 @@ class CallHandshakeTest {
|
|||||||
// TLS_RSA_WITH_AES_256_CBC_SHA
|
// TLS_RSA_WITH_AES_256_CBC_SHA
|
||||||
// TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
|
// TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
|
||||||
// TLS_RSA_WITH_AES_128_CBC_SHA
|
// TLS_RSA_WITH_AES_128_CBC_SHA
|
||||||
assertThat(handshakeEnabledCipherSuites).containsExactlyElementsOf(expectedConnectionCipherSuites(client))
|
assertThat(handshakeEnabledCipherSuites).containsExactlyElementsOf(
|
||||||
|
expectedConnectionCipherSuites(client))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testHandshakeCipherSuiteOrderingWhenReversed() {
|
fun testHandshakeCipherSuiteOrderingWhenReversed() {
|
||||||
val client = makeClient(ConnectionSpec.RESTRICTED_TLS, TlsVersion.TLS_1_2, defaultEnabledCipherSuites.asReversed())
|
val client = makeClient(ConnectionSpec.RESTRICTED_TLS, TlsVersion.TLS_1_2,
|
||||||
|
defaultEnabledCipherSuites.asReversed())
|
||||||
|
|
||||||
val handshake = makeRequest(client)
|
val handshake = makeRequest(client)
|
||||||
|
|
||||||
@@ -146,7 +161,8 @@ class CallHandshakeTest {
|
|||||||
assertThat(handshake.cipherSuite).isIn(expectedModernTls12CipherSuites)
|
assertThat(handshake.cipherSuite).isIn(expectedModernTls12CipherSuites)
|
||||||
|
|
||||||
// TODO reversed ciphers
|
// TODO reversed ciphers
|
||||||
assertThat(handshakeEnabledCipherSuites).containsExactlyElementsOf(expectedConnectionCipherSuites(client))
|
assertThat(handshakeEnabledCipherSuites).containsExactlyElementsOf(
|
||||||
|
expectedConnectionCipherSuites(client))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -154,17 +170,62 @@ class CallHandshakeTest {
|
|||||||
val client = makeClient()
|
val client = makeClient()
|
||||||
makeRequest(client)
|
makeRequest(client)
|
||||||
|
|
||||||
val socketOrderedByDefaults = handshakeEnabledCipherSuites.sortedBy { defaultEnabledCipherSuites.indexOf(it) }
|
val socketOrderedByDefaults =
|
||||||
|
handshakeEnabledCipherSuites.sortedBy { defaultEnabledCipherSuites.indexOf(it) }
|
||||||
assertThat(handshakeEnabledCipherSuites).containsExactlyElementsOf(socketOrderedByDefaults)
|
assertThat(handshakeEnabledCipherSuites).containsExactlyElementsOf(socketOrderedByDefaults)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun advertisedOrderInRestricted() {
|
||||||
|
assertThat(ConnectionSpec.RESTRICTED_TLS.cipherSuites).containsExactly(
|
||||||
|
TLS_AES_128_GCM_SHA256,
|
||||||
|
TLS_AES_256_GCM_SHA384,
|
||||||
|
TLS_CHACHA20_POLY1305_SHA256,
|
||||||
|
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
|
||||||
|
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun effectiveOrderInRestrictedJdk11() {
|
||||||
|
platform.assumeJdkVersion(11)
|
||||||
|
|
||||||
|
val platform = Platform.get()
|
||||||
|
val platformDefaultCipherSuites =
|
||||||
|
platform.newSslSocketFactory(platform.platformTrustManager()).defaultCipherSuites
|
||||||
|
val cipherSuites =
|
||||||
|
ConnectionSpec.RESTRICTED_TLS.effectiveCipherSuites(platformDefaultCipherSuites)
|
||||||
|
assertThat(cipherSuites).containsExactlyElementsOf(listOf(
|
||||||
|
TLS_AES_128_GCM_SHA256,
|
||||||
|
TLS_AES_256_GCM_SHA384,
|
||||||
|
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
|
||||||
|
// Disabled
|
||||||
|
// TLS_CHACHA20_POLY1305_SHA256,
|
||||||
|
// TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
|
||||||
|
// TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
|
||||||
|
).map { it.javaName })
|
||||||
|
}
|
||||||
|
|
||||||
private fun expectedConnectionCipherSuites(client: OkHttpClient): Set<String> {
|
private fun expectedConnectionCipherSuites(client: OkHttpClient): Set<String> {
|
||||||
// TODO correct for the client provided order
|
// TODO correct for the client provided order
|
||||||
// return client.connectionSpecs.first().cipherSuites!!.map { it.javaName }.intersect(defaultEnabledCipherSuites)
|
// return client.connectionSpecs.first().cipherSuites!!.map { it.javaName }.intersect(defaultEnabledCipherSuites)
|
||||||
return defaultEnabledCipherSuites.intersect(client.connectionSpecs.first().cipherSuites!!.map { it.javaName })
|
return defaultEnabledCipherSuites.intersect(
|
||||||
|
client.connectionSpecs.first().cipherSuites!!.map { it.javaName })
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun makeClient(connectionSpec: ConnectionSpec? = null, tlsVersion: TlsVersion? = null, cipherSuites: List<String>? = null): OkHttpClient {
|
private fun makeClient(
|
||||||
|
connectionSpec: ConnectionSpec? = null,
|
||||||
|
tlsVersion: TlsVersion? = null,
|
||||||
|
cipherSuites: List<String>? = null
|
||||||
|
): OkHttpClient {
|
||||||
return this.client.newBuilder()
|
return this.client.newBuilder()
|
||||||
.apply {
|
.apply {
|
||||||
if (connectionSpec != null) {
|
if (connectionSpec != null) {
|
||||||
|
Reference in New Issue
Block a user