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 af9735b89a..09a46121e8 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 @@ -22,9 +22,9 @@ import java.security.cert.X509Certificate * needed. */ class NetworkRegistrationHelper(private val config: NodeConfiguration, private val certService: NetworkRegistrationService) { - companion object { + private companion object { val pollInterval = 10.seconds - val SELF_SIGNED_PRIVATE_KEY = "Self Signed Private Key" + const val SELF_SIGNED_PRIVATE_KEY = "Self Signed Private Key" } private val requestIdStore = config.certificatesDirectory / "certificate-request-id.txt" @@ -62,54 +62,81 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v */ fun buildKeystore() { config.certificatesDirectory.createDirectories() - val caKeyStore = loadOrCreateKeyStore(config.nodeKeystore, keystorePassword) - if (!caKeyStore.containsAlias(CORDA_CLIENT_CA)) { - // Create or load self signed keypair from the key store. - // We use the self sign certificate to store the key temporarily in the keystore while waiting for the request approval. - if (!caKeyStore.containsAlias(SELF_SIGNED_PRIVATE_KEY)) { - val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val selfSignCert = X509Utilities.createSelfSignedCACertificate(config.myLegalName, keyPair) - // Save to the key store. - caKeyStore.addOrReplaceKey(SELF_SIGNED_PRIVATE_KEY, keyPair.private, privateKeyPassword.toCharArray(), - arrayOf(selfSignCert)) - caKeyStore.save(config.nodeKeystore, keystorePassword) - } - val keyPair = caKeyStore.getKeyPair(SELF_SIGNED_PRIVATE_KEY, privateKeyPassword) - val requestId = submitOrResumeCertificateSigningRequest(keyPair) - - val certificates = try { - pollServerForCertificates(requestId) - } catch (certificateRequestException: CertificateRequestException) { - System.err.println(certificateRequestException.message) - System.err.println("Please make sure the details in configuration file are correct and try again.") - System.err.println("Corda node will now terminate.") - requestIdStore.deleteIfExists() - throw certificateRequestException - } - - println("Certificate signing request approved, storing private key with the certificate chain.") - // Save private key and certificate chain to the key store. - caKeyStore.addOrReplaceKey(CORDA_CLIENT_CA, keyPair.private, privateKeyPassword.toCharArray(), certificates) - caKeyStore.deleteEntry(SELF_SIGNED_PRIVATE_KEY) - caKeyStore.save(config.nodeKeystore, keystorePassword) - println("Node private key and certificate stored in ${config.nodeKeystore}.") - - println("Checking root of the certificate path is what we expect.") - X509Utilities.validateCertificateChain(rootCert, *certificates) - - println("Generating SSL certificate for node messaging service.") - val sslKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val caCert = caKeyStore.getX509Certificate(CORDA_CLIENT_CA).toX509CertHolder() - val sslCert = X509Utilities.createCertificate(CertificateType.TLS, caCert, keyPair, CordaX500Name.build(caCert.cert.subjectX500Principal), sslKey.public) - val sslKeyStore = loadOrCreateKeyStore(config.sslKeystore, keystorePassword) - sslKeyStore.addOrReplaceKey(CORDA_CLIENT_TLS, sslKey.private, privateKeyPassword.toCharArray(), arrayOf(sslCert.cert, *certificates)) - sslKeyStore.save(config.sslKeystore, config.keyStorePassword) - println("SSL private key and certificate stored in ${config.sslKeystore}.") - // All done, clean up temp files. - requestIdStore.deleteIfExists() - } else { + val nodeKeyStore = loadOrCreateKeyStore(config.nodeKeystore, keystorePassword) + if (nodeKeyStore.containsAlias(CORDA_CLIENT_CA)) { println("Certificate already exists, Corda node will now terminate...") + return } + + // Create or load self signed keypair from the key store. + // We use the self sign certificate to store the key temporarily in the keystore while waiting for the request approval. + if (!nodeKeyStore.containsAlias(SELF_SIGNED_PRIVATE_KEY)) { + val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val selfSignCert = X509Utilities.createSelfSignedCACertificate(config.myLegalName, keyPair) + // Save to the key store. + nodeKeyStore.addOrReplaceKey(SELF_SIGNED_PRIVATE_KEY, keyPair.private, privateKeyPassword.toCharArray(), + arrayOf(selfSignCert)) + nodeKeyStore.save(config.nodeKeystore, keystorePassword) + } + + val keyPair = nodeKeyStore.getKeyPair(SELF_SIGNED_PRIVATE_KEY, privateKeyPassword) + val requestId = submitOrResumeCertificateSigningRequest(keyPair) + + val certificates = try { + pollServerForCertificates(requestId) + } catch (certificateRequestException: CertificateRequestException) { + System.err.println(certificateRequestException.message) + System.err.println("Please make sure the details in configuration file are correct and try again.") + System.err.println("Corda node will now terminate.") + requestIdStore.deleteIfExists() + throw certificateRequestException + } + + val nodeCaCert = certificates[0] as X509Certificate + + val nodeCaSubject = try { + CordaX500Name.build(nodeCaCert.subjectX500Principal) + } catch (e: IllegalArgumentException) { + throw CertificateRequestException("Received node CA cert has invalid subject name: ${e.message}") + } + if (nodeCaSubject != config.myLegalName) { + throw CertificateRequestException("Subject of received node CA cert doesn't match with node legal name: $nodeCaSubject") + } + + val nodeCaCertRole = try { + CertRole.extract(nodeCaCert) + } catch (e: IllegalArgumentException) { + throw CertificateRequestException("Unable to extract cert role from received node CA cert: ${e.message}") + } + if (nodeCaCertRole != CertRole.NODE_CA) { + throw CertificateRequestException("Received node CA cert has invalid role: $nodeCaCertRole") + } + + println("Checking root of the certificate path is what we expect.") + X509Utilities.validateCertificateChain (rootCert , * certificates) + + println("Certificate signing request approved, storing private key with the certificate chain.") + // Save private key and certificate chain to the key store. + nodeKeyStore.addOrReplaceKey(CORDA_CLIENT_CA, keyPair.private, privateKeyPassword.toCharArray(), certificates) + nodeKeyStore.deleteEntry(SELF_SIGNED_PRIVATE_KEY) + nodeKeyStore.save(config.nodeKeystore, keystorePassword) + println("Node private key and certificate stored in ${config.nodeKeystore}.") + + println("Generating SSL certificate for node messaging service.") + val sslKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val sslCert = X509Utilities.createCertificate( + CertificateType.TLS, + nodeCaCert.toX509CertHolder(), + keyPair, + config.myLegalName, + sslKeyPair.public) + val sslKeyStore = loadOrCreateKeyStore(config.sslKeystore, keystorePassword) + sslKeyStore.addOrReplaceKey(CORDA_CLIENT_TLS, sslKeyPair.private, privateKeyPassword.toCharArray(), arrayOf(sslCert.cert, *certificates)) + sslKeyStore.save(config.sslKeystore, config.keyStorePassword) + println("SSL private key and certificate stored in ${config.sslKeystore}.") + + // All done, clean up temp files. + requestIdStore.deleteIfExists() } /** 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 13113fccf9..5b8dab7282 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 @@ -11,18 +11,20 @@ import net.corda.core.crypto.SecureHash import net.corda.core.identity.CordaX500Name import net.corda.core.internal.cert import net.corda.core.internal.createDirectories +import net.corda.core.internal.x500Name import net.corda.node.services.config.NodeConfiguration import net.corda.nodeapi.internal.crypto.* import net.corda.testing.ALICE_NAME -import net.corda.testing.internal.createDevNodeCaCertPath +import net.corda.testing.internal.createDevIntermediateCaCertPath import net.corda.testing.internal.rigorousMock -import org.assertj.core.api.Assertions.assertThat -import org.assertj.core.api.Assertions.assertThatThrownBy +import org.assertj.core.api.Assertions.* +import org.bouncycastle.asn1.x509.GeneralName +import org.bouncycastle.asn1.x509.GeneralSubtree +import org.bouncycastle.asn1.x509.NameConstraints import org.junit.After import org.junit.Before import org.junit.Test import java.security.cert.CertPathValidatorException -import java.security.cert.Certificate import java.security.cert.X509Certificate import kotlin.test.assertFalse import kotlin.test.assertTrue @@ -32,18 +34,10 @@ class NetworkRegistrationHelperTest { private val requestId = SecureHash.randomSHA256().toString() private val nodeLegalName = ALICE_NAME - private lateinit var rootCaCert: X509Certificate - private lateinit var intermediateCaCert: X509Certificate - private lateinit var nodeCaCert: X509Certificate private lateinit var config: NodeConfiguration @Before fun init() { - val (rootCa, intermediateCa, nodeCa) = createDevNodeCaCertPath(nodeLegalName) - this.rootCaCert = rootCa.certificate.cert - this.intermediateCaCert = intermediateCa.certificate.cert - this.nodeCaCert = nodeCa.certificate.cert - val baseDirectory = fs.getPath("/baseDir").createDirectories() abstract class AbstractNodeConfiguration : NodeConfiguration config = rigorousMock().also { @@ -66,9 +60,10 @@ class NetworkRegistrationHelperTest { assertThat(config.sslKeystore).doesNotExist() assertThat(config.trustStoreFile).doesNotExist() - saveTrustStoreWithRootCa(rootCaCert) + val nodeCaCertPath = createNodeCaCertPath() - createRegistrationHelper().buildKeystore() + saveTrustStoreWithRootCa(nodeCaCertPath.last()) + createRegistrationHelper(nodeCaCertPath).buildKeystore() val nodeKeystore = loadKeyStore(config.nodeKeystore, config.keyStorePassword) val sslKeystore = loadKeyStore(config.sslKeystore, config.keyStorePassword) @@ -79,8 +74,7 @@ class NetworkRegistrationHelperTest { assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA)) assertFalse(containsAlias(X509Utilities.CORDA_ROOT_CA)) assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_TLS)) - val nodeCaCertChain = getCertificateChain(X509Utilities.CORDA_CLIENT_CA) - assertThat(nodeCaCertChain).containsExactly(nodeCaCert, intermediateCaCert, rootCaCert) + assertThat(getCertificateChain(X509Utilities.CORDA_CLIENT_CA)).containsExactly(*nodeCaCertPath) } sslKeystore.run { @@ -92,40 +86,77 @@ class NetworkRegistrationHelperTest { assertThat(nodeTlsCertChain).hasSize(4) // The TLS cert has the same subject as the node CA cert assertThat(CordaX500Name.build((nodeTlsCertChain[0] as X509Certificate).subjectX500Principal)).isEqualTo(nodeLegalName) - assertThat(nodeTlsCertChain.drop(1)).containsExactly(nodeCaCert, intermediateCaCert, rootCaCert) + assertThat(nodeTlsCertChain.drop(1)).containsExactly(*nodeCaCertPath) } trustStore.run { assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_CA)) assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA)) assertTrue(containsAlias(X509Utilities.CORDA_ROOT_CA)) - val trustStoreRootCaCert = getCertificate(X509Utilities.CORDA_ROOT_CA) - assertThat(trustStoreRootCaCert).isEqualTo(rootCaCert) + assertThat(getCertificate(X509Utilities.CORDA_ROOT_CA)).isEqualTo(nodeCaCertPath.last()) } } @Test fun `missing truststore`() { + val nodeCaCertPath = createNodeCaCertPath() assertThatThrownBy { - createRegistrationHelper() + createRegistrationHelper(nodeCaCertPath) }.hasMessageContaining("This file must contain the root CA cert of your compatibility zone. Please contact your CZ operator.") } + @Test + fun `node CA with incorrect cert role`() { + val nodeCaCertPath = createNodeCaCertPath(type = CertificateType.TLS) + saveTrustStoreWithRootCa(nodeCaCertPath.last()) + val registrationHelper = createRegistrationHelper(nodeCaCertPath) + assertThatExceptionOfType(CertificateRequestException::class.java) + .isThrownBy { registrationHelper.buildKeystore() } + .withMessageContaining(CertificateType.TLS.toString()) + } + + @Test + fun `node CA with incorrect subject`() { + val invalidName = CordaX500Name("Foo", "MU", "GB") + val nodeCaCertPath = createNodeCaCertPath(legalName = invalidName) + saveTrustStoreWithRootCa(nodeCaCertPath.last()) + val registrationHelper = createRegistrationHelper(nodeCaCertPath) + assertThatExceptionOfType(CertificateRequestException::class.java) + .isThrownBy { registrationHelper.buildKeystore() } + .withMessageContaining(invalidName.toString()) + } + @Test fun `wrong root cert in truststore`() { - val rootKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val rootCert = X509Utilities.createSelfSignedCACertificate(CordaX500Name("Foo", "MU", "GB"), rootKeyPair) - saveTrustStoreWithRootCa(rootCert.cert) - val registrationHelper = createRegistrationHelper() + val wrongRootCert = X509Utilities.createSelfSignedCACertificate( + CordaX500Name("Foo", "MU", "GB"), + Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)) + saveTrustStoreWithRootCa(wrongRootCert.cert) + val registrationHelper = createRegistrationHelper(createNodeCaCertPath()) assertThatThrownBy { registrationHelper.buildKeystore() }.isInstanceOf(CertPathValidatorException::class.java) } - private fun createRegistrationHelper(): NetworkRegistrationHelper { + private fun createNodeCaCertPath(type: CertificateType = CertificateType.NODE_CA, + legalName: CordaX500Name = nodeLegalName): Array { + val (rootCa, intermediateCa) = createDevIntermediateCaCertPath() + val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName.x500Name))), arrayOf()) + val nodeCaCert = X509Utilities.createCertificate( + type, + intermediateCa.certificate, + intermediateCa.keyPair, + legalName, + keyPair.public, + nameConstraints = nameConstraints) + return arrayOf(nodeCaCert.cert, intermediateCa.certificate.cert, rootCa.certificate.cert) + } + + private fun createRegistrationHelper(response: Array): NetworkRegistrationHelper { val certService = rigorousMock().also { doReturn(requestId).whenever(it).submitRequest(any()) - doReturn(arrayOf(nodeCaCert, intermediateCaCert, rootCaCert)).whenever(it).retrieveCertificates(eq(requestId)) + doReturn(response).whenever(it).retrieveCertificates(eq(requestId)) } return NetworkRegistrationHelper(config, certService) }