Skip to content

Commit 40c4b6f

Browse files
varshith257jdegoes
andauthored
Add more options to the SSL configuration (#3139)
Co-authored-by: John A. De Goes <[email protected]>
1 parent e1afcf2 commit 40c4b6f

File tree

3 files changed

+189
-11
lines changed

3 files changed

+189
-11
lines changed

zio-http/jvm/src/main/scala/zio/http/netty/client/ClientSSLConverter.scala

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ package zio.http.netty.client
1818

1919
import java.io.{File, FileInputStream, InputStream}
2020
import java.security.KeyStore
21-
import javax.net.ssl.TrustManagerFactory
21+
import javax.net.ssl.{KeyManagerFactory, TrustManagerFactory}
2222

2323
import scala.util.Using
2424

@@ -31,6 +31,39 @@ import zio.http.ClientSSLConfig
3131
import io.netty.handler.ssl.util.InsecureTrustManagerFactory
3232
import io.netty.handler.ssl.{SslContext, SslContextBuilder}
3333
private[netty] object ClientSSLConverter {
34+
private def keyManagerTrustManagerToSslContext(
35+
keyManagerInfo: Option[(String, InputStream, Option[Secret])],
36+
trustManagerInfo: Option[(String, InputStream, Option[Secret])],
37+
sslContextBuilder: SslContextBuilder,
38+
): SslContextBuilder = {
39+
val mkeyManagerFactory =
40+
keyManagerInfo.map { case (keyStoreType, inputStream, maybePassword) =>
41+
val keyStore = KeyStore.getInstance(keyStoreType)
42+
val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm)
43+
val password = maybePassword.map(_.value.toArray).orNull
44+
45+
keyStore.load(inputStream, password)
46+
keyManagerFactory.init(keyStore, password)
47+
keyManagerFactory
48+
}
49+
50+
val mtrustManagerFactory =
51+
trustManagerInfo.map { case (keyStoreType, inputStream, maybePassword) =>
52+
val keyStore = KeyStore.getInstance(keyStoreType)
53+
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm)
54+
val password = maybePassword.map(_.value.toArray).orNull
55+
56+
keyStore.load(inputStream, password)
57+
trustManagerFactory.init(keyStore)
58+
trustManagerFactory
59+
}
60+
61+
var bldr = SslContextBuilder.forClient()
62+
mkeyManagerFactory.foreach(kmf => bldr = bldr.keyManager(kmf))
63+
mtrustManagerFactory.foreach(tmf => bldr = bldr.trustManager(tmf))
64+
bldr
65+
}
66+
3467
private def trustStoreToSslContext(
3568
trustStoreStream: InputStream,
3669
trustStorePassword: Secret,
@@ -78,6 +111,41 @@ private[netty] object ClientSSLConverter {
78111
case ClientSSLConfig.FromTrustStoreFile(trustStorePath, trustStorePassword) =>
79112
val trustStoreStream = new FileInputStream(trustStorePath)
80113
trustStoreToSslContext(trustStoreStream, trustStorePassword, sslContextBuilder)
114+
case ClientSSLConfig.FromJavaxNetSsl(
115+
keyManagerKeyStoreType,
116+
keyManagerSource,
117+
keyManagerPassword,
118+
trustManagerKeyStoreType,
119+
trustManagerSource,
120+
trustManagerPassword,
121+
) =>
122+
val keyManagerInfo =
123+
keyManagerSource match {
124+
case ClientSSLConfig.FromJavaxNetSsl.File(path) =>
125+
Option(new FileInputStream(path)).map(inputStream =>
126+
(keyManagerKeyStoreType, inputStream, keyManagerPassword),
127+
)
128+
case ClientSSLConfig.FromJavaxNetSsl.Resource(path) =>
129+
Option(getClass.getClassLoader.getResourceAsStream(path)).map(inputStream =>
130+
(keyManagerKeyStoreType, inputStream, keyManagerPassword),
131+
)
132+
case ClientSSLConfig.FromJavaxNetSsl.Empty => None
133+
}
134+
135+
val trustManagerInfo =
136+
trustManagerSource match {
137+
case ClientSSLConfig.FromJavaxNetSsl.File(path) =>
138+
Option(new FileInputStream(path)).map(inputStream =>
139+
(trustManagerKeyStoreType, inputStream, trustManagerPassword),
140+
)
141+
case ClientSSLConfig.FromJavaxNetSsl.Resource(path) =>
142+
Option(getClass.getClassLoader.getResourceAsStream(path)).map(inputStream =>
143+
(trustManagerKeyStoreType, inputStream, trustManagerPassword),
144+
)
145+
case ClientSSLConfig.FromJavaxNetSsl.Empty => None
146+
}
147+
148+
keyManagerTrustManagerToSslContext(keyManagerInfo, trustManagerInfo, sslContextBuilder)
81149
}
82150

83151
def toNettySSLContext(sslConfig: ClientSSLConfig): SslContext = {

zio-http/jvm/src/test/scala/zio/http/ClientHttpsSpec.scala

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,26 +18,22 @@ package zio.http
1818

1919
import zio._
2020
import zio.test.Assertion._
21-
import zio.test.TestAspect.{ignore, nonFlaky}
21+
import zio.test.TestAspect.nonFlaky
2222
import zio.test.{TestAspect, assertZIO}
2323

2424
import zio.http.netty.NettyConfig
2525
import zio.http.netty.client.NettyClientDriver
2626

27-
object ClientHttpsSpec extends ZIOHttpSpec {
28-
29-
val sslConfig = ClientSSLConfig.FromTrustStoreResource(
30-
trustStorePath = "truststore.jks",
31-
trustStorePassword = "changeit",
32-
)
27+
abstract class ClientHttpsSpecBase extends ZIOHttpSpec {
28+
val sslConfig: ClientSSLConfig
3329

3430
val zioDev =
3531
URL.decode("https://zio.dev").toOption.get
3632

3733
val badRequest =
3834
URL
3935
.decode(
40-
"https://www.whatissslcertificate.com/google-has-made-the-list-of-untrusted-providers-of-digital-certificates/",
36+
"https://httpbin.org/status/400",
4137
)
4238
.toOption
4339
.get
@@ -57,7 +53,7 @@ object ClientHttpsSpec extends ZIOHttpSpec {
5753
test("should respond as Bad Request") {
5854
val actual = Client.batched(Request.get(badRequest)).map(_.status)
5955
assertZIO(actual)(equalTo(Status.BadRequest))
60-
} @@ ignore,
56+
},
6157
test("should throw DecoderException for handshake failure") {
6258
val actual = Client.batched(Request.get(untrusted)).exit
6359
assertZIO(actual)(
@@ -69,7 +65,7 @@ object ClientHttpsSpec extends ZIOHttpSpec {
6965
),
7066
),
7167
)
72-
} @@ nonFlaky(20) @@ ignore,
68+
} @@ nonFlaky(20),
7369
)
7470
.provideShared(
7571
ZLayer.succeed(ZClient.Config.default.ssl(sslConfig)),
@@ -83,3 +79,20 @@ object ClientHttpsSpec extends ZIOHttpSpec {
8379
ZLayer.succeed(NettyConfig.defaultWithFastShutdown),
8480
)
8581
}
82+
83+
object ClientHttpsSpec extends ClientHttpsSpecBase {
84+
85+
val sslConfig = ClientSSLConfig.FromTrustStoreResource(
86+
trustStorePath = "truststore.jks",
87+
trustStorePassword = "changeit",
88+
)
89+
}
90+
91+
object ClientHttpsFromJavaxNetSslSpec extends ClientHttpsSpecBase {
92+
93+
val sslConfig =
94+
ClientSSLConfig.FromJavaxNetSsl
95+
.builderWithTrustManagerResource("trustStore.jks")
96+
.trustManagerPassword("changeit")
97+
.build()
98+
}

zio-http/shared/src/main/scala/zio/http/ClientSSLConfig.scala

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ object ClientSSLConfig {
2828
val trustStorePath = Config.string("trust-store-path")
2929
val trustStorePassword = Config.secret("trust-store-password")
3030

31+
val keyManagerKeyStoreType = Config.string("keyManagerKeyStoreType")
32+
val keyManagerFile = Config.string("keyManagerFile")
33+
val keyManagerResource = Config.string("keyManagerResource")
34+
val keyManagerPassword = Config.secret("keyManagerPassword")
35+
val trustManagerKeyStoreType = Config.string("trustManagerKeyStoreType")
36+
val trustManagerFile = Config.string("trustManagerFile")
37+
val trustManagerResource = Config.string("trustManagerResource")
38+
val trustManagerPassword = Config.secret("trustManagerPassword")
39+
3140
val default = Config.succeed(Default)
3241
val fromCertFile = certPath.map(FromCertFile(_))
3342
val fromCertResource = certPath.map(FromCertResource(_))
@@ -39,6 +48,45 @@ object ClientSSLConfig {
3948
serverCertConfig.zipWith(clientCertConfig)(FromClientAndServerCert(_, _))
4049
}
4150

51+
val fromJavaxNetSsl = {
52+
keyManagerKeyStoreType.optional
53+
.zip(keyManagerFile.optional)
54+
.zip(keyManagerResource.optional)
55+
.zip(keyManagerPassword.optional)
56+
.zip(trustManagerKeyStoreType.optional)
57+
.zip(
58+
trustManagerFile.optional
59+
.zip(trustManagerResource.optional)
60+
.validate("must supply trustManagerFile or trustManagerResource")(pair =>
61+
pair._1.isDefined || pair._2.isDefined,
62+
),
63+
)
64+
.zip(trustManagerPassword.optional)
65+
.map { case (kmkst, kmf, kmr, kmpass, tmkst, (tmf, tmr), tmpass) =>
66+
val bldr0 =
67+
List[(Option[String], FromJavaxNetSsl => String => FromJavaxNetSsl)](
68+
(kmkst, b => b.keyManagerKeyStoreType(_)),
69+
(kmf, b => b.keyManagerFile),
70+
(kmr, b => b.keyManagerResource),
71+
(tmkst, b => b.trustManagerKeyStoreType(_)),
72+
(tmf, b => b.trustManagerFile),
73+
(tmr, b => b.trustManagerResource),
74+
)
75+
.foldLeft(FromJavaxNetSsl()) { case (bldr, (maybe, lens)) =>
76+
maybe.fold(bldr)(s => lens(bldr)(s))
77+
}
78+
79+
List[(Option[Secret], FromJavaxNetSsl => Secret => FromJavaxNetSsl)](
80+
(kmpass, b => b.keyManagerPassword(_)),
81+
(tmpass, b => b.trustManagerPassword(_)),
82+
)
83+
.foldLeft(bldr0) { case (bldr, (maybe, lens)) =>
84+
maybe.fold(bldr)(s => lens(bldr)(s))
85+
}
86+
.build()
87+
}
88+
}
89+
4290
tpe.switch(
4391
"Default" -> default,
4492
"FromCertFile" -> fromCertFile,
@@ -58,6 +106,55 @@ object ClientSSLConfig {
58106
clientCertConfig: ClientSSLCertConfig,
59107
) extends ClientSSLConfig
60108

109+
final case class FromJavaxNetSsl(
110+
keyManagerKeyStoreType: String = "JKS",
111+
keyManagerSource: FromJavaxNetSsl.Source = FromJavaxNetSsl.Empty,
112+
keyManagerPassword: Option[Secret] = None,
113+
trustManagerKeyStoreType: String = "JKS",
114+
trustManagerSource: FromJavaxNetSsl.Source = FromJavaxNetSsl.Empty,
115+
trustManagerPassword: Option[Secret] = None,
116+
) extends ClientSSLConfig { self =>
117+
118+
def isValidBuild: Boolean = trustManagerSource != FromJavaxNetSsl.Empty
119+
def isInvalidBuild: Boolean = !isValidBuild
120+
def build(): FromJavaxNetSsl = this
121+
122+
def keyManagerKeyStoreType(tpe: String): FromJavaxNetSsl = self.copy(keyManagerKeyStoreType = tpe)
123+
def keyManagerFile(file: String): FromJavaxNetSsl =
124+
keyManagerSource match {
125+
case FromJavaxNetSsl.Resource(_) => this
126+
case _ => self.copy(keyManagerSource = FromJavaxNetSsl.File(file))
127+
}
128+
def keyManagerResource(path: String): FromJavaxNetSsl = self.copy(keyManagerSource = FromJavaxNetSsl.Resource(path))
129+
def keyManagerPassword(password: Secret): FromJavaxNetSsl = self.copy(keyManagerPassword = Some(password))
130+
def keyManagerPassword(password: String): FromJavaxNetSsl = keyManagerPassword(Secret(password))
131+
132+
def trustManagerKeyStoreType(tpe: String): FromJavaxNetSsl = self.copy(trustManagerKeyStoreType = tpe)
133+
def trustManagerFile(file: String): FromJavaxNetSsl =
134+
trustManagerSource match {
135+
case FromJavaxNetSsl.Resource(_) => this
136+
case _ => self.copy(trustManagerSource = FromJavaxNetSsl.File(file))
137+
}
138+
def trustManagerResource(path: String): FromJavaxNetSsl =
139+
self.copy(trustManagerSource = FromJavaxNetSsl.Resource(path))
140+
def trustManagerPassword(password: Secret): FromJavaxNetSsl = self.copy(trustManagerPassword = Some(password))
141+
def trustManagerPassword(password: String): FromJavaxNetSsl = trustManagerPassword(Secret(password))
142+
}
143+
144+
object FromJavaxNetSsl {
145+
146+
sealed trait Source extends Product with Serializable
147+
case object Empty extends Source
148+
final case class File(file: String) extends Source
149+
final case class Resource(resource: String) extends Source
150+
151+
def builderWithTrustManagerFile(file: String): FromJavaxNetSsl =
152+
FromJavaxNetSsl().trustManagerFile(file)
153+
154+
def builderWithTrustManagerResource(resource: String): FromJavaxNetSsl =
155+
FromJavaxNetSsl().trustManagerResource(resource)
156+
}
157+
61158
object FromTrustStoreResource {
62159
def apply(trustStorePath: String, trustStorePassword: String): FromTrustStoreResource =
63160
FromTrustStoreResource(trustStorePath, Secret(trustStorePassword))

0 commit comments

Comments
 (0)