diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt index b3ee7f06ee..de1311ef78 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt @@ -22,6 +22,7 @@ import java.time.Instant import java.time.LocalDate import java.time.temporal.Temporal import java.util.* +import javax.security.auth.x500.X500Principal import kotlin.reflect.KClass import kotlin.reflect.KProperty import kotlin.reflect.KType @@ -128,6 +129,7 @@ private fun Config.getSingleValue(path: String, type: KType, onUnknownKeys: (Set Path::class -> Paths.get(getString(path)) URL::class -> URL(getString(path)) UUID::class -> UUID.fromString(getString(path)) + X500Principal::class -> X500Principal(getString(path)) CordaX500Name::class -> { when (getValue(path).valueType()) { ConfigValueType.OBJECT -> getConfig(path).parseAs(onUnknownKeys) @@ -173,6 +175,7 @@ private fun Config.getCollectionValue(path: String, type: KType, onUnknownKeys: NetworkHostAndPort::class -> getStringList(path).map(NetworkHostAndPort.Companion::parse) Path::class -> getStringList(path).map { Paths.get(it) } URL::class -> getStringList(path).map(::URL) + X500Principal::class -> getStringList(path).map(::X500Principal) UUID::class -> getStringList(path).map { UUID.fromString(it) } CordaX500Name::class -> getStringList(path).map(CordaX500Name.Companion::parse) Properties::class -> getConfigList(path).map(Config::toProperties) @@ -226,7 +229,7 @@ private fun Any.toConfigMap(): Map { val configValue = if (value is String || value is Boolean || value is Number) { // These types are supported by Config as use as is value - } else if (value is Temporal || value is NetworkHostAndPort || value is CordaX500Name || value is Path || value is URL || value is UUID) { + } else if (value is Temporal || value is NetworkHostAndPort || value is CordaX500Name || value is Path || value is URL || value is UUID || value is X500Principal) { // These types make sense to be represented as Strings and the exact inverse parsing function for use in parseAs value.toString() } else if (value is Enum<*>) { @@ -261,6 +264,7 @@ private fun Iterable<*>.toConfigIterable(field: Field): Iterable { NetworkHostAndPort::class.java -> map(Any?::toString) Path::class.java -> map(Any?::toString) URL::class.java -> map(Any?::toString) + X500Principal::class.java -> map(Any?::toString) UUID::class.java -> map(Any?::toString) CordaX500Name::class.java -> map(Any?::toString) Properties::class.java -> map { ConfigFactory.parseMap(uncheckedCast(it)).root() } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/config/ConfigParsingTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/config/ConfigParsingTest.kt index 07698b83c3..07de31c51d 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/config/ConfigParsingTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/config/ConfigParsingTest.kt @@ -14,6 +14,7 @@ import java.nio.file.Path import java.time.Instant import java.time.LocalDate import java.util.* +import javax.security.auth.x500.X500Principal import kotlin.reflect.full.primaryConstructor class ConfigParsingTest { @@ -84,6 +85,11 @@ class ConfigParsingTest { testPropertyType(URL("http://localhost:1234"), URL("http://localhost:1235"), valuesToString = true) } + @Test + fun X500Principal() { + testPropertyType(X500Principal("C=US, L=New York, CN=Corda Root CA, OU=Corda, O=R3 HoldCo LLC"), X500Principal("O=Bank A,L=London,C=GB"), valuesToString = true) + } + @Test fun UUID() { testPropertyType(UUID.randomUUID(), UUID.randomUUID(), valuesToString = true) @@ -324,6 +330,8 @@ class ConfigParsingTest { data class PathListData(override val values: List) : ListData data class URLData(override val value: URL) : SingleData data class URLListData(override val values: List) : ListData + data class X500PrincipalData(override val value: X500Principal) : SingleData + data class X500PrincipalListData(override val values: List) : ListData data class UUIDData(override val value: UUID) : SingleData data class UUIDListData(override val values: List) : ListData data class CordaX500NameData(override val value: CordaX500Name) : SingleData 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 b4de9fbea4..b4087a2870 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 @@ -16,12 +16,12 @@ import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.config.parseAs 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 import java.time.Duration import java.util.* +import javax.security.auth.x500.X500Principal val Int.MB: Long get() = this * 1024L * 1024L @@ -63,7 +63,7 @@ interface NodeConfiguration : NodeSSLConfiguration { val drainingModePollPeriod: Duration get() = Duration.ofSeconds(5) val extraNetworkMapKeys: List val tlsCertCrlDistPoint: URL? - val tlsCertCrlIssuer: String? + val tlsCertCrlIssuer: X500Principal? val effectiveH2Settings: NodeH2Settings? val flowMonitorPeriodMillis: Duration get() = DEFAULT_FLOW_MONITOR_PERIOD_MILLIS val flowMonitorSuspensionLoggingThresholdMillis: Duration get() = DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS @@ -168,7 +168,7 @@ data class NodeConfigurationImpl( override val compatibilityZoneURL: URL? = null, override var networkServices: NetworkServicesConfig? = null, override val tlsCertCrlDistPoint: URL? = null, - override val tlsCertCrlIssuer: String? = null, + override val tlsCertCrlIssuer: X500Principal? = null, override val rpcUsers: List, override val security: SecurityConfiguration? = null, override val verifierType: VerifierType, @@ -237,11 +237,6 @@ data class NodeConfigurationImpl( 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" 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 451550f0e2..b8637baace 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 @@ -283,20 +283,20 @@ class NodeRegistrationHelper(private val config: NodeConfiguration, certService: } override fun validateAndGetTlsCrlIssuerCert(): X509Certificate? { - config.tlsCertCrlIssuer ?: return null - val tlsCertCrlIssuerPrincipal = X500Principal(config.tlsCertCrlIssuer) - if (principalMatchesCertificatePrincipal(tlsCertCrlIssuerPrincipal, rootCert)) { + val tlsCertCrlIssuer = config.tlsCertCrlIssuer + tlsCertCrlIssuer ?: return null + if (principalMatchesCertificatePrincipal(tlsCertCrlIssuer, rootCert)) { return rootCert } return if (config.trustStoreFile.exists()) { - findMatchingCertificate(tlsCertCrlIssuerPrincipal, config.loadTrustStore()) + findMatchingCertificate(tlsCertCrlIssuer, config.loadTrustStore()) } else { null } } override fun isTlsCrlIssuerCertRequired(): Boolean { - return !config.tlsCertCrlIssuer.isNullOrEmpty() + return config.tlsCertCrlIssuer != null } private fun findMatchingCertificate(principal: X500Principal, trustStore: X509KeyStore): X509Certificate? { 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 9f183d0b9a..22d68fab17 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 @@ -14,6 +14,7 @@ import org.junit.Test import java.net.URI import java.net.URL import java.nio.file.Paths +import javax.security.auth.x500.X500Principal import kotlin.test.assertFalse import kotlin.test.assertTrue @@ -34,13 +35,6 @@ class NodeConfigurationImplTest { 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() @@ -218,7 +212,7 @@ class NodeConfigurationImplTest { } private fun configTlsCertCrlOptions(tlsCertCrlDistPoint: URL?, tlsCertCrlIssuer: String?, crlCheckSoftFail: Boolean = true): NodeConfiguration { - return testConfiguration.copy(tlsCertCrlDistPoint = tlsCertCrlDistPoint, tlsCertCrlIssuer = tlsCertCrlIssuer, crlCheckSoftFail = crlCheckSoftFail) + return testConfiguration.copy(tlsCertCrlDistPoint = tlsCertCrlDistPoint, tlsCertCrlIssuer = tlsCertCrlIssuer?.let { X500Principal(it) }, crlCheckSoftFail = crlCheckSoftFail) } private val testConfiguration = testNodeConfiguration()