diff --git a/docs/source/corda-configuration-file.rst b/docs/source/corda-configuration-file.rst index 7743bd1740..3632bdd1d4 100644 --- a/docs/source/corda-configuration-file.rst +++ b/docs/source/corda-configuration-file.rst @@ -205,6 +205,13 @@ absolute path to the node's base directory. .. note:: This is temporary feature for onboarding network participants that limits their visibility for privacy reasons. +:tlsCertCrlDistPoint: CRL distribution point (i.e. URL) for the TLS certificate. Default value is NULL, which indicates no CRL availability for the TLS certificate. + Note: If crlCheckSoftFail is FALSE (meaning that there is the strict CRL checking mode) this value needs to be set. + +:tlsCertCrlIssuer: CRL issuer (given in the X500 name format) for the TLS certificate. Default value is NULL, + which indicates that the issuer of the TLS certificate is also the issuer of the CRL. + Note: If this parameter is set then the tlsCertCrlDistPoint needs to be set as well. + Examples -------- @@ -294,4 +301,4 @@ path to the node's base directory. :permissions: A list of permissions for starting flows via RPC. To give the user the permission to start the flow ``foo.bar.FlowClass``, add the string ``StartFlow.foo.bar.FlowClass`` to the list. If the list contains the string ``ALL``, the user can start any flow via RPC. This value is intended for administrator - users and for development. + users and for development. \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index dda74f670a..da3ca0d97f 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -12,6 +12,7 @@ import net.corda.node.services.config.rpc.NodeRpcOptions import net.corda.nodeapi.internal.config.* import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.tools.shell.SSHDConfiguration +import org.bouncycastle.asn1.x500.X500Name import org.slf4j.Logger import java.net.URL import java.nio.file.Path @@ -52,6 +53,8 @@ interface NodeConfiguration : NodeSSLConfiguration { // do not change this value without syncing it with ScheduledFlowsDrainingModeTest val drainingModePollPeriod: Duration get() = Duration.ofSeconds(5) val extraNetworkMapKeys: List + val tlsCertCrlDistPoint: URL? + val tlsCertCrlIssuer: String? fun validate(): List @@ -133,6 +136,8 @@ data class NodeConfigurationImpl( override val crlCheckSoftFail: Boolean, override val dataSourceProperties: Properties, override val compatibilityZoneURL: URL? = null, + override val tlsCertCrlDistPoint: URL? = null, + override val tlsCertCrlIssuer: String? = null, override val rpcUsers: List, override val security: SecurityConfiguration? = null, override val verifierType: VerifierType, @@ -179,10 +184,29 @@ data class NodeConfigurationImpl( }.asOptions(fallbackSslOptions) } + private fun validateTlsCertCrlConfig(): List { + val errors = mutableListOf() + if (tlsCertCrlIssuer != null) { + if (tlsCertCrlDistPoint == null) { + errors += "tlsCertCrlDistPoint needs to be specified when tlsCertCrlIssuer is not NULL" + } + try { + X500Name(tlsCertCrlIssuer) + } catch (e: Exception) { + errors += "Error when parsing tlsCertCrlIssuer: ${e.message}" + } + } + if (!crlCheckSoftFail && tlsCertCrlDistPoint == null) { + errors += "tlsCertCrlDistPoint needs to be specified when crlCheckSoftFail is FALSE" + } + return errors + } + override fun validate(): List { val errors = mutableListOf() errors += validateDevModeOptions() errors += validateRpcOptions(rpcOptions) + errors += validateTlsCertCrlConfig() return errors } diff --git a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt index 8bb223694c..302afeee46 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt @@ -3,6 +3,7 @@ package net.corda.node.utilities.registration import net.corda.core.crypto.Crypto import net.corda.core.identity.CordaX500Name import net.corda.core.internal.* +import net.corda.core.utilities.contextLogger import net.corda.node.NodeRegistrationOption import net.corda.node.services.config.NodeConfiguration import net.corda.nodeapi.internal.config.SSLConfiguration @@ -12,6 +13,7 @@ import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_CA import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_TLS import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA +import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.openssl.jcajce.JcaPEMWriter import org.bouncycastle.util.io.pem.PemObject import java.io.StringWriter @@ -216,6 +218,10 @@ class NodeRegistrationHelper(private val config: NodeConfiguration, certService: CORDA_CLIENT_CA, CertRole.NODE_CA) { + companion object { + val logger = contextLogger() + } + override fun onSuccess(nodeCAKeyPair: KeyPair, certificates: List) { createSSLKeystore(nodeCAKeyPair, certificates) createTruststore(certificates.last()) @@ -230,7 +236,10 @@ class NodeRegistrationHelper(private val config: NodeConfiguration, certService: certificates.first(), nodeCAKeyPair, config.myLegalName.x500Principal, - sslKeyPair.public) + sslKeyPair.public, + crlDistPoint = config.tlsCertCrlDistPoint?.toString(), + crlIssuer = if (config.tlsCertCrlIssuer != null) X500Name(config.tlsCertCrlIssuer) else null) + logger.info("Generated TLS certificate: $sslCert") setPrivateKey(CORDA_CLIENT_TLS, sslKeyPair.private, listOf(sslCert) + certificates) } println("SSL private key and certificate stored in ${config.sslKeystore}.") diff --git a/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt b/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt index 891f0fff2a..71bba97ff2 100644 --- a/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt @@ -13,6 +13,7 @@ import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Test import java.net.URI +import java.net.URL import java.nio.file.Paths import java.util.* import kotlin.test.assertFalse @@ -28,6 +29,27 @@ class NodeConfigurationImplTest { configDebugOptions(false, null) } + @Test + fun `can't have tlsCertCrlDistPoint null when tlsCertCrlIssuer is given`() { + val configValidationResult = configTlsCertCrlOptions(null, "C=US, L=New York, OU=Corda, O=R3 HoldCo LLC, CN=Corda Root CA").validate() + assertTrue { configValidationResult.isNotEmpty() } + assertThat(configValidationResult.first()).contains("tlsCertCrlDistPoint needs to be specified when tlsCertCrlIssuer is not NULL") + } + + @Test + fun `tlsCertCrlIssuer validation fails when misconfigured`() { + val configValidationResult = configTlsCertCrlOptions(URL("http://test.com/crl"), "Corda Root CA").validate() + assertTrue { configValidationResult.isNotEmpty() } + assertThat(configValidationResult.first()).contains("Error when parsing tlsCertCrlIssuer:") + } + + @Test + fun `can't have tlsCertCrlDistPoint null when crlCheckSoftFail is false`() { + val configValidationResult = configTlsCertCrlOptions(null, null, false).validate() + assertTrue { configValidationResult.isNotEmpty() } + assertThat(configValidationResult.first()).contains("tlsCertCrlDistPoint needs to be specified when crlCheckSoftFail is FALSE") + } + @Test fun `check devModeOptions flag helper`() { assertTrue { configDebugOptions(true, null).shouldCheckCheckpoints() } @@ -114,8 +136,8 @@ class NodeConfigurationImplTest { return testConfiguration.copy(devMode = devMode, devModeOptions = devModeOptions) } - private fun testConfiguration(dataSourceProperties: Properties): NodeConfigurationImpl { - return testConfiguration.copy(dataSourceProperties = dataSourceProperties) + private fun configTlsCertCrlOptions(tlsCertCrlDistPoint: URL?, tlsCertCrlIssuer: String?, crlCheckSoftFail: Boolean = true): NodeConfiguration { + return testConfiguration.copy(tlsCertCrlDistPoint = tlsCertCrlDistPoint, tlsCertCrlIssuer = tlsCertCrlIssuer, crlCheckSoftFail = crlCheckSoftFail) } private val testConfiguration = testNodeConfiguration() @@ -147,7 +169,8 @@ class NodeConfigurationImplTest { devMode = true, noLocalShell = false, rpcSettings = rpcSettings, - crlCheckSoftFail = true + crlCheckSoftFail = true, + tlsCertCrlDistPoint = null ) } } diff --git a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt index 5c82a90afa..1bf49ca960 100644 --- a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt +++ b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt @@ -58,6 +58,8 @@ class NetworkRegistrationHelperTest { doReturn("cordacadevpass").whenever(it).keyStorePassword doReturn(nodeLegalName).whenever(it).myLegalName doReturn("").whenever(it).emailAddress + doReturn(null).whenever(it).tlsCertCrlDistPoint + doReturn(null).whenever(it).tlsCertCrlIssuer } }