Changed logic to check for initial certificate, now using the truststore instead of the .cer file (#2213)

This commit is contained in:
Alberto Arri
2017-12-12 10:39:52 +00:00
committed by GitHub
parent 5b12c5177e
commit 2dc73ecf3b
6 changed files with 100 additions and 76 deletions

View File

@ -1,9 +1,11 @@
package net.corda.node.utilities.registration
import com.google.common.net.HostAndPort
import net.corda.core.crypto.Crypto
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.cert
import net.corda.core.internal.toX509CertHolder
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.minutes
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
@ -18,6 +20,7 @@ import net.corda.testing.driver.PortAllocation
import net.corda.testing.internal.internalDriver
import net.corda.testing.node.network.NetworkMapServer
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
import org.junit.After
@ -41,13 +44,12 @@ class NodeRegistrationTest {
private val registrationHandler = RegistrationHandler(rootCertAndKeyPair)
private lateinit var server: NetworkMapServer
private lateinit var compatibilityZone: CompatibilityZoneParams
private lateinit var serverHostAndPort: NetworkHostAndPort
@Before
fun startServer() {
server = NetworkMapServer(1.minutes, portAllocation.nextHostAndPort(), registrationHandler)
val address = server.start()
compatibilityZone = CompatibilityZoneParams(URL("http://$address"), rootCert = rootCertAndKeyPair.certificate.cert)
serverHostAndPort = server.start()
}
@After
@ -59,6 +61,7 @@ class NodeRegistrationTest {
// starting a second node hangs so that needs to be fixed.
@Test
fun `node registration correct root cert`() {
val compatibilityZone = CompatibilityZoneParams(URL("http://$serverHostAndPort"), rootCert = rootCertAndKeyPair.certificate.cert)
internalDriver(
portAllocation = portAllocation,
notarySpecs = emptyList(),
@ -69,6 +72,23 @@ class NodeRegistrationTest {
}
}
@Test
fun `node registration wrong root cert`() {
val someCert = createSelfKeyAndSelfSignedCertificate().certificate.cert
val compatibilityZone = CompatibilityZoneParams(URL("http://$serverHostAndPort"), rootCert = someCert)
internalDriver(
portAllocation = portAllocation,
notarySpecs = emptyList(),
compatibilityZone = compatibilityZone,
// Changing the content of the truststore makes the node fail in a number of ways if started out process.
startNodesInProcess = true
) {
assertThatThrownBy {
startNode(providedName = CordaX500Name("Alice", "London", "GB")).getOrThrow()
}.isInstanceOf(WrongRootCertException::class.java)
}
}
private fun createSelfKeyAndSelfSignedCertificate(): CertificateAndKeyPair {
val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val rootCACert = X509Utilities.createSelfSignedCACertificate(

View File

@ -27,18 +27,27 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
val SELF_SIGNED_PRIVATE_KEY = "Self Signed Private Key"
}
init {
require(config.rootCertFile.exists()) {
"${config.rootCertFile} does not exist. This file must contain the root CA cert of your compatibility zone. " +
"Please contact your CZ operator."
}
}
private val requestIdStore = config.certificatesDirectory / "certificate-request-id.txt"
private val keystorePassword = config.keyStorePassword
// TODO: Use different password for private key.
private val privateKeyPassword = config.keyStorePassword
private val rootCert = X509Utilities.loadCertificateFromPEMFile(config.rootCertFile)
private val trustStore: KeyStore
private val rootCert: Certificate
init {
require(config.trustStoreFile.exists()) {
"${config.trustStoreFile} does not exist. This file must contain the root CA cert of your compatibility zone. " +
"Please contact your CZ operator."
}
trustStore = loadKeyStore(config.trustStoreFile, config.trustStorePassword)
val rootCert = trustStore.getCertificate(CORDA_ROOT_CA)
require(rootCert != null) {
"${config.trustStoreFile} does not contain a certificate with the key $CORDA_ROOT_CA." +
"This file must contain the root CA cert of your compatibility zone. " +
"Please contact your CZ operator."
}
this.rootCert = rootCert
}
/**
* Ensure the initial keystore for a node is set up.
@ -83,18 +92,14 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
caKeyStore.addOrReplaceKey(CORDA_CLIENT_CA, keyPair.private, privateKeyPassword.toCharArray(), certificates)
caKeyStore.deleteEntry(SELF_SIGNED_PRIVATE_KEY)
caKeyStore.save(config.nodeKeystore, keystorePassword)
// Check the root certificate.
val returnedRootCa = certificates.last()
checkReturnedRootCaMatchesExpectedCa(returnedRootCa)
// Save root certificates to trust store.
val trustStore = loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword)
// Assumes certificate chain always starts with client certificate and end with root certificate.
trustStore.addOrReplaceCertificate(CORDA_ROOT_CA, returnedRootCa)
trustStore.save(config.trustStoreFile, config.trustStorePassword)
println("Node private key and certificate stored in ${config.nodeKeystore}.")
// Check that the root of the signed certificate matches the expected certificate in the truststore.
if (rootCert != certificates.last()) {
// Assumes certificate chain always starts with client certificate and end with root certificate.
throw WrongRootCertException(rootCert, certificates.last(), config.trustStoreFile)
}
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()
@ -111,16 +116,6 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
}
}
/**
* Checks that the passed Certificate is the expected root CA.
* @throws WrongRootCertException if the certificates don't match.
*/
private fun checkReturnedRootCaMatchesExpectedCa(returnedRootCa: Certificate) {
if (rootCert != returnedRootCa) {
throw WrongRootCertException(rootCert, returnedRootCa, config.rootCertFile)
}
}
/**
* Poll Certificate Signing Server for approved certificate,
* enter a slow polling loop if server return null.
@ -177,7 +172,7 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
/**
* Exception thrown when the doorman root certificate doesn't match the expected (out-of-band) root certificate.
* This usually means the has been a Man-in-the-middle attack when contacting the doorman.
* This usually means that there has been a Man-in-the-middle attack when contacting the doorman.
*/
class WrongRootCertException(expected: Certificate,
actual: Certificate,
@ -186,5 +181,5 @@ class WrongRootCertException(expected: Certificate,
The Root CA returned back from the registration process does not match the expected Root CA
expected: $expected
actual: $actual
the expected certificate is stored in: $expectedFilePath
the expected certificate is stored in: $expectedFilePath with alias $CORDA_ROOT_CA
""".trimMargin())

View File

@ -9,9 +9,8 @@ import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.*
import net.corda.node.services.config.NodeConfiguration
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.crypto.getX509Certificate
import net.corda.nodeapi.internal.crypto.loadKeyStore
import net.corda.node.services.config.createKeystoreForCordaNode
import net.corda.nodeapi.internal.crypto.*
import net.corda.testing.ALICE
import net.corda.testing.rigorousMock
import net.corda.testing.testNodeConfiguration
@ -33,6 +32,15 @@ class NetworkRegistrationHelperTest {
private val requestId = SecureHash.randomSHA256().toString()
private lateinit var config: NodeConfiguration
private val identities = listOf("CORDA_CLIENT_CA",
"CORDA_INTERMEDIATE_CA",
"CORDA_ROOT_CA")
.map { CordaX500Name(commonName = it, organisation = "R3 Ltd", locality = "London", country = "GB") }
private val certs = identities.map { X509Utilities.createSelfSignedCACertificate(it, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)) }
.map { it.cert }.toTypedArray()
private val certService = mockRegistrationResponse(*certs)
@Before
fun init() {
config = testNodeConfiguration(baseDirectory = tempFolder.root.toPath(), myLegalName = ALICE.name)
@ -40,21 +48,13 @@ class NetworkRegistrationHelperTest {
@Test
fun `successful registration`() {
val identities = listOf("CORDA_CLIENT_CA",
"CORDA_INTERMEDIATE_CA",
"CORDA_ROOT_CA")
.map { CordaX500Name(commonName = it, organisation = "R3 Ltd", locality = "London", country = "GB") }
val certs = identities.stream().map { X509Utilities.createSelfSignedCACertificate(it, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)) }
.map { it.cert }.toTypedArray()
val certService = mockRegistrationResponse(*certs)
config.rootCertFile.parent.createDirectories()
X509Utilities.saveCertificateAsPEMFile(certs.last(), config.rootCertFile)
assertFalse(config.nodeKeystore.exists())
assertFalse(config.sslKeystore.exists())
assertFalse(config.trustStoreFile.exists())
config.trustStoreFile.parent.createDirectories()
loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword).also {
it.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, certs.last())
it.save(config.trustStoreFile, config.trustStorePassword)
}
NetworkRegistrationHelper(config, certService).buildKeystore()
@ -97,33 +97,21 @@ class NetworkRegistrationHelperTest {
}
@Test
fun `rootCertFile doesn't exist`() {
val certService = rigorousMock<NetworkRegistrationService>()
fun `missing truststore`() {
assertThatThrownBy {
NetworkRegistrationHelper(config, certService)
}.hasMessageContaining(config.rootCertFile.toString())
NetworkRegistrationHelper(config, certService).buildKeystore()
}.hasMessageContaining("This file must contain the root CA cert of your compatibility zone. Please contact your CZ operator.")
.isInstanceOf(IllegalArgumentException::class.java)
}
@Test
fun `root cert in response doesn't match expected`() {
val identities = listOf("CORDA_CLIENT_CA",
"CORDA_INTERMEDIATE_CA",
"CORDA_ROOT_CA")
.map { CordaX500Name(commonName = it, organisation = "R3 Ltd", locality = "London", country = "GB") }
val certs = identities.stream().map { X509Utilities.createSelfSignedCACertificate(it, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)) }
.map { it.cert }.toTypedArray()
val certService = mockRegistrationResponse(*certs)
config.rootCertFile.parent.createDirectories()
X509Utilities.saveCertificateAsPEMFile(
X509Utilities.createSelfSignedCACertificate(
CordaX500Name("CORDA_ROOT_CA", "R3 Ltd", "London", "GB"),
Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)).cert,
config.rootCertFile
)
fun `wrong root cert in truststore`() {
val someCert = X509Utilities.createSelfSignedCACertificate(CordaX500Name("Foo", "MU", "GB"), Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)).cert
config.trustStoreFile.parent.createDirectories()
loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword).also {
it.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, someCert)
it.save(config.trustStoreFile, config.trustStorePassword)
}
assertThatThrownBy {
NetworkRegistrationHelper(config, certService).buildKeystore()
}.isInstanceOf(WrongRootCertException::class.java)