Skip to content

Commit cda5e0b

Browse files
authored
feat: add enhanced tls configs (#1396)
1 parent 2645b61 commit cda5e0b

File tree

10 files changed

+484
-23
lines changed

10 files changed

+484
-23
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"id": "39ca59b7-65d6-44da-85a6-84c26ad481e7",
3+
"type": "feature",
4+
"description": "Add additional TLS configuration options to HTTP engines.",
5+
"issues": [
6+
"awslabs/smithy-kotlin#820"
7+
]
8+
}

runtime/protocol/http-client-engines/http-client-engine-crt/api/http-client-engine-crt.api

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,41 @@ public final class aws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngine$Compa
1515
public final class aws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngineConfig : aws/smithy/kotlin/runtime/http/engine/HttpClientEngineConfigImpl {
1616
public static final field Companion Laws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngineConfig$Companion;
1717
public synthetic fun <init> (Laws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngineConfig$Builder;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
18+
public final fun getCertificateFile ()Ljava/lang/String;
19+
public final fun getCertificatePem ()Ljava/lang/String;
20+
public final fun getCertificatesDirectory ()Ljava/lang/String;
1821
public final fun getClientBootstrap ()Laws/sdk/kotlin/crt/io/ClientBootstrap;
1922
public final fun getInitialWindowSizeBytes ()I
2023
public final fun getMaxConnections-pVg5ArA ()I
24+
public final fun getTlsCipherPreference ()Laws/sdk/kotlin/crt/io/TlsCipherPreference;
25+
public final fun getVerifyPeer ()Z
26+
public final fun setCertificateFile (Ljava/lang/String;)V
27+
public final fun setCertificatePem (Ljava/lang/String;)V
28+
public final fun setCertificatesDirectory (Ljava/lang/String;)V
2129
public final fun setClientBootstrap (Laws/sdk/kotlin/crt/io/ClientBootstrap;)V
30+
public final fun setTlsCipherPreference (Laws/sdk/kotlin/crt/io/TlsCipherPreference;)V
31+
public final fun setVerifyPeer (Z)V
2232
public fun toBuilderApplicator ()Lkotlin/jvm/functions/Function1;
2333
}
2434

2535
public final class aws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngineConfig$Builder : aws/smithy/kotlin/runtime/http/engine/HttpClientEngineConfigImpl$BuilderImpl {
2636
public fun <init> ()V
37+
public final fun getCertificateFile ()Ljava/lang/String;
38+
public final fun getCertificatePem ()Ljava/lang/String;
39+
public final fun getCertificatesDirectory ()Ljava/lang/String;
2740
public final fun getClientBootstrap ()Laws/sdk/kotlin/crt/io/ClientBootstrap;
2841
public final fun getInitialWindowSizeBytes ()I
2942
public final fun getMaxConnections-pVg5ArA ()I
43+
public final fun getTlsCipherPreference ()Laws/sdk/kotlin/crt/io/TlsCipherPreference;
44+
public final fun getVerifyPeer ()Z
45+
public final fun setCertificateFile (Ljava/lang/String;)V
46+
public final fun setCertificatePem (Ljava/lang/String;)V
47+
public final fun setCertificatesDirectory (Ljava/lang/String;)V
3048
public final fun setClientBootstrap (Laws/sdk/kotlin/crt/io/ClientBootstrap;)V
3149
public final fun setInitialWindowSizeBytes (I)V
3250
public final fun setMaxConnections-WZ4Q5Ns (I)V
51+
public final fun setTlsCipherPreference (Laws/sdk/kotlin/crt/io/TlsCipherPreference;)V
52+
public final fun setVerifyPeer (Z)V
3353
}
3454

3555
public final class aws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngineConfig$Companion {

runtime/protocol/http-client-engines/http-client-engine-crt/jvm/src/aws/smithy/kotlin/runtime/http/engine/crt/ConnectionManager.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,13 @@ internal class ConnectionManager(
3232

3333
private val crtTlsContext: TlsContext = TlsContextOptionsBuilder()
3434
.apply {
35-
verifyPeer = true
3635
alpn = config.tlsContext.alpn.joinToString(separator = ";") { it.protocolId }
3736
minTlsVersion = toCrtTlsVersion(config.tlsContext.minVersion)
37+
caRoot = config.certificatePem
38+
caFile = config.certificateFile
39+
caDir = config.certificatesDirectory
40+
tlsCipherPreference = config.tlsCipherPreference
41+
verifyPeer = config.verifyPeer
3842
}
3943
.build()
4044
.let(::CrtTlsContext)

runtime/protocol/http-client-engines/http-client-engine-crt/jvm/src/aws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngineConfig.kt

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package aws.smithy.kotlin.runtime.http.engine.crt
77

88
import aws.sdk.kotlin.crt.io.ClientBootstrap
9+
import aws.sdk.kotlin.crt.io.TlsCipherPreference
910
import aws.smithy.kotlin.runtime.http.engine.HttpClientEngineConfig
1011
import aws.smithy.kotlin.runtime.http.engine.HttpClientEngineConfigImpl
1112

@@ -44,13 +45,48 @@ public class CrtHttpEngineConfig private constructor(builder: Builder) : HttpCli
4445
*/
4546
public var clientBootstrap: ClientBootstrap? = builder.clientBootstrap
4647

48+
/**
49+
* Certificate authority content in PEM format
50+
* Mutually exclusive with [certificateFile] and [certificatesDirectory].
51+
*/
52+
public var certificatePem: String? = builder.certificatePem
53+
54+
/**
55+
* Path to the certificate file in PEM format.
56+
* Mutually exclusive with [certificatePem]. Can be used independently or together with [certificatesDirectory].
57+
*/
58+
public var certificateFile: String? = builder.certificateFile
59+
60+
/**
61+
* Path to the certificates directory containing PEM files.
62+
* Mutually exclusive with [certificatePem]. Can be used independently or together with [certificateFile].
63+
*/
64+
public var certificatesDirectory: String? = builder.certificatesDirectory
65+
66+
/**
67+
* TLS cipher suite preference for connections.
68+
* Controls which cipher suites are available during TLS negotiation.
69+
*/
70+
public var tlsCipherPreference: TlsCipherPreference = builder.tlsCipherPreference
71+
72+
/**
73+
* Whether to verify the peer's certificate during TLS handshake.
74+
* When false, accepts any certificate.
75+
*/
76+
public var verifyPeer: Boolean = builder.verifyPeer
77+
4778
override fun toBuilderApplicator(): HttpClientEngineConfig.Builder.() -> Unit = {
4879
super.toBuilderApplicator()()
4980

5081
if (this is Builder) {
5182
maxConnections = this@CrtHttpEngineConfig.maxConnections
5283
initialWindowSizeBytes = this@CrtHttpEngineConfig.initialWindowSizeBytes
5384
clientBootstrap = this@CrtHttpEngineConfig.clientBootstrap
85+
certificatePem = this@CrtHttpEngineConfig.certificatePem
86+
certificateFile = this@CrtHttpEngineConfig.certificateFile
87+
certificatesDirectory = this@CrtHttpEngineConfig.certificatesDirectory
88+
tlsCipherPreference = this@CrtHttpEngineConfig.tlsCipherPreference
89+
verifyPeer = this@CrtHttpEngineConfig.verifyPeer
5490
}
5591
}
5692

@@ -73,5 +109,35 @@ public class CrtHttpEngineConfig private constructor(builder: Builder) : HttpCli
73109
* Set the [ClientBootstrap] to use for the engine. By default it is a shared instance.
74110
*/
75111
public var clientBootstrap: ClientBootstrap? = null
112+
113+
/**
114+
* Certificate Authority content in PEM format.
115+
* Mutually exclusive with caFile and caDir.
116+
*/
117+
public var certificatePem: String? = null
118+
119+
/**
120+
* Path to the certificate file in PEM format.
121+
* Mutually exclusive with [certificatePem]. Can be used independently or together with [certificatesDirectory].
122+
*/
123+
public var certificateFile: String? = null
124+
125+
/**
126+
* Path to the certificates directory containing PEM files.
127+
* Mutually exclusive with [certificatePem]. Can be used independently or together with [certificateFile].
128+
*/
129+
public var certificatesDirectory: String? = null
130+
131+
/**
132+
* TLS cipher suite preference for connections.
133+
* Controls which cipher suites are available during TLS negotiation.
134+
*/
135+
public var tlsCipherPreference: TlsCipherPreference = TlsCipherPreference.SYSTEM_DEFAULT
136+
137+
/**
138+
* Whether to verify the peer's certificate during TLS handshake.
139+
* When false, accepts any certificate (insecure, for testing only).
140+
*/
141+
public var verifyPeer: Boolean = true
76142
}
77143
}

runtime/protocol/http-client-engines/http-client-engine-okhttp/api/http-client-engine-okhttp.api

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,19 +66,36 @@ public final class aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngine$Com
6666
public final class aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngineConfig : aws/smithy/kotlin/runtime/http/engine/HttpClientEngineConfigImpl {
6767
public static final field Companion Laws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngineConfig$Companion;
6868
public synthetic fun <init> (Laws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngineConfig$Builder;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
69+
public final fun getCertificatePinner ()Lokhttp3/CertificatePinner;
70+
public final fun getCipherSuites ()Ljava/util/List;
6971
public final fun getConnectionIdlePollingInterval-FghU774 ()Lkotlin/time/Duration;
72+
public final fun getHostnameVerifier ()Ljavax/net/ssl/HostnameVerifier;
73+
public final fun getKeyManager ()Ljavax/net/ssl/KeyManager;
7074
public final fun getMaxConcurrencyPerHost-pVg5ArA ()I
75+
public final fun getTrustManager ()Ljavax/net/ssl/X509TrustManager;
76+
public final fun setKeyManager (Ljavax/net/ssl/KeyManager;)V
77+
public final fun setTrustManager (Ljavax/net/ssl/X509TrustManager;)V
7178
public fun toBuilderApplicator ()Lkotlin/jvm/functions/Function1;
7279
}
7380

7481
public final class aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngineConfig$Builder : aws/smithy/kotlin/runtime/http/engine/HttpClientEngineConfigImpl$BuilderImpl {
7582
public fun <init> ()V
83+
public final fun getCertificatePinner ()Lokhttp3/CertificatePinner;
84+
public final fun getCipherSuites ()Ljava/util/List;
7685
public final fun getConnectionIdlePollingInterval-FghU774 ()Lkotlin/time/Duration;
86+
public final fun getHostnameVerifier ()Ljavax/net/ssl/HostnameVerifier;
87+
public final fun getKeyManager ()Ljavax/net/ssl/KeyManager;
7788
public final fun getMaxConcurrencyPerHost-0hXNFcg ()Lkotlin/UInt;
7889
public fun getTelemetryProvider ()Laws/smithy/kotlin/runtime/telemetry/TelemetryProvider;
90+
public final fun getTrustManager ()Ljavax/net/ssl/X509TrustManager;
91+
public final fun setCertificatePinner (Lokhttp3/CertificatePinner;)V
92+
public final fun setCipherSuites (Ljava/util/List;)V
7993
public final fun setConnectionIdlePollingInterval-BwNAW2A (Lkotlin/time/Duration;)V
94+
public final fun setHostnameVerifier (Ljavax/net/ssl/HostnameVerifier;)V
95+
public final fun setKeyManager (Ljavax/net/ssl/KeyManager;)V
8096
public final fun setMaxConcurrencyPerHost-ExVfyTY (Lkotlin/UInt;)V
8197
public fun setTelemetryProvider (Laws/smithy/kotlin/runtime/telemetry/TelemetryProvider;)V
98+
public final fun setTrustManager (Ljavax/net/ssl/X509TrustManager;)V
8299
}
83100

84101
public final class aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngineConfig$Companion {

runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/src/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngine.kt

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ import kotlinx.coroutines.job
2424
import okhttp3.*
2525
import okhttp3.coroutines.executeAsync
2626
import java.util.concurrent.TimeUnit
27+
import javax.net.ssl.KeyManager
28+
import javax.net.ssl.SSLContext
29+
import javax.net.ssl.X509TrustManager
2730
import kotlin.time.toJavaDuration
2831
import aws.smithy.kotlin.runtime.net.TlsVersion as SdkTlsVersion
2932
import okhttp3.TlsVersion as OkHttpTlsVersion
@@ -103,7 +106,15 @@ public fun OkHttpEngineConfig.buildClient(
103106
followRedirects(false)
104107
followSslRedirects(false)
105108

106-
connectionSpecs(listOf(minTlsConnectionSpec(config.tlsContext), ConnectionSpec.CLEARTEXT))
109+
connectionSpecs(listOf(tlsConnectionSpec(config.tlsContext, config.cipherSuites), ConnectionSpec.CLEARTEXT))
110+
111+
config.trustManager?.let {
112+
val sslContext = createSslContext(it, config.keyManager)
113+
sslSocketFactory(sslContext.socketFactory, trustManager!!)
114+
}
115+
116+
config.certificatePinner?.let(::certificatePinner)
117+
config.hostnameVerifier?.let(::hostnameVerifier)
107118

108119
// Transient connection errors are handled by retry strategy (exceptions are wrapped and marked retryable
109120
// appropriately internally). We don't want inner retry logic that inflates the number of retries.
@@ -159,7 +170,7 @@ public fun OkHttpEngineConfig.buildClient(
159170
}.build()
160171
}
161172

162-
private fun minTlsConnectionSpec(tlsContext: TlsContext): ConnectionSpec {
173+
private fun tlsConnectionSpec(tlsContext: TlsContext, cipherSuites: List<String>?): ConnectionSpec {
163174
val minVersion = tlsContext.minVersion ?: TlsVersion.TLS_1_2
164175
val okHttpTlsVersions = SdkTlsVersion
165176
.values()
@@ -170,6 +181,9 @@ private fun minTlsConnectionSpec(tlsContext: TlsContext): ConnectionSpec {
170181
return ConnectionSpec
171182
.Builder(ConnectionSpec.MODERN_TLS)
172183
.tlsVersions(*okHttpTlsVersions)
184+
.apply {
185+
cipherSuites?.toTypedArray()?.let(::cipherSuites)
186+
}
173187
.build()
174188
}
175189

@@ -179,3 +193,16 @@ private fun toOkHttpTlsVersion(sdkTlsVersion: SdkTlsVersion): OkHttpTlsVersion =
179193
SdkTlsVersion.TLS_1_2 -> OkHttpTlsVersion.TLS_1_2
180194
SdkTlsVersion.TLS_1_3 -> OkHttpTlsVersion.TLS_1_3
181195
}
196+
197+
/**
198+
* Creates an SSL context with custom trust and key managers
199+
*/
200+
private fun createSslContext(trustManager: X509TrustManager, keyManager: KeyManager?): SSLContext {
201+
val keyManagers = keyManager?.let { arrayOf(it) }
202+
val trustManagers = arrayOf(trustManager)
203+
204+
val sslContext = SSLContext.getInstance("TLS")
205+
sslContext.init(keyManagers, trustManagers, null)
206+
207+
return sslContext
208+
}

runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/src/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngineConfig.kt

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ import aws.smithy.kotlin.runtime.http.engine.HttpClientEngineConfig
99
import aws.smithy.kotlin.runtime.http.engine.HttpClientEngineConfigImpl
1010
import aws.smithy.kotlin.runtime.telemetry.Global
1111
import aws.smithy.kotlin.runtime.telemetry.TelemetryProvider
12+
import okhttp3.CertificatePinner
13+
import javax.net.ssl.HostnameVerifier
14+
import javax.net.ssl.KeyManager
15+
import javax.net.ssl.X509TrustManager
1216
import kotlin.time.Duration
1317

1418
/**
@@ -50,12 +54,50 @@ public class OkHttpEngineConfig private constructor(builder: Builder) : HttpClie
5054
*/
5155
public val maxConcurrencyPerHost: UInt = builder.maxConcurrencyPerHost ?: builder.maxConcurrency
5256

57+
/**
58+
* Trust manager used to validate server certificates during TLS handshake.
59+
* Determines whether to trust the certificate chain presented by a remote server.
60+
* When provided, this trust manager will be used instead of the default system trust store.
61+
*/
62+
public var trustManager: X509TrustManager? = builder.trustManager
63+
64+
/**
65+
* Key manager that supplies client certificates for mutual TLS (mTLS) authentication.
66+
* When provided, the client will present certificates from this key manager when the server
67+
* requests client authentication. Used for scenarios requiring client certificate authentication.
68+
*/
69+
public var keyManager: KeyManager? = builder.keyManager
70+
71+
/**
72+
* List of cipher suites to enable for TLS connections. If null, uses OkHttp defaults.
73+
* When specified, only the listed cipher suites will be enabled.
74+
*/
75+
public val cipherSuites: List<String>? = builder.cipherSuites
76+
77+
/**
78+
* Certificate pinner that validates server certificates against known public key pins.
79+
* Used to prevent man-in-the-middle attacks by ensuring the server presents expected certificates.
80+
*/
81+
public val certificatePinner: CertificatePinner? = builder.certificatePinner
82+
83+
/**
84+
* Custom hostname verifier for validating server hostnames during TLS handshake.
85+
* By default, OkHttp verifies that the certificate's hostname matches the request hostname.
86+
* Use this to implement custom hostname verification logic.
87+
*/
88+
public val hostnameVerifier: HostnameVerifier? = builder.hostnameVerifier
89+
5390
override fun toBuilderApplicator(): HttpClientEngineConfig.Builder.() -> Unit = {
5491
super.toBuilderApplicator()()
5592

5693
if (this is Builder) {
5794
connectionIdlePollingInterval = this@OkHttpEngineConfig.connectionIdlePollingInterval
5895
maxConcurrencyPerHost = this@OkHttpEngineConfig.maxConcurrencyPerHost
96+
trustManager = this@OkHttpEngineConfig.trustManager
97+
keyManager = this@OkHttpEngineConfig.keyManager
98+
cipherSuites = this@OkHttpEngineConfig.cipherSuites
99+
certificatePinner = this@OkHttpEngineConfig.certificatePinner
100+
hostnameVerifier = this@OkHttpEngineConfig.hostnameVerifier
59101
}
60102
}
61103

@@ -84,6 +126,39 @@ public class OkHttpEngineConfig private constructor(builder: Builder) : HttpClie
84126
*/
85127
public var maxConcurrencyPerHost: UInt? = null
86128

129+
/**
130+
* Trust manager used to validate server certificates during TLS handshake.
131+
* Determines whether to trust the certificate chain presented by a remote server.
132+
* When provided, this trust manager will be used instead of the default system trust store.
133+
*/
134+
public var trustManager: X509TrustManager? = null
135+
136+
/**
137+
* Key manager that supplies client certificates for mutual TLS (mTLS) authentication.
138+
* When provided, the client will present certificates from this key manager when the server
139+
* requests client authentication. Used for scenarios requiring client certificate authentication.
140+
*/
141+
public var keyManager: KeyManager? = null
142+
143+
/**
144+
* List of cipher suites to enable for TLS connections. If null, uses OkHttp defaults.
145+
* When specified, only the listed cipher suites will be enabled.
146+
*/
147+
public var cipherSuites: List<String>? = null
148+
149+
/**
150+
* Certificate pinner that validates server certificates against known public key pins.
151+
* Used to prevent man-in-the-middle attacks by ensuring the server presents expected certificates.
152+
*/
153+
public var certificatePinner: CertificatePinner? = null
154+
155+
/**
156+
* Custom hostname verifier for validating server hostnames during TLS handshake.
157+
* By default, OkHttp verifies that the certificate's hostname matches the request hostname.
158+
* Use this to implement custom hostname verification logic.
159+
*/
160+
public var hostnameVerifier: HostnameVerifier? = null
161+
87162
override var telemetryProvider: TelemetryProvider = TelemetryProvider.Global
88163
}
89164
}

runtime/protocol/http-client-engines/test-suite/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ kotlin {
2626
dependencies {
2727
implementation(libs.ktor.server.jetty.jakarta)
2828
implementation(libs.ktor.network.tls.certificates)
29+
implementation(libs.okhttp)
2930

3031
implementation(project(":runtime:protocol:http-client-engines:http-client-engine-default"))
3132
implementation(project(":runtime:protocol:http-client-engines:http-client-engine-crt"))

0 commit comments

Comments
 (0)