diff --git a/docs/03-server-config.adoc b/docs/03-server-config.adoc index 8270cf2b..9241b6f6 100644 --- a/docs/03-server-config.adoc +++ b/docs/03-server-config.adoc @@ -20,7 +20,8 @@ tls: key: "server.p8.key" client: require: true - ca: "ca.crt" + cas: + - "ca.crt" compression: grpc: @@ -40,7 +41,7 @@ It configures following: - compression is disabled for gRPC-server (enabled by default) - compression is disabled for upstream gRPC-client (enabled by default) - server certificate is located at `server.crt` with the key for it at `server.p8.key` -- the server requires a client authentication by TLS client certificate signed by `ca.crt` certificate +- the server requires a client authentication by TLS client certificate signed by `ca.crt` certificate. - no JSON RPC is configured - upstreams configuration is configured in the file `upstreams.yaml` diff --git a/docs/08-authentication.adoc b/docs/08-authentication.adoc index 05909b43..19576751 100644 --- a/docs/08-authentication.adoc +++ b/docs/08-authentication.adoc @@ -180,15 +180,17 @@ tls: ---- | Path to certificate and certificate private key -a| `client.ca`, `client.required` +a| `client.ca`, `client.cas`, `client.required` a| [source,yaml] ---- tls: client: - ca: ca.crt + cas: + - ca1.crt + - ca2.crt required: true ---- -a| Path to CA used to authenticate incoming connections, used if `required: true` +a| Paths to CA used to authenticate incoming connections, used if `required: true` |=== \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/TlsSetup.kt b/src/main/kotlin/io/emeraldpay/dshackle/TlsSetup.kt index 2772f36d..29a77d9e 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/TlsSetup.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/TlsSetup.kt @@ -26,6 +26,7 @@ import org.apache.commons.lang3.StringUtils import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service +import java.security.cert.CertificateFactory @Service open class TlsSetup( @@ -77,10 +78,18 @@ open class TlsSetup( fileResolver.resolve(config.key!!) ) } - if (StringUtils.isNotEmpty(config.clientCa)) { + if (config.clientCAs.isNotEmpty()) { log.info("Using TLS for client authentication for $category") + val cf = CertificateFactory.getInstance("X.509") sslContextBuilder.trustManager( - fileResolver.resolve(config.clientCa!!) + + config.clientCAs + .map { fileResolver.resolve(it) } + .map { file -> + file.inputStream().use { + cf.generateCertificate(it) as java.security.cert.X509Certificate + } + } ) if (config.clientRequire != null && config.clientRequire!!) { sslContextBuilder.clientAuth(ClientAuth.REQUIRE) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/AuthConfig.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/AuthConfig.kt index 8d8c2d4e..5e28aec5 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/AuthConfig.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/AuthConfig.kt @@ -56,6 +56,6 @@ class AuthConfig { var certificate: String? = null var key: String? = null var clientRequire: Boolean? = null - var clientCa: String? = null + var clientCAs: MutableList = mutableListOf() } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/AuthConfigReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/AuthConfigReader.kt index 272da506..8ef4b542 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/AuthConfigReader.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/AuthConfigReader.kt @@ -18,6 +18,7 @@ package io.emeraldpay.dshackle.config import org.slf4j.LoggerFactory import org.yaml.snakeyaml.nodes.MappingNode +import org.yaml.snakeyaml.nodes.ScalarNode class AuthConfigReader : YamlConfigReader() { @@ -77,7 +78,15 @@ class AuthConfigReader : YamlConfigReader() { getValueAsBool(clientNode, "require")?.let { auth.clientRequire = it } - auth.clientCa = getValueAsString(clientNode, "ca") + getValueAsString(clientNode, "ca")?.let { + auth.clientCAs.add(it) + } + getList(clientNode, "cas")?.let { + println(it) + it.value?.let { crt -> + auth.clientCAs.addAll(crt.map { v -> v.value }) + } + } } auth } diff --git a/src/test/groovy/io/emeraldpay/dshackle/TlsSetupSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/TlsSetupSpec.groovy index f8a46e5a..8d14593a 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/TlsSetupSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/TlsSetupSpec.groovy @@ -78,7 +78,7 @@ class TlsSetupSpec extends Specification { certificate: "127.0.0.1.crt", key: "127.0.0.1.p8.key", clientRequire: true, - clientCa: "ca.myhost.dev.crt" + clientCAs: ["ca.myhost.dev.crt"] ) when: def act = tlsSetup.setupServer("test", config, false) @@ -182,12 +182,12 @@ class TlsSetupSpec extends Specification { certificate: "127.0.0.1.crt", key: "127.0.0.1.p8.key", clientRequire: true, - clientCa: "none.crt" + clientCAs: ["none.crt"] ) when: tlsSetup.setupServer("test", config, false) then: - def t = thrown(IllegalArgumentException) + def t = thrown(FileNotFoundException) } def "Fail if client certificate is invalid"() { @@ -197,11 +197,11 @@ class TlsSetupSpec extends Specification { certificate: "127.0.0.1.crt", key: "127.0.0.1.p8.key", clientRequire: true, - clientCa: "ca.myhost.dev.key" + clientCAs: ["ca.myhost.dev.key"] ) when: tlsSetup.setupServer("test", config, false) then: - def t = thrown(IllegalArgumentException) + def t = thrown(java.security.cert.CertificateParsingException) } } diff --git a/src/test/groovy/io/emeraldpay/dshackle/config/AuthConfigReaderSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/config/AuthConfigReaderSpec.groovy index 7f1d483a..c31a95a9 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/config/AuthConfigReaderSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/config/AuthConfigReaderSpec.groovy @@ -72,6 +72,6 @@ class AuthConfigReaderSpec extends Specification { act.key == "/etc/client1.myservice.com.key" act.clientRequire != null !act.clientRequire - act.clientCa == "/etc/ca.myservice.com.crt" + act.clientCAs == ["/etc/ca.myservice.com.crt"] } } diff --git a/src/test/groovy/io/emeraldpay/dshackle/config/MainConfigReaderSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/config/MainConfigReaderSpec.groovy index 6dc071fd..a03a7282 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/config/MainConfigReaderSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/config/MainConfigReaderSpec.groovy @@ -39,7 +39,7 @@ class MainConfigReaderSpec extends Specification { certificate == "/path/127.0.0.1.crt" key == "/path/127.0.0.1.p8.key" !clientRequire - clientCa == "/path/ca.dshackle.test.crt" + clientCAs == ["/path/ca.dshackle.test.crt"] } act.cache != null with(act.cache) {