CORDA-1476 Adding TLS certificate CRL extension point configs (#3140)

* Adding TLS certificate CRL extension point configs

* Addressing review comments

* Addressing review comments - round 3
This commit is contained in:
Michal Kit 2018-05-17 08:21:24 +01:00 committed by GitHub
parent 27803cdc9e
commit 0ee116a1d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 70 additions and 5 deletions

View File

@ -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. .. 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 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 :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 ``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 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.

View File

@ -12,6 +12,7 @@ import net.corda.node.services.config.rpc.NodeRpcOptions
import net.corda.nodeapi.internal.config.* import net.corda.nodeapi.internal.config.*
import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.tools.shell.SSHDConfiguration import net.corda.tools.shell.SSHDConfiguration
import org.bouncycastle.asn1.x500.X500Name
import org.slf4j.Logger import org.slf4j.Logger
import java.net.URL import java.net.URL
import java.nio.file.Path import java.nio.file.Path
@ -52,6 +53,8 @@ interface NodeConfiguration : NodeSSLConfiguration {
// do not change this value without syncing it with ScheduledFlowsDrainingModeTest // do not change this value without syncing it with ScheduledFlowsDrainingModeTest
val drainingModePollPeriod: Duration get() = Duration.ofSeconds(5) val drainingModePollPeriod: Duration get() = Duration.ofSeconds(5)
val extraNetworkMapKeys: List<UUID> val extraNetworkMapKeys: List<UUID>
val tlsCertCrlDistPoint: URL?
val tlsCertCrlIssuer: String?
fun validate(): List<String> fun validate(): List<String>
@ -133,6 +136,8 @@ data class NodeConfigurationImpl(
override val crlCheckSoftFail: Boolean, override val crlCheckSoftFail: Boolean,
override val dataSourceProperties: Properties, override val dataSourceProperties: Properties,
override val compatibilityZoneURL: URL? = null, override val compatibilityZoneURL: URL? = null,
override val tlsCertCrlDistPoint: URL? = null,
override val tlsCertCrlIssuer: String? = null,
override val rpcUsers: List<User>, override val rpcUsers: List<User>,
override val security: SecurityConfiguration? = null, override val security: SecurityConfiguration? = null,
override val verifierType: VerifierType, override val verifierType: VerifierType,
@ -179,10 +184,29 @@ data class NodeConfigurationImpl(
}.asOptions(fallbackSslOptions) }.asOptions(fallbackSslOptions)
} }
private fun validateTlsCertCrlConfig(): List<String> {
val errors = mutableListOf<String>()
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<String> { override fun validate(): List<String> {
val errors = mutableListOf<String>() val errors = mutableListOf<String>()
errors += validateDevModeOptions() errors += validateDevModeOptions()
errors += validateRpcOptions(rpcOptions) errors += validateRpcOptions(rpcOptions)
errors += validateTlsCertCrlConfig()
return errors return errors
} }

View File

@ -3,6 +3,7 @@ package net.corda.node.utilities.registration
import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.* import net.corda.core.internal.*
import net.corda.core.utilities.contextLogger
import net.corda.node.NodeRegistrationOption import net.corda.node.NodeRegistrationOption
import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NodeConfiguration
import net.corda.nodeapi.internal.config.SSLConfiguration 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_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_TLS import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_TLS
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA 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.openssl.jcajce.JcaPEMWriter
import org.bouncycastle.util.io.pem.PemObject import org.bouncycastle.util.io.pem.PemObject
import java.io.StringWriter import java.io.StringWriter
@ -216,6 +218,10 @@ class NodeRegistrationHelper(private val config: NodeConfiguration, certService:
CORDA_CLIENT_CA, CORDA_CLIENT_CA,
CertRole.NODE_CA) { CertRole.NODE_CA) {
companion object {
val logger = contextLogger()
}
override fun onSuccess(nodeCAKeyPair: KeyPair, certificates: List<X509Certificate>) { override fun onSuccess(nodeCAKeyPair: KeyPair, certificates: List<X509Certificate>) {
createSSLKeystore(nodeCAKeyPair, certificates) createSSLKeystore(nodeCAKeyPair, certificates)
createTruststore(certificates.last()) createTruststore(certificates.last())
@ -230,7 +236,10 @@ class NodeRegistrationHelper(private val config: NodeConfiguration, certService:
certificates.first(), certificates.first(),
nodeCAKeyPair, nodeCAKeyPair,
config.myLegalName.x500Principal, 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) setPrivateKey(CORDA_CLIENT_TLS, sslKeyPair.private, listOf(sslCert) + certificates)
} }
println("SSL private key and certificate stored in ${config.sslKeystore}.") println("SSL private key and certificate stored in ${config.sslKeystore}.")

View File

@ -13,6 +13,7 @@ import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Test import org.junit.Test
import java.net.URI import java.net.URI
import java.net.URL
import java.nio.file.Paths import java.nio.file.Paths
import java.util.* import java.util.*
import kotlin.test.assertFalse import kotlin.test.assertFalse
@ -28,6 +29,27 @@ class NodeConfigurationImplTest {
configDebugOptions(false, null) 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 @Test
fun `check devModeOptions flag helper`() { fun `check devModeOptions flag helper`() {
assertTrue { configDebugOptions(true, null).shouldCheckCheckpoints() } assertTrue { configDebugOptions(true, null).shouldCheckCheckpoints() }
@ -114,8 +136,8 @@ class NodeConfigurationImplTest {
return testConfiguration.copy(devMode = devMode, devModeOptions = devModeOptions) return testConfiguration.copy(devMode = devMode, devModeOptions = devModeOptions)
} }
private fun testConfiguration(dataSourceProperties: Properties): NodeConfigurationImpl { private fun configTlsCertCrlOptions(tlsCertCrlDistPoint: URL?, tlsCertCrlIssuer: String?, crlCheckSoftFail: Boolean = true): NodeConfiguration {
return testConfiguration.copy(dataSourceProperties = dataSourceProperties) return testConfiguration.copy(tlsCertCrlDistPoint = tlsCertCrlDistPoint, tlsCertCrlIssuer = tlsCertCrlIssuer, crlCheckSoftFail = crlCheckSoftFail)
} }
private val testConfiguration = testNodeConfiguration() private val testConfiguration = testNodeConfiguration()
@ -147,7 +169,8 @@ class NodeConfigurationImplTest {
devMode = true, devMode = true,
noLocalShell = false, noLocalShell = false,
rpcSettings = rpcSettings, rpcSettings = rpcSettings,
crlCheckSoftFail = true crlCheckSoftFail = true,
tlsCertCrlDistPoint = null
) )
} }
} }

View File

@ -58,6 +58,8 @@ class NetworkRegistrationHelperTest {
doReturn("cordacadevpass").whenever(it).keyStorePassword doReturn("cordacadevpass").whenever(it).keyStorePassword
doReturn(nodeLegalName).whenever(it).myLegalName doReturn(nodeLegalName).whenever(it).myLegalName
doReturn("").whenever(it).emailAddress doReturn("").whenever(it).emailAddress
doReturn(null).whenever(it).tlsCertCrlDistPoint
doReturn(null).whenever(it).tlsCertCrlIssuer
} }
} }