mirror of
https://github.com/corda/corda.git
synced 2025-01-29 15:43:55 +00:00
Added checks on the received node CA cert from the doorman service. (#2301)
This commit is contained in:
parent
730fec2eb4
commit
20683c3239
@ -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()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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<AbstractNodeConfiguration>().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<X509Certificate> {
|
||||
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<X509Certificate>): NetworkRegistrationHelper {
|
||||
val certService = rigorousMock<NetworkRegistrationService>().also {
|
||||
doReturn(requestId).whenever(it).submitRequest(any())
|
||||
doReturn(arrayOf<Certificate>(nodeCaCert, intermediateCaCert, rootCaCert)).whenever(it).retrieveCertificates(eq(requestId))
|
||||
doReturn(response).whenever(it).retrieveCertificates(eq(requestId))
|
||||
}
|
||||
return NetworkRegistrationHelper(config, certService)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user