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 1a56fdb5da..3d4e735498 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 @@ -51,7 +51,7 @@ open class NetworkRegistrationHelper(private val certificatesDirectory: Path, } private val requestIdStore = certificatesDirectory / "certificate-request-id.txt" - private val rootTrustStore: X509KeyStore + protected val rootTrustStore: X509KeyStore protected val rootCert: X509Certificate init { @@ -294,9 +294,18 @@ class NodeRegistrationHelper(private val config: NodeConfiguration, certService: private fun createTruststore(rootCertificate: X509Certificate) { // Save root certificates to trust store. config.p2pSslOptions.trustStore.get(createNew = true).update { + if (this.aliases().hasNext()) { + logger.warn("The node's trust store already exists. The following certificates will be overridden: ${this.aliases().asSequence()}") + } println("Generating trust store for corda node.") // Assumes certificate chain always starts with client certificate and end with root certificate. setCertificate(CORDA_ROOT_CA, rootCertificate) + // Copy remaining certificates from the network-trust-store + rootTrustStore.aliases().asSequence().filter { it != CORDA_ROOT_CA }.forEach { + val certificate = rootTrustStore.getCertificate(it) + logger.info("Copying trusted certificate to the node's trust store: Alias: $it, Certificate: $certificate") + setCertificate(it, certificate) + } } println("Node trust store stored in ${config.p2pSslOptions.trustStore.path}.") } 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 33922035e2..5f60bb6618 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 @@ -20,11 +20,13 @@ import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509KeyStore import net.corda.nodeapi.internal.crypto.X509Utilities +import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA import net.corda.nodeapi.internal.crypto.X509Utilities.DISTRIBUTED_NOTARY_ALIAS_PREFIX +import net.corda.nodeapi.internal.crypto.X509Utilities.createSelfSignedCACertificate import net.corda.testing.core.ALICE_NAME -import net.corda.testing.internal.stubs.CertificateStoreStubs import net.corda.testing.internal.createDevIntermediateCaCertPath import net.corda.testing.internal.rigorousMock +import net.corda.testing.internal.stubs.CertificateStoreStubs import org.assertj.core.api.Assertions.* import org.bouncycastle.asn1.x509.GeneralName import org.bouncycastle.asn1.x509.GeneralSubtree @@ -38,7 +40,9 @@ import java.security.PublicKey import java.security.cert.CertPathValidatorException import java.security.cert.X509Certificate import javax.security.auth.x500.X500Principal +import kotlin.test.assertEquals import kotlin.test.assertFalse +import kotlin.test.assertTrue class NetworkRegistrationHelperTest { private val fs = Jimfs.newFileSystem(unix()) @@ -53,6 +57,7 @@ class NetworkRegistrationHelperTest { val baseDirectory = fs.getPath("/baseDir").createDirectories() abstract class AbstractNodeConfiguration : NodeConfiguration + val certificatesDirectory = baseDirectory / "certificates" config = rigorousMock().also { doReturn(baseDirectory).whenever(it).baseDirectory @@ -78,7 +83,7 @@ class NetworkRegistrationHelperTest { assertThat(config.p2pSslOptions.keyStore.getOptional()).isNull() assertThat(config.p2pSslOptions.trustStore.getOptional()).isNull() - val rootAndIntermediateCA = createDevIntermediateCaCertPath().also { saveNetworkTrustStore(it.first.certificate) } + val rootAndIntermediateCA = createDevIntermediateCaCertPath().also { saveNetworkTrustStore(CORDA_ROOT_CA to it.first.certificate) } createRegistrationHelper(rootAndIntermediateCA = rootAndIntermediateCA).buildKeystore() @@ -122,7 +127,7 @@ class NetworkRegistrationHelperTest { @Test fun `node CA with incorrect cert role`() { val nodeCaCertPath = createNodeCaCertPath(type = CertificateType.TLS) - saveNetworkTrustStore(nodeCaCertPath.last()) + saveNetworkTrustStore(CORDA_ROOT_CA to nodeCaCertPath.last()) val registrationHelper = createFixedResponseRegistrationHelper(nodeCaCertPath) assertThatExceptionOfType(CertificateRequestException::class.java) .isThrownBy { registrationHelper.buildKeystore() } @@ -133,19 +138,39 @@ class NetworkRegistrationHelperTest { fun `node CA with incorrect subject`() { val invalidName = CordaX500Name("Foo", "MU", "GB") val nodeCaCertPath = createNodeCaCertPath(legalName = invalidName) - saveNetworkTrustStore(nodeCaCertPath.last()) + saveNetworkTrustStore(CORDA_ROOT_CA to nodeCaCertPath.last()) val registrationHelper = createFixedResponseRegistrationHelper(nodeCaCertPath) assertThatExceptionOfType(CertificateRequestException::class.java) .isThrownBy { registrationHelper.buildKeystore() } .withMessageContaining(invalidName.toString()) } + @Test + fun `multiple certificates are copied to the node's trust store`() { + val extraTrustedCertAlias = "trusted_test" + val extraTrustedCert = createSelfSignedCACertificate( + X500Principal("O=Test Trusted CA,L=MU,C=GB"), + Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)) + val rootAndIntermediateCA = createDevIntermediateCaCertPath().also { + saveNetworkTrustStore(CORDA_ROOT_CA to it.first.certificate, extraTrustedCertAlias to extraTrustedCert) + } + + val registrationHelper = createRegistrationHelper(rootAndIntermediateCA = rootAndIntermediateCA) + registrationHelper.buildKeystore() + val trustStore = config.p2pSslOptions.trustStore.get() + trustStore.run { + assertTrue(contains(extraTrustedCertAlias)) + assertTrue(contains(CORDA_ROOT_CA)) + assertEquals(extraTrustedCert, get(extraTrustedCertAlias)) + } + } + @Test fun `wrong root cert in truststore`() { - val wrongRootCert = X509Utilities.createSelfSignedCACertificate( + val wrongRootCert = createSelfSignedCACertificate( X500Principal("O=Foo,L=MU,C=GB"), Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)) - saveNetworkTrustStore(wrongRootCert) + saveNetworkTrustStore(CORDA_ROOT_CA to wrongRootCert) val registrationHelper = createRegistrationHelper() assertThatThrownBy { @@ -159,7 +184,7 @@ class NetworkRegistrationHelperTest { assertThat(config.p2pSslOptions.keyStore.getOptional()).isNull() assertThat(config.p2pSslOptions.trustStore.getOptional()).isNull() - val rootAndIntermediateCA = createDevIntermediateCaCertPath().also { saveNetworkTrustStore(it.first.certificate) } + val rootAndIntermediateCA = createDevIntermediateCaCertPath().also { saveNetworkTrustStore(CORDA_ROOT_CA to it.first.certificate) } createRegistrationHelper(CertRole.SERVICE_IDENTITY, rootAndIntermediateCA).buildKeystore() @@ -239,11 +264,19 @@ class NetworkRegistrationHelperTest { } } - private fun saveNetworkTrustStore(rootCert: X509Certificate) { + /** + * Saves given certificates into the truststore. + * + * @param trustedCertificates pairs containing the alias under which the given certificate needs to be stored and + * the certificate itself. + */ + private fun saveNetworkTrustStore(vararg trustedCertificates: Pair) { config.certificatesDirectory.createDirectories() val rootTruststorePath = config.certificatesDirectory / networkRootTrustStoreFileName X509KeyStore.fromFile(rootTruststorePath, networkRootTrustStorePassword, createNew = true).update { - setCertificate(X509Utilities.CORDA_ROOT_CA, rootCert) + trustedCertificates.forEach { + setCertificate(it.first, it.second) + } } } }