mirror of
https://github.com/corda/corda.git
synced 2025-01-30 08:04:16 +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.
|
* needed.
|
||||||
*/
|
*/
|
||||||
class NetworkRegistrationHelper(private val config: NodeConfiguration, private val certService: NetworkRegistrationService) {
|
class NetworkRegistrationHelper(private val config: NodeConfiguration, private val certService: NetworkRegistrationService) {
|
||||||
companion object {
|
private companion object {
|
||||||
val pollInterval = 10.seconds
|
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"
|
private val requestIdStore = config.certificatesDirectory / "certificate-request-id.txt"
|
||||||
@ -62,54 +62,81 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
|
|||||||
*/
|
*/
|
||||||
fun buildKeystore() {
|
fun buildKeystore() {
|
||||||
config.certificatesDirectory.createDirectories()
|
config.certificatesDirectory.createDirectories()
|
||||||
val caKeyStore = loadOrCreateKeyStore(config.nodeKeystore, keystorePassword)
|
val nodeKeyStore = loadOrCreateKeyStore(config.nodeKeystore, keystorePassword)
|
||||||
if (!caKeyStore.containsAlias(CORDA_CLIENT_CA)) {
|
if (nodeKeyStore.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 {
|
|
||||||
println("Certificate already exists, Corda node will now terminate...")
|
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.identity.CordaX500Name
|
||||||
import net.corda.core.internal.cert
|
import net.corda.core.internal.cert
|
||||||
import net.corda.core.internal.createDirectories
|
import net.corda.core.internal.createDirectories
|
||||||
|
import net.corda.core.internal.x500Name
|
||||||
import net.corda.node.services.config.NodeConfiguration
|
import net.corda.node.services.config.NodeConfiguration
|
||||||
import net.corda.nodeapi.internal.crypto.*
|
import net.corda.nodeapi.internal.crypto.*
|
||||||
import net.corda.testing.ALICE_NAME
|
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 net.corda.testing.internal.rigorousMock
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.*
|
||||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
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.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.security.cert.CertPathValidatorException
|
import java.security.cert.CertPathValidatorException
|
||||||
import java.security.cert.Certificate
|
|
||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
import kotlin.test.assertFalse
|
import kotlin.test.assertFalse
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
@ -32,18 +34,10 @@ class NetworkRegistrationHelperTest {
|
|||||||
private val requestId = SecureHash.randomSHA256().toString()
|
private val requestId = SecureHash.randomSHA256().toString()
|
||||||
private val nodeLegalName = ALICE_NAME
|
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
|
private lateinit var config: NodeConfiguration
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun init() {
|
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()
|
val baseDirectory = fs.getPath("/baseDir").createDirectories()
|
||||||
abstract class AbstractNodeConfiguration : NodeConfiguration
|
abstract class AbstractNodeConfiguration : NodeConfiguration
|
||||||
config = rigorousMock<AbstractNodeConfiguration>().also {
|
config = rigorousMock<AbstractNodeConfiguration>().also {
|
||||||
@ -66,9 +60,10 @@ class NetworkRegistrationHelperTest {
|
|||||||
assertThat(config.sslKeystore).doesNotExist()
|
assertThat(config.sslKeystore).doesNotExist()
|
||||||
assertThat(config.trustStoreFile).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 nodeKeystore = loadKeyStore(config.nodeKeystore, config.keyStorePassword)
|
||||||
val sslKeystore = loadKeyStore(config.sslKeystore, 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_INTERMEDIATE_CA))
|
||||||
assertFalse(containsAlias(X509Utilities.CORDA_ROOT_CA))
|
assertFalse(containsAlias(X509Utilities.CORDA_ROOT_CA))
|
||||||
assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_TLS))
|
assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_TLS))
|
||||||
val nodeCaCertChain = getCertificateChain(X509Utilities.CORDA_CLIENT_CA)
|
assertThat(getCertificateChain(X509Utilities.CORDA_CLIENT_CA)).containsExactly(*nodeCaCertPath)
|
||||||
assertThat(nodeCaCertChain).containsExactly(nodeCaCert, intermediateCaCert, rootCaCert)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sslKeystore.run {
|
sslKeystore.run {
|
||||||
@ -92,40 +86,77 @@ class NetworkRegistrationHelperTest {
|
|||||||
assertThat(nodeTlsCertChain).hasSize(4)
|
assertThat(nodeTlsCertChain).hasSize(4)
|
||||||
// The TLS cert has the same subject as the node CA cert
|
// The TLS cert has the same subject as the node CA cert
|
||||||
assertThat(CordaX500Name.build((nodeTlsCertChain[0] as X509Certificate).subjectX500Principal)).isEqualTo(nodeLegalName)
|
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 {
|
trustStore.run {
|
||||||
assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_CA))
|
assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_CA))
|
||||||
assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA))
|
assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA))
|
||||||
assertTrue(containsAlias(X509Utilities.CORDA_ROOT_CA))
|
assertTrue(containsAlias(X509Utilities.CORDA_ROOT_CA))
|
||||||
val trustStoreRootCaCert = getCertificate(X509Utilities.CORDA_ROOT_CA)
|
assertThat(getCertificate(X509Utilities.CORDA_ROOT_CA)).isEqualTo(nodeCaCertPath.last())
|
||||||
assertThat(trustStoreRootCaCert).isEqualTo(rootCaCert)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `missing truststore`() {
|
fun `missing truststore`() {
|
||||||
|
val nodeCaCertPath = createNodeCaCertPath()
|
||||||
assertThatThrownBy {
|
assertThatThrownBy {
|
||||||
createRegistrationHelper()
|
createRegistrationHelper(nodeCaCertPath)
|
||||||
}.hasMessageContaining("This file must contain the root CA cert of your compatibility zone. Please contact your CZ operator.")
|
}.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
|
@Test
|
||||||
fun `wrong root cert in truststore`() {
|
fun `wrong root cert in truststore`() {
|
||||||
val rootKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
val wrongRootCert = X509Utilities.createSelfSignedCACertificate(
|
||||||
val rootCert = X509Utilities.createSelfSignedCACertificate(CordaX500Name("Foo", "MU", "GB"), rootKeyPair)
|
CordaX500Name("Foo", "MU", "GB"),
|
||||||
saveTrustStoreWithRootCa(rootCert.cert)
|
Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME))
|
||||||
val registrationHelper = createRegistrationHelper()
|
saveTrustStoreWithRootCa(wrongRootCert.cert)
|
||||||
|
val registrationHelper = createRegistrationHelper(createNodeCaCertPath())
|
||||||
assertThatThrownBy {
|
assertThatThrownBy {
|
||||||
registrationHelper.buildKeystore()
|
registrationHelper.buildKeystore()
|
||||||
}.isInstanceOf(CertPathValidatorException::class.java)
|
}.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 {
|
val certService = rigorousMock<NetworkRegistrationService>().also {
|
||||||
doReturn(requestId).whenever(it).submitRequest(any())
|
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)
|
return NetworkRegistrationHelper(config, certService)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user