Added checks on the received node CA cert from the doorman service. (#2301)

This commit is contained in:
Shams Asari 2018-01-02 15:51:13 +00:00 committed by GitHub
parent 730fec2eb4
commit 20683c3239
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 132 additions and 74 deletions

View File

@ -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()
} }
/** /**

View File

@ -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)
} }