diff --git a/docs/03-server-config.adoc b/docs/03-server-config.adoc index 8b99ec36..8b8f9963 100644 --- a/docs/03-server-config.adoc +++ b/docs/03-server-config.adoc @@ -29,6 +29,31 @@ It configures following: - no JSON RPC is configured - upstreams configuration is configured in the file `upstreams.yaml` +.Configuration +|=== +| Name | Example | Description + +a| `version` +| +| Version of the format of the config + +a| `port` +a| `port: 12449` +| Port to bind gRPC server. `2449` by default + +a| `tls` +| +| TLS configuration for gRPC. See link:07-authentication.adoc[Authentication] for details + +a| `proxy` +| +| Proxy configuration + +a| `upstreams` +| +| Upstreams configuration +|=== + === Enabling JSON RPC proxy .Example proxy: @@ -51,4 +76,49 @@ upstreams: With that configuration Dshackle starts a JSON RPC proxy: - JSON RPC server is listening on `0.0.0.0:8080` -- `http://0.0.0.0:8080/eth` provides access to Ethereum API routed to an available upstream \ No newline at end of file +- `http://0.0.0.0:8080/eth` provides access to Ethereum API routed to an available upstream + +.Full configuration: +[source,yaml] +---- +proxy: + port: 8080 + tls: + enabled: true + server: + certificate: server.crt + key: server.p8.key + client: + require: true + ca: ca.crt + routes: + - id: eth + blockchain: ethereum + - id: etc + blockchain: ethereum_classic +---- + +.Proxy configuration +|=== +| Name | Example | Description + +a| `host` +a| `host: 0.0.0.0` +| Host to bind proxy server. `127.0.0.1` by default + +a| `port` +a| `port: 8545` +| Port to bind proxy server. `8080` by default + +a| `enabled` +a| `enabled: true` +| Enable/disable proxy server + +a| `tls` +| +| TLS configuration for proxy. See link:07-authentication.adoc[Authentication] for details + +a| `routes` +| +| List of endpoints to proxy +|=== \ No newline at end of file diff --git a/docs/07-authentication.adoc b/docs/07-authentication.adoc index bb839ebb..43bd6f5e 100644 --- a/docs/07-authentication.adoc +++ b/docs/07-authentication.adoc @@ -12,16 +12,22 @@ generate certificates, but the traditional `openssl` tool can be used as well. ==== Setup Server certificate -.Generate a server certificate +.Generate a Certificate Authority [source,bash] ---- -SERVER_CA="ca.myhost.dev" -SERVER_IP="127.0.0.1" -ORG="My Company" -ORG_UNIT="Blockchain" +export SERVER_CA="ca.myhost.dev" +export ORG="My Company" +export ORG_UNIT="Blockchain" certstrap init --common-name "$SERVER_CA" --passphrase "" -o "$ORG" -ou "$ORG_UNIT CA" -certstrap request-cert -ip $SERVER_IP --passphrase "" -o "$ORG" -ou "$ORG_UNIT Server" +openssl pkcs8 -topk8 -inform PEM -outform PEM -in out/$SERVER_CA.key -out out/$SERVER_CA.p8.key -nocrypt +---- + +.Generate a Server Certificate +---- +export SERVER_IP="127.0.0.1" + +certstrap request-cert -ip $SERVER_IP --common-name $SERVER_IP --passphrase "" -o "$ORG" -ou "$ORG_UNIT Server" certstrap sign $SERVER_IP --CA $SERVER_CA openssl pkcs8 -topk8 -inform PEM -outform PEM -in out/$SERVER_IP.key -out out/$SERVER_IP.p8.key -nocrypt @@ -30,8 +36,9 @@ openssl pkcs8 -topk8 -inform PEM -outform PEM -in out/$SERVER_IP.key -out out/$S You are going to get following files now in `out` directory: - `ca.myhost.dev.crt` your Certification Authority to sign or verify other certificates -- `127.0.0.1.crt` server certificate, 127.0.0.1 is an IP address supposed to be used by that instance, and we use local - host for demo purposes. For production use a real IP must be used. +- `ca.myhost.dev.p8.key` is the private key for Certification Authority in PKSC 8 format +- `127.0.0.1.crt` server certificate, 127.0.0.1 is an IP address supposed to be used by that instance, and we use local host for demo purposes. +For production use a real IP must be used. - `127.0.0.1.p8.key` is private key for certificate in PKCS 8 format, it needed by Dshackle server to use certificate Copy those files to directory with Dshackle and update configuration. @@ -144,5 +151,43 @@ upstreams: key: client_1.p8.key ---- -Now if you run second server it will connect to first server ("upstream") running on port 2449, will verify upstream -with certificate `ca.myhost.dev.crt` and authenticate itself by using pair of `client_1.crt` and `client_1.p8.key` \ No newline at end of file +Now if you run second server it will connect to first server ("upstream") running on port 2449, will verify upstream with certificate `ca.myhost.dev.crt` and authenticate itself by using pair of `client_1.crt` and `client_1.p8.key` + +=== Server TLS configuration + +|=== +| Name | Example | Description + +a| `enabled` +a| +[source,yaml] +---- +tls: + enabled: true +---- +| Enabled or disable TLS. By default it checks if certificate is set, and then enables it. But if you enable the TLS +but didn't specify the certificate or key, then the DShackle will fails to start with error. + +a| `server.certificate`, `server.key` +a| +[source,yaml] +---- +tls: + server: + certificate: server.com.crt + key: server.com.p8.key +---- +| Path to certificate and certificate private key + +a| `client.ca`, `client.required` +a| +[source,yaml] +---- +tls: + client: + ca: ca.crt + required: true +---- +a| Path 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/ProxyStarter.kt b/src/main/kotlin/io/emeraldpay/dshackle/ProxyStarter.kt index 0c0b6a54..fcca4e49 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/ProxyStarter.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/ProxyStarter.kt @@ -34,7 +34,8 @@ class ProxyStarter( @Autowired private val env: Environment, @Autowired private val readRpcJson: ReadRpcJson, @Autowired private val writeRpcJson: WriteRpcJson, - @Autowired private val nativeCall: NativeCall + @Autowired private val nativeCall: NativeCall, + @Autowired private val tlsSetup: TlsSetup ) { companion object { @@ -48,7 +49,7 @@ class ProxyStarter( log.debug("Proxy server is not configured") return } - val server = ProxyServer(config, readRpcJson, writeRpcJson, nativeCall) + val server = ProxyServer(config, readRpcJson, writeRpcJson, nativeCall, tlsSetup) server.start() } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/TlsSetup.kt b/src/main/kotlin/io/emeraldpay/dshackle/TlsSetup.kt new file mode 100644 index 00000000..cf7c82ff --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/TlsSetup.kt @@ -0,0 +1,83 @@ +/** + * Copyright (c) 2020 ETCDEV GmbH + * + * 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 io.emeraldpay.dshackle + +import io.emeraldpay.dshackle.config.AuthConfig +import io.grpc.netty.GrpcSslContexts +import io.netty.handler.ssl.ClientAuth +import io.netty.handler.ssl.SslContext +import io.netty.handler.ssl.SslContextBuilder +import org.apache.commons.lang3.StringUtils +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Service + +@Service +class TlsSetup( + @Autowired val fileResolver: FileResolver +) { + + companion object { + private val log = LoggerFactory.getLogger(TlsSetup::class.java) + } + + fun setupServer(category: String, config: AuthConfig.ServerTlsAuth): SslContext? { + val mustBeSecure = config.enabled != null && config.enabled!! + val tlsDisabled = config.enabled != null && !config.enabled!! + var hasServerCertificate = true + if (!tlsDisabled) { + if (StringUtils.isEmpty(config.certificate)) { + if (mustBeSecure) { + log.error("tls.server.certificate property for $category is not set (path to server TLS certificate) but TLS is enabled") + throw IllegalArgumentException("Certificate not set") + } + hasServerCertificate = false + } + if (StringUtils.isEmpty(config.key)) { + if (mustBeSecure) { + log.error("tls.server.key property for $category is not set (path to server TLS certificate key) but TLS is enabled") + throw IllegalArgumentException("Certificate Key not set") + } + hasServerCertificate = false + } + } + if (mustBeSecure || (!tlsDisabled && hasServerCertificate)) { + log.info("Using TLS for $category") + val sslContextBuilder = SslContextBuilder.forServer( + fileResolver.resolve(config.certificate!!), + fileResolver.resolve(config.key!!) + ) + if (StringUtils.isNotEmpty(config.clientCa)) { + log.info("Using TLS for client authentication for $category") + sslContextBuilder.trustManager( + fileResolver.resolve(config.clientCa!!) + ) + if (config.clientRequire != null && config.clientRequire!!) { + sslContextBuilder.clientAuth(ClientAuth.REQUIRE) + } + } else if (config.clientRequire != null && config.clientRequire!!) { + throw IllegalArgumentException("Client Certificate not set") + } else { + log.warn("Trust all clients for $category") + } + return sslContextBuilder.build() + } else { + log.warn("Using insecure transport for $category") + } + return null + } + +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/AuthConfig.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/AuthConfig.kt new file mode 100644 index 00000000..99a507a7 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/AuthConfig.kt @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2020 ETCDEV GmbH + * + * 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 io.emeraldpay.dshackle.config + +import org.slf4j.LoggerFactory + +class AuthConfig { + + companion object { + private val log = LoggerFactory.getLogger(AuthConfig::class.java) + } + + open class ClientAuth { + var type: String? = null + } + + class ClientBasicAuth( + val username: String, + val password: String + ) : ClientAuth() + + class ClientTlsAuth( + var ca: String? = null, + var certificate: String? = null, + var key: String? = null + ) : ClientAuth() + + /** + * Example config: + * ``` + * enabled: false + * server: + * certificate: "127.0.0.1.crt" + * key: "127.0.0.1.p8.key" + * client: + * require: false + * ca: "ca.dshackle.test.crt" + * ``` + */ + open class ServerTlsAuth { + var enabled: Boolean? = null + var certificate: String? = null + var key: String? = null + var clientRequire: Boolean? = null + var clientCa: String? = null + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/AuthConfigReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/AuthConfigReader.kt new file mode 100644 index 00000000..0135ca4a --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/AuthConfigReader.kt @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2020 ETCDEV GmbH + * + * 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 io.emeraldpay.dshackle.config + +import org.slf4j.LoggerFactory +import org.yaml.snakeyaml.nodes.MappingNode + +class AuthConfigReader : YamlConfigReader() { + + companion object { + private val log = LoggerFactory.getLogger(AuthConfigReader::class.java) + } + + fun readClientBasicAuth(node: MappingNode?): AuthConfig.ClientBasicAuth? { + return getMapping(node, "basic-auth")?.let { authNode -> + val username = getValueAsString(authNode, "username") + val password = getValueAsString(authNode, "password") + if (username != null && password != null) { + AuthConfig.ClientBasicAuth(username, password) + } else { + log.warn("Basic auth is not fully configured") + null + } + } + } + + fun readClientTls(node: MappingNode?): AuthConfig.ClientTlsAuth? { + return getMapping(node, "tls")?.let { authNode -> + val auth = AuthConfig.ClientTlsAuth() + auth.ca = getValueAsString(authNode, "ca") + auth.certificate = getValueAsString(authNode, "certificate") + auth.key = getValueAsString(authNode, "key") + auth + } + } + + /** + * Example config: + * ``` + * enabled: false + * server: + * certificate: "127.0.0.1.crt" + * key: "127.0.0.1.p8.key" + * client: + * require: false + * ca: "ca.dshackle.test.crt" + * ``` + */ + fun readServerTls(node: MappingNode?): AuthConfig.ServerTlsAuth? { + return getMapping(node, "tls")?.let { node -> + val auth = AuthConfig.ServerTlsAuth() + getValueAsBool(node, "enabled")?.let { + auth.enabled = it + } + getMapping(node, "server")?.let { node -> + auth.certificate = getValueAsString(node, "certificate") + auth.key = getValueAsString(node, "key") + } + getMapping(node, "client")?.let { node -> + getValueAsBool(node, "require")?.let { + auth.clientRequire = it + } + auth.clientCa = getValueAsString(node, "ca") + } + auth + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/ProxyConfig.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/ProxyConfig.kt index b543a741..a011e13f 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/ProxyConfig.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/ProxyConfig.kt @@ -38,6 +38,11 @@ class ProxyConfig { */ var port: Int = 8080 + /** + * TLS Auth required from clients. + */ + var tls: AuthConfig.ServerTlsAuth? = null + /** * List of available routes */ diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/ProxyConfigReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/ProxyConfigReader.kt index 43436f21..3e3d03bd 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/ProxyConfigReader.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/ProxyConfigReader.kt @@ -33,10 +33,10 @@ class ProxyConfigReader : YamlConfigReader() { } private var filename = "dshackle.yaml" + private val authConfigReader = AuthConfigReader() fun read(input: InputStream): ProxyConfig? { - val yaml = Yaml() - val configNode = asMappingNode(yaml.compose(InputStreamReader(input))) + val configNode = readNode(input) return read(getMapping(configNode, "proxy")) } @@ -73,8 +73,10 @@ class ProxyConfigReader : YamlConfigReader() { } } if (config.routes.isEmpty()) { + log.warn("Proxy config has no routes") return null } + config.tls = authConfigReader.readServerTls(input) return config } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt index e4537db9..11a128e4 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt @@ -17,7 +17,6 @@ package io.emeraldpay.dshackle.config import io.emeraldpay.dshackle.Defaults import java.net.URI -import java.time.Duration import java.util.* import kotlin.collections.ArrayList import kotlin.collections.HashMap @@ -81,7 +80,7 @@ class UpstreamsConfig { class GrpcConnection : UpstreamConnection() { var host: String? = null var port: Int = 0 - var auth: TlsAuth? = null + var auth: AuthConfig.ClientTlsAuth? = null } class EthereumConnection : UpstreamConnection() { @@ -90,29 +89,16 @@ class UpstreamsConfig { } class HttpEndpoint(val url: URI) { - var basicAuth: BasicAuth? = null - var tls: TlsAuth? = null + var basicAuth: AuthConfig.ClientBasicAuth? = null + var tls: AuthConfig.ClientTlsAuth? = null } class WsEndpoint(val url: URI) { var origin: URI? = null - var basicAuth: BasicAuth? = null + var basicAuth: AuthConfig.ClientBasicAuth? = null } - open class Auth { - var type: String? = null - } - class BasicAuth( - val username: String, - val password: String - ) : Auth() - - class TlsAuth( - var ca: String? = null, - var certificate: String? = null, - var key: String? = null - ) : Auth() //TODO make it unmodifiable after initial load class Labels: HashMap() { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt index 8ddf1f79..d63b52da 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt @@ -18,24 +18,21 @@ package io.emeraldpay.dshackle.config import org.apache.commons.lang3.StringUtils import org.slf4j.LoggerFactory import org.yaml.snakeyaml.Yaml -import org.yaml.snakeyaml.nodes.CollectionNode import org.yaml.snakeyaml.nodes.MappingNode -import org.yaml.snakeyaml.nodes.Node import org.yaml.snakeyaml.nodes.ScalarNode import reactor.util.function.Tuples import java.io.InputStream import java.io.InputStreamReader -import java.lang.IllegalArgumentException import java.net.URI import java.time.Duration class UpstreamsConfigReader : YamlConfigReader() { private val log = LoggerFactory.getLogger(UpstreamsConfigReader::class.java) + private val authConfigReader = AuthConfigReader() fun read(input: InputStream): UpstreamsConfig { - val yaml = Yaml() - val configNode = asMappingNode(yaml.compose(InputStreamReader(input))) + val configNode = readNode(input) val config = UpstreamsConfig() config.version = getValueAsString(configNode, "version") @@ -65,8 +62,8 @@ class UpstreamsConfigReader : YamlConfigReader() { getValueAsString(node, "url")?.let { url -> val http = UpstreamsConfig.HttpEndpoint(URI(url)) connection.rpc = http - http.basicAuth = readBasicAuth(node) - http.tls = readTls(node) + http.basicAuth = authConfigReader.readClientBasicAuth(node) + http.tls = authConfigReader.readClientTls(node) } } getMapping(connConfigNode, "ws")?.let { node -> @@ -76,7 +73,7 @@ class UpstreamsConfigReader : YamlConfigReader() { getValueAsString(node, "origin")?.let { origin -> ws.origin = URI(origin) } - ws.basicAuth = readBasicAuth(node) + ws.basicAuth = authConfigReader.readClientBasicAuth(node) } } } else { @@ -97,7 +94,7 @@ class UpstreamsConfigReader : YamlConfigReader() { getValueAsInt(connConfigNode, "port")?.let { connection.port = it } - connection.auth = readTls(connConfigNode) + connection.auth = authConfigReader.readClientTls(connConfigNode) } else { log.error("Upstream at #0 has invalid configuration") } @@ -194,27 +191,4 @@ class UpstreamsConfigReader : YamlConfigReader() { return options } - private fun readBasicAuth(node: MappingNode?): UpstreamsConfig.BasicAuth? { - return getMapping(node, "basic-auth")?.let { authNode -> - val username = getValueAsString(authNode, "username") - val password = getValueAsString(authNode, "password") - if (username != null && password != null) { - UpstreamsConfig.BasicAuth(username, password) - } else { - log.warn("Basic auth is not fully configured") - null - } - } - } - - private fun readTls(node: MappingNode?): UpstreamsConfig.TlsAuth? { - return getMapping(node, "tls")?.let { authNode -> - val auth = UpstreamsConfig.TlsAuth() - auth.ca = getValueAsString(authNode, "ca") - auth.certificate = getValueAsString(authNode, "certificate") - auth.key = getValueAsString(authNode, "key") - auth - } - } - } \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/YamlConfigReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/YamlConfigReader.kt index 8aff504d..8bd024b5 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/YamlConfigReader.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/YamlConfigReader.kt @@ -16,14 +16,26 @@ package io.emeraldpay.dshackle.config import io.emeraldpay.grpc.Chain +import org.yaml.snakeyaml.Yaml import org.yaml.snakeyaml.nodes.CollectionNode import org.yaml.snakeyaml.nodes.MappingNode import org.yaml.snakeyaml.nodes.Node import org.yaml.snakeyaml.nodes.ScalarNode +import java.io.InputStream +import java.io.InputStreamReader open class YamlConfigReader { private val envVariables = EnvVariables() + fun readNode(input: String): MappingNode { + return readNode(input.byteInputStream()) + } + + fun readNode(input: InputStream): MappingNode { + val yaml = Yaml() + return asMappingNode(yaml.compose(InputStreamReader(input))) + } + protected fun hasAny(mappingNode: MappingNode?, key: String): Boolean { if (mappingNode == null) { return false diff --git a/src/main/kotlin/io/emeraldpay/dshackle/proxy/ProxyServer.kt b/src/main/kotlin/io/emeraldpay/dshackle/proxy/ProxyServer.kt index da401a0f..a82d907d 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/proxy/ProxyServer.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/proxy/ProxyServer.kt @@ -17,9 +17,11 @@ package io.emeraldpay.dshackle.proxy import io.emeraldpay.api.proto.BlockchainOuterClass import io.emeraldpay.api.proto.Common +import io.emeraldpay.dshackle.TlsSetup import io.emeraldpay.dshackle.config.ProxyConfig import io.emeraldpay.dshackle.rpc.NativeCall import io.netty.buffer.Unpooled +import io.netty.handler.ssl.SslContextBuilder import org.reactivestreams.Publisher import org.slf4j.LoggerFactory import org.springframework.http.HttpHeaders @@ -29,6 +31,7 @@ import reactor.netty.http.server.HttpServer import reactor.netty.http.server.HttpServerRequest import reactor.netty.http.server.HttpServerResponse import reactor.netty.http.server.HttpServerRoutes +import java.io.File import java.util.function.BiFunction /** @@ -38,7 +41,8 @@ class ProxyServer( private var config: ProxyConfig, private val readRpcJson: ReadRpcJson, private val writeRpcJson: WriteRpcJson, - private val nativeCall: NativeCall + private val nativeCall: NativeCall, + private val tlsSetup: TlsSetup ) { companion object { @@ -51,9 +55,17 @@ class ProxyServer( return } log.info("Listening Proxy on ${config.host}:${config.port}") - val server: DisposableServer = HttpServer.create() + var serverBuilder = HttpServer.create() .host(config.host) .port(config.port) + + config.tls?.let { tls -> + tlsSetup.setupServer("proxy", tls)?.let { sslContext -> + serverBuilder = serverBuilder.secure { secure -> secure.sslContext(sslContext) } + } + } + + val server: DisposableServer = serverBuilder .route(this::setupRoutes) .bindNow() } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumWs.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumWs.kt index fffb3eae..ae1cb898 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumWs.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumWs.kt @@ -18,6 +18,7 @@ package io.emeraldpay.dshackle.upstream.ethereum import io.emeraldpay.dshackle.Defaults import io.emeraldpay.dshackle.cache.Caches import io.emeraldpay.dshackle.cache.CachesEnabled +import io.emeraldpay.dshackle.config.AuthConfig import io.emeraldpay.dshackle.config.UpstreamsConfig import io.emeraldpay.dshackle.reader.EmptyReader import io.emeraldpay.dshackle.reader.Reader @@ -45,7 +46,7 @@ class EthereumWs( .builder>() .name("new-blocks") .build() - var basicAuth: UpstreamsConfig.BasicAuth? = null + var basicAuth: AuthConfig.ClientBasicAuth? = null private var blockCache: Reader> = EmptyReader() diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/GrpcUpstreams.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/GrpcUpstreams.kt index 89be5917..41824419 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/GrpcUpstreams.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/GrpcUpstreams.kt @@ -20,6 +20,7 @@ import io.emeraldpay.api.proto.BlockchainOuterClass import io.emeraldpay.api.proto.ReactorBlockchainGrpc import io.emeraldpay.dshackle.Defaults import io.emeraldpay.dshackle.FileResolver +import io.emeraldpay.dshackle.config.AuthConfig import io.emeraldpay.dshackle.config.UpstreamsConfig import io.emeraldpay.dshackle.upstream.UpstreamAvailability import io.emeraldpay.dshackle.startup.UpstreamChange @@ -45,7 +46,7 @@ class GrpcUpstreams( private val host: String, private val port: Int, private val objectMapper: ObjectMapper, - private val auth: UpstreamsConfig.TlsAuth? = null, + private val auth: AuthConfig.ClientTlsAuth? = null, private val fileResolver: FileResolver ) { private val log = LoggerFactory.getLogger(GrpcUpstreams::class.java) @@ -133,7 +134,7 @@ class GrpcUpstreams( return Flux.fromIterable(removed + added) } - internal fun withTls(auth: UpstreamsConfig.TlsAuth): SslContext { + internal fun withTls(auth: AuthConfig.ClientTlsAuth): SslContext { val sslContext = SslContextBuilder.forClient() .clientAuth(ClientAuth.REQUIRE) sslContext.trustManager(fileResolver.resolve(auth.ca!!).inputStream()) diff --git a/src/test/groovy/io/emeraldpay/dshackle/TlsSetupSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/TlsSetupSpec.groovy new file mode 100644 index 00000000..d3e4fe8d --- /dev/null +++ b/src/test/groovy/io/emeraldpay/dshackle/TlsSetupSpec.groovy @@ -0,0 +1,191 @@ +/** + * Copyright (c) 2020 ETCDEV GmbH + * + * 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 io.emeraldpay.dshackle + +import io.emeraldpay.dshackle.config.AuthConfig +import io.netty.handler.ssl.ClientAuth +import io.netty.handler.ssl.OpenSslServerContext +import spock.lang.Specification +import sun.security.x509.X509CertImpl + +class TlsSetupSpec extends Specification { + + TlsSetup tlsSetup = new TlsSetup(new FileResolver(new File("src/test/resources/tls-local"))) + + def "TLS disabled"() { + setup: + def config = new AuthConfig.ServerTlsAuth( + enabled: false + ) + when: + def act = tlsSetup.setupServer("test", config) + then: + act == null + } + + def "TLS enabled"() { + setup: + def config = new AuthConfig.ServerTlsAuth( + enabled: true, + certificate: "127.0.0.1.crt", + key: "127.0.0.1.p8.key" + ) + when: + def act = tlsSetup.setupServer("test", config) + then: + act != null + act.server + !act.client + with((OpenSslServerContext) act) { + act.clientAuth == ClientAuth.NONE + with((X509CertImpl) keyCertChain[0]) { + getIssuerDN().name == "CN=ca.myhost.dev, OU=Blockchain CA, O=My Company" + } + } + } + + def "TLS enabled and required from client"() { + setup: + def config = new AuthConfig.ServerTlsAuth( + enabled: true, + certificate: "127.0.0.1.crt", + key: "127.0.0.1.p8.key", + clientRequire: true, + clientCa: "ca.myhost.dev.crt" + ) + when: + def act = tlsSetup.setupServer("test", config) + then: + act != null + act.server + !act.client + with((OpenSslServerContext) act) { + act.clientAuth == ClientAuth.REQUIRE + with((X509CertImpl) keyCertChain[0]) { + getIssuerDN().name == "CN=ca.myhost.dev, OU=Blockchain CA, O=My Company" + } + } + } + + def "Fail if certificate not set"() { + setup: + def config = new AuthConfig.ServerTlsAuth( + enabled: true, + key: "127.0.0.1.p8.key", + ) + when: + tlsSetup.setupServer("test", config) + then: + def t = thrown(IllegalArgumentException) + t.message == "Certificate not set" + } + + def "Fail if certificate key not set"() { + setup: + def config = new AuthConfig.ServerTlsAuth( + enabled: true, + certificate: "127.0.0.1.crt" + ) + when: + tlsSetup.setupServer("test", config) + then: + def t = thrown(IllegalArgumentException) + t.message == "Certificate Key not set" + } + + def "Fail if certificate not exists"() { + setup: + def config = new AuthConfig.ServerTlsAuth( + enabled: true, + certificate: "none.crt", + key: "127.0.0.1.p8.key", + ) + when: + tlsSetup.setupServer("test", config) + then: + def t = thrown(IllegalArgumentException) + } + + def "Fail if certificate key not exists"() { + setup: + def config = new AuthConfig.ServerTlsAuth( + enabled: true, + certificate: "127.0.0.1.crt", + key: "none.p8.key", + ) + when: + tlsSetup.setupServer("test", config) + then: + def t = thrown(IllegalArgumentException) + } + + def "Fail if certificate key is invalid"() { + setup: + def config = new AuthConfig.ServerTlsAuth( + enabled: true, + certificate: "127.0.0.1.crt", + key: "127.0.0.1.key", + ) + when: + tlsSetup.setupServer("test", config) + then: + def t = thrown(IllegalArgumentException) + } + + def "Fail if client certificate not set but required"() { + setup: + def config = new AuthConfig.ServerTlsAuth( + enabled: true, + certificate: "127.0.0.1.crt", + key: "127.0.0.1.p8.key", + clientRequire: true + ) + when: + tlsSetup.setupServer("test", config) + then: + def t = thrown(IllegalArgumentException) + } + + def "Fail if client certificate not exists"() { + setup: + def config = new AuthConfig.ServerTlsAuth( + enabled: true, + certificate: "127.0.0.1.crt", + key: "127.0.0.1.p8.key", + clientRequire: true, + clientCa: "none.crt" + ) + when: + tlsSetup.setupServer("test", config) + then: + def t = thrown(IllegalArgumentException) + } + + def "Fail if client certificate is invalid"() { + setup: + def config = new AuthConfig.ServerTlsAuth( + enabled: true, + certificate: "127.0.0.1.crt", + key: "127.0.0.1.p8.key", + clientRequire: true, + clientCa: "ca.myhost.dev.key" + ) + when: + tlsSetup.setupServer("test", config) + then: + def t = thrown(IllegalArgumentException) + } +} diff --git a/src/test/groovy/io/emeraldpay/dshackle/config/AuthConfigReaderSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/config/AuthConfigReaderSpec.groovy new file mode 100644 index 00000000..2365dcef --- /dev/null +++ b/src/test/groovy/io/emeraldpay/dshackle/config/AuthConfigReaderSpec.groovy @@ -0,0 +1,62 @@ +package io.emeraldpay.dshackle.config + +import spock.lang.Specification + +class AuthConfigReaderSpec extends Specification { + + AuthConfigReader reader = new AuthConfigReader() + + def "Read basic-auth for client"() { + setup: + def yaml = + "basic-auth:\n" + + " username: 9c199ad8f281f20154fc258fe41a6814\n" + + " password: 258fe4149c199ad8f2811a68f20154fc" + when: + def act = reader.readClientBasicAuth(reader.readNode(yaml)) + then: + act != null + act.username == "9c199ad8f281f20154fc258fe41a6814" + act.password == "258fe4149c199ad8f2811a68f20154fc" + } + + def "Read tls for client"() { + setup: + def yaml = + "tls:\n" + + " ca: /etc/ca.myservice.com.crt\n" + + " certificate: /etc/client1.myservice.com.crt\n" + + " key: /etc/client1.myservice.com.key" + when: + def act = reader.readClientTls(reader.readNode(yaml)) + then: + act != null + act.ca == "/etc/ca.myservice.com.crt" + act.certificate == "/etc/client1.myservice.com.crt" + act.key == "/etc/client1.myservice.com.key" + } + + def "Read tls for server"() { + setup: + def yaml = + "tls:\n" + + " enabled: true\n" + + " server:\n" + + " certificate: \"/etc/client1.myservice.com.crt\"\n" + + " key: \"/etc/client1.myservice.com.key\"\n" + + " client:\n" + + " require: false\n" + + " ca: /etc/ca.myservice.com.crt" + when: + def act = reader.readServerTls(reader.readNode(yaml)) + then: + act != null + act.enabled != null + act.enabled + act.certificate == "/etc/client1.myservice.com.crt" + act.key == "/etc/client1.myservice.com.key" + act.clientRequire != null + !act.clientRequire + act.clientCa == "/etc/ca.myservice.com.crt" + } +} diff --git a/src/test/groovy/io/emeraldpay/dshackle/config/UpstreamsConfigReaderSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/config/UpstreamsConfigReaderSpec.groovy index 0c904d25..d4d8abed 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/config/UpstreamsConfigReaderSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/config/UpstreamsConfigReaderSpec.groovy @@ -64,7 +64,7 @@ class UpstreamsConfigReaderSpec extends Specification { with((UpstreamsConfig.EthereumConnection)connection) { rpc.url == new URI("https://mainnet.infura.io/v3/fa28c968191849c1aff541ad1d8511f2") rpc.basicAuth != null - with((UpstreamsConfig.BasicAuth)rpc.basicAuth) { + with((AuthConfig.ClientBasicAuth) rpc.basicAuth) { username == "4fc258fe41a68149c199ad8f281f2015" password == "1a68f20154fc258fe4149c199ad8f281" } diff --git a/src/test/groovy/io/emeraldpay/dshackle/proxy/ProxyServerSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/proxy/ProxyServerSpec.groovy index e8257952..ff6cfeaa 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/proxy/ProxyServerSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/proxy/ProxyServerSpec.groovy @@ -17,6 +17,7 @@ package io.emeraldpay.dshackle.proxy import io.emeraldpay.api.proto.BlockchainOuterClass import io.emeraldpay.api.proto.Common +import io.emeraldpay.dshackle.TlsSetup import io.emeraldpay.dshackle.config.ProxyConfig import io.emeraldpay.dshackle.rpc.NativeCall import io.emeraldpay.dshackle.test.TestingCommons @@ -42,7 +43,8 @@ class ProxyServerSpec extends Specification { new ProxyConfig(), new ReadRpcJson(TestingCommons.objectMapper()), writeRpcJson, - nativeCall + nativeCall, + new TlsSetup(TestingCommons.fileResolver()) ) def call = new ProxyCall(ProxyCall.RpcType.SINGLE) diff --git a/src/test/groovy/io/emeraldpay/dshackle/test/TestingCommons.groovy b/src/test/groovy/io/emeraldpay/dshackle/test/TestingCommons.groovy index d7e7158f..1c65891c 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/test/TestingCommons.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/test/TestingCommons.groovy @@ -19,6 +19,7 @@ import com.fasterxml.jackson.core.Version import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.module.SimpleModule +import io.emeraldpay.dshackle.FileResolver import io.emeraldpay.dshackle.cache.Caches import io.emeraldpay.dshackle.cache.CachesFactory import io.emeraldpay.dshackle.upstream.AggregatedUpstream @@ -79,4 +80,8 @@ class TestingCommons { static CachesFactory emptyCaches() { return new CachesFactory(objectMapper(), new StandardEnvironment()) } + + static FileResolver fileResolver() { + return new FileResolver(new File("src/test/resources")) + } } diff --git a/src/test/resources/tls-local/127.0.0.1.crt b/src/test/resources/tls-local/127.0.0.1.crt new file mode 100644 index 00000000..82eb3afb --- /dev/null +++ b/src/test/resources/tls-local/127.0.0.1.crt @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEmDCCAoCgAwIBAgIQDTubkUXjTKQ5k1GApHel5jANBgkqhkiG9w0BAQsFADBF +MRMwEQYDVQQKEwpNeSBDb21wYW55MRYwFAYDVQQLEw1CbG9ja2NoYWluIENBMRYw +FAYDVQQDEw1jYS5teWhvc3QuZGV2MB4XDTIwMDMyMDIzMTEyN1oXDTIxMDkyMDIz +MTAzNFowRTETMBEGA1UEChMKTXkgQ29tcGFueTEaMBgGA1UECxMRQmxvY2tjaGFp +biBTZXJ2ZXIxEjAQBgNVBAMTCTEyNy4wLjAuMTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBANMdYDhfCvUhcEPcKIQXvxFrIy2HUOM9soplutOAUv6dbTOs +TeCcpUVgt62sJZcPnE2Iy1nYHeVl+N7vuovUxtzYtfW4rGWprjth4rVcRHkWPwz5 +QcyH6GwxsUi/0dEtboOxyqqf3W5C+V0+AJYGZ/bI94pGFOMC4+2HTkF8gCDORWKu +SG67SqiD9HL0LDrEdpq+IJOevW7t/Oqz+DWcO+VAi1T62xup4JL+WJI1UGtDPeET +0SRq3kXb8n4te58Y6NXjyodYlon+BESY42JPw4Uw6rjcmDHQVphg5Dtl343OcfN9 +CNehtXavuszQdRU9uY1RUdVsvg44xhR60huFJ80CAwEAAaOBgzCBgDAOBgNVHQ8B +Af8EBAMCA7gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQW +BBT9J7UEZRalIioijC1h4WsrRr9bpDAfBgNVHSMEGDAWgBRkRRFBxmIJWihWWm42 +q3T1nI5IYDAPBgNVHREECDAGhwR/AAABMA0GCSqGSIb3DQEBCwUAA4ICAQAC3AN0 +8Ptp6xCOhZ8ja/RLKdF97WGyJS04KoPXyyB0qmh1zpYzHtcUcy6Zw41CiwS9d+7s +XfEi780ifmL9XOGxNWTFBvwLMJGq4ukdt//q/y5nhNdINbbZ+LNvZna9gPEBW0cK +YR5HEqMMk2eW2Lgo/pg4ska7leI/7ESoVqehNv3Pt9ewVLq38xecRC1K/Q2Z6vlU +zt+RbLYW7W7jIXc/cH7TUCDG9sT4tRptTlJdq9sBPVFFBXEZ+QZlY6b0ipeelqnL +Wrbsc8XWc/WogEEovBgDpqtqas+jfPI5x9DnopfaSeMP9m9BXYEk+D2nnukw5anp +tid4kSqTUOL8q8wTkEnvTd8nqXfFTC3H+Xe5OMGifXkJTRoQsqFS4M/2jLI36MpN +KHlNG3O5B1ML+vB0SedxaOD4PwWYQwq0kDWMOHOhe4OwyGgdr8MmPCzNFLbx5ppm +apMAco1RXzN/MaBJsYaq5MrTJw4XF8H1CMIEb4GHWb/9gLf4cPetFWzud48eg+YL +XADs+uzCGB0+lDMllVpyU+BSZtDUF77PWk1C5/33JNMg8Wzdj/tXs489B6AG1yED +fqcihzkQNp5WvwCtO5gY/+AYWZQ/zD0ICqjPfzofs7rb+s3qi8mRVfJJ/qOdGzxw +9NZPNkcfB8utd/pGbr4jEeHh+8Ds9vUgZFLXNw== +-----END CERTIFICATE----- diff --git a/src/test/resources/tls-local/127.0.0.1.key b/src/test/resources/tls-local/127.0.0.1.key new file mode 100644 index 00000000..044cdc53 --- /dev/null +++ b/src/test/resources/tls-local/127.0.0.1.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEA0x1gOF8K9SFwQ9wohBe/EWsjLYdQ4z2yimW604BS/p1tM6xN +4JylRWC3rawllw+cTYjLWdgd5WX43u+6i9TG3Ni19bisZamuO2HitVxEeRY/DPlB +zIfobDGxSL/R0S1ug7HKqp/dbkL5XT4AlgZn9sj3ikYU4wLj7YdOQXyAIM5FYq5I +brtKqIP0cvQsOsR2mr4gk569bu386rP4NZw75UCLVPrbG6ngkv5YkjVQa0M94RPR +JGreRdvyfi17nxjo1ePKh1iWif4ERJjjYk/DhTDquNyYMdBWmGDkO2Xfjc5x830I +16G1dq+6zNB1FT25jVFR1Wy+DjjGFHrSG4UnzQIDAQABAoIBAQCX1uj9ol4fMI2u +QQpi9zFVNdl3RXvH9PgU0lYtCH6o4lFIeQUKJ6A25fk10Dq5C2E/4sNfOzFFbLIy +pfll2QOuk69LrCdSd1f5Hc4Q4uvcq0Nt8ViB4r4oExWPXWdrK2HxFk7NqW15gHIZ +vh5tyO29cY2Yxg7/t3R3wnlmYEVHUcS7HmhzgDveNzA0VLza3765ntgwXypY8N2j +heEQC1h5kMCurcKJyRXmlsXPRWizX0UBWDrMHFeqyhrH0BlRSFTNC3sKmyYaJQmp +daPNRr4zO0yfm8utVSbNHX2OM5DpIO1Ecq9Sd43QI+ATAtxFrhPoYK1rwll267CV +cJCRbz+BAoGBAPzz/1Xq8s1lGZ6eWz7H1JlzxYLH+TzQfrV1ym7xJgR/b3rVXiJ+ +D+qL8zUJDa5xZyflXB7zCg4I7ALmNwMJzVLdIOpEntHK2NtRJ2rp4qH+kMXojays +zOGYfbRLNVe+mgAK9Pu8eOi8NzXqkB/S8rml3xqSOpUFsvlc2qiYhGdXAoGBANWo +XcQDpisRFcrn3J0+pKU57ZIRjxyOTDlEwH7k+x+PprCRFki80kW22u4l22FdDaip +s4vCuAm5tmEogEjINU6ZhSKHxonjaGXfzuZ3gAMk/PN7zFazlgfYEKng+fa1YuZ+ +3Ubzq6py8enoffJ/PSF/lClKlV5sxjyilxeZmOd7AoGBAMtaJHUf0l4I3tXDnLsV +zvYmOixvMxEO1C5xKXKS7utCv45SJcE48vat17FVO+h3RmSuYKaI4BZ0Wbfi92q7 +4JKzLpjm3X7uwfNehH/Q0t9EVYKk9/BPYs4h1zywEYwesJNEO7p8w/7mAMSZc5AB ++BwDGx6zW+EdmcoaOba8FgU1AoGAV2WftWaoukUq3O0rWUceolenznBQUiYDGAn/ +k+imsKpaTS+MJgTXHp1FwNTLgHBH/g4s26azEYdeCzA+CYecBqLVyuIvXIghVErQ +n4WSX7bpoc+qLm0Xme3QIy1cEobwBckvSq6yMe8C9eOcYW2a2/EL8jgIEa/9ByCb +HZQ+77ECgYEAxR1eoxc/XV9rcftdaRl7+Db9Qvnhfwu7MFQ3lgomol1N1ckSjwnO +wo/HX4+8cMS5QN11d8l2hf+7TyuRCrQBLrYYkrVWb4Z+ote3ejrAsDg90xjOFYWf +MWSy7kPeDl3JTEdNPFIIa28EQhZYupD0ihBoVspz2eQ1Y66BOfqC9nU= +-----END RSA PRIVATE KEY----- diff --git a/src/test/resources/tls-local/127.0.0.1.p8.key b/src/test/resources/tls-local/127.0.0.1.p8.key new file mode 100644 index 00000000..587f0346 --- /dev/null +++ b/src/test/resources/tls-local/127.0.0.1.p8.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDTHWA4Xwr1IXBD +3CiEF78RayMth1DjPbKKZbrTgFL+nW0zrE3gnKVFYLetrCWXD5xNiMtZ2B3lZfje +77qL1Mbc2LX1uKxlqa47YeK1XER5Fj8M+UHMh+hsMbFIv9HRLW6Dscqqn91uQvld +PgCWBmf2yPeKRhTjAuPth05BfIAgzkVirkhuu0qog/Ry9Cw6xHaaviCTnr1u7fzq +s/g1nDvlQItU+tsbqeCS/liSNVBrQz3hE9Ekat5F2/J+LXufGOjV48qHWJaJ/gRE +mONiT8OFMOq43Jgx0FaYYOQ7Zd+NznHzfQjXobV2r7rM0HUVPbmNUVHVbL4OOMYU +etIbhSfNAgMBAAECggEBAJfW6P2iXh8wja5BCmL3MVU12XdFe8f0+BTSVi0Ifqji +UUh5BQonoDbl+TXQOrkLYT/iw187MUVssjKl+WXZA66Tr0usJ1J3V/kdzhDi69yr +Q23xWIHivigTFY9dZ2srYfEWTs2pbXmAchm+Hm3I7b1xjZjGDv+3dHfCeWZgRUdR +xLseaHOAO943MDRUvNrfvrme2DBfKljw3aOF4RALWHmQwK6twonJFeaWxc9FaLNf +RQFYOswcV6rKGsfQGVFIVM0LewqbJholCal1o81GvjM7TJ+by61VJs0dfY4zkOkg +7URyr1J3jdAj4BMC3EWuE+hgrWvCWXbrsJVwkJFvP4ECgYEA/PP/VeryzWUZnp5b +PsfUmXPFgsf5PNB+tXXKbvEmBH9vetVeIn4P6ovzNQkNrnFnJ+VcHvMKDgjsAuY3 +AwnNUt0g6kSe0crY21Enauniof6QxeiNrKzM4Zh9tEs1V76aAAr0+7x46Lw3NeqQ +H9LyuaXfGpI6lQWy+VzaqJiEZ1cCgYEA1ahdxAOmKxEVyufcnT6kpTntkhGPHI5M +OUTAfuT7H4+msJEWSLzSRbba7iXbYV0NqKmzi8K4Cbm2YSiASMg1TpmFIofGieNo +Zd/O5neAAyT883vMVrOWB9gQqeD59rVi5n7dRvOrqnLx6eh98n89IX+UKUqVXmzG +PKKXF5mY53sCgYEAy1okdR/SXgje1cOcuxXO9iY6LG8zEQ7ULnEpcpLu60K/jlIl +wTjy9q3XsVU76HdGZK5gpojgFnRZt+L3arvgkrMumObdfu7B816Ef9DS30RVgqT3 +8E9iziHXPLARjB6wk0Q7unzD/uYAxJlzkAH4HAMbHrNb4R2Zyho5trwWBTUCgYBX +ZZ+1Zqi6RSrc7StZRx6iV6fOcFBSJgMYCf+T6KawqlpNL4wmBNcenUXA1MuAcEf+ +DizbprMRh14LMD4Jh5wGotXK4i9ciCFUStCfhZJftumhz6oubReZ7dAjLVwShvAF +yS9KrrIx7wL145xhbZrb8QvyOAgRr/0HIJsdlD7vsQKBgQDFHV6jFz9dX2tx+11p +GXv4Nv1C+eF/C7swVDeWCiaiXU3VyRKPCc7Cj8dfj7xwxLlA3XV3yXaF/7tPK5EK +tAEuthiStVZvhn6i17d6OsCwOD3TGM4VhZ8xZLLuQ94OXclMR008UghrbwRCFli6 +kPSKEGhWynPZ5DVjroE5+oL2dQ== +-----END PRIVATE KEY----- diff --git a/src/test/resources/tls-local/ca.myhost.dev.crt b/src/test/resources/tls-local/ca.myhost.dev.crt new file mode 100644 index 00000000..a612bb06 --- /dev/null +++ b/src/test/resources/tls-local/ca.myhost.dev.crt @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFSjCCAzKgAwIBAgIBATANBgkqhkiG9w0BAQsFADBFMRMwEQYDVQQKEwpNeSBD +b21wYW55MRYwFAYDVQQLEw1CbG9ja2NoYWluIENBMRYwFAYDVQQDEw1jYS5teWhv +c3QuZGV2MB4XDTIwMDMyMDIzMTAzNloXDTIxMDkyMDIzMTAzNFowRTETMBEGA1UE +ChMKTXkgQ29tcGFueTEWMBQGA1UECxMNQmxvY2tjaGFpbiBDQTEWMBQGA1UEAxMN +Y2EubXlob3N0LmRldjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL+v +wvbjRXpdjlBioUFFnnL3uCgfVMHw1zad8z36bBXr/MQDjAVgTfGrzI3FM3YVPTqb +peS7DuVfXOr+ULBU/mq6QuRgLTk9ynVbqcezajECtLZgbudkStF4ESFArjuaJ3IY +kYtL8TEKID31+EJIDyjZOwBjb87QoPaEICTIXLDYkoFv3heg9k8npYItWiKJpuJp +BSj23gCBm8zx/uqTZQOa1bEhhSBSI/okmM0dy+rGgquwZamH1HVH5xm7rok/wYry +EVb1AAT+j42Ug0qihF8mJrvlXLaN0G0QHAiqCf+wnS3DzBnaLPVLNcGJSeY6O8m+ +HdCo+/y8WBJU6y+50HFk4qLVAoTYswn3X362W3q48DeCjvImty3g2vvfsVqvKkVd +bQy6C4ok3PXjXyhaxdFNx8f4SUXuXIfXyUMdSt2MNN0r4QY8KAWC9Kkic75Ui8sG +wHw2sBtEQxj1EX/QRAiqWeLT5J/CL3fHhmas+NaICeU3lH7pyLgKjs8+aZ44g9dt +g+AHEBqQvFMRW6wcIJLlWPT4zUo//LWGIbnSUGzz34frzVljg5f/ZsxxTTa10qGT +VOb6Jdln12JvnlObMdr45HSFFAXZrJB+g3z6/K9mFml29Py6dnDgQAFdlRW/LLns +4yEx5YXYrkCRse5PUHqt/APVvUp9kbWGnSB0EeoNAgMBAAGjRTBDMA4GA1UdDwEB +/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBRkRRFBxmIJWihW +Wm42q3T1nI5IYDANBgkqhkiG9w0BAQsFAAOCAgEAFsXRgMoPT4RYSaB9oO2e/1vc +3e2rkNG2Bd3twKV33r8hUuqH9lm1EcBOIoWkWSZC4bbAvNtaoTm/pEHgBjKHTiPg +liVCuL66OmXWpL0LjGibialKeyowMkJ5S4K/p7vdPvIYA7jAxL7LNDwcoLyMm+22 +GmCqrFp8tmy3ELj1uJe5fvI69WME7zGG6rLRGVT+Hc7gtKwk/iDIPvCF4UkQ0Jai +8RWlNy4G5BBSMCY8AWkRsnmVg/njZr8e/CMuOlLbRtSX8Nx/JAoR8bwszy7N0vCi +OkTELZNjo7w0lXWtINxNb5ySnHF6WSxFjmdiAgWv0ka/KBtR/+9PMMTv1g0y6c5W +dQpSTCJyzHhYb8Yu12tj1jaZDrqT0rRD8klZTuPxDtIbuhnpairjIRygRsVOEtYv +RXFWdjp/keD0A41TNtGj5eNsACPBOcYZlsb9AGqqmSQfRJ4R+YJGsBnECmIZpsva +VvX2eKI7jAZcYjHZIzqASnNFxlIM8jTxi2ifpS/7T93/RIl2Es5PJ3SX+FniOtN5 +ajlzWSo7SVvgaYOOeOybKZq4cBmQ/R3XAnK1NSgMgHWZv1NOgHZEhC9kpvj0QQnE +WzjC3KDBLzhRpe678C5iorf9FXMkvYfb1NVl5UYYa9/C4aaj4Jt4SiaIaqsq27fP +wkLM79un1zrcbPHQUcA= +-----END CERTIFICATE----- diff --git a/src/test/resources/tls-local/ca.myhost.dev.key b/src/test/resources/tls-local/ca.myhost.dev.key new file mode 100644 index 00000000..e610adbb --- /dev/null +++ b/src/test/resources/tls-local/ca.myhost.dev.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/src/test/resources/tls-local/ca.myhost.dev.p8.key b/src/test/resources/tls-local/ca.myhost.dev.p8.key new file mode 100644 index 00000000..badd5c0d --- /dev/null +++ b/src/test/resources/tls-local/ca.myhost.dev.p8.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +/rUweL14cs7iUki+lWEGjjvXmOFkORI= +-----END PRIVATE KEY-----