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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 100 additions and 76 deletions

View File

@ -15,6 +15,4 @@ interface SSLConfiguration {
interface NodeSSLConfiguration : SSLConfiguration { interface NodeSSLConfiguration : SSLConfiguration {
val baseDirectory: Path val baseDirectory: Path
override val certificatesDirectory: Path get() = baseDirectory / "certificates" override val certificatesDirectory: Path get() = baseDirectory / "certificates"
// TODO This will be removed. Instead we will just check against the truststore, which will be provided out-of-band, along with its password
val rootCertFile: Path get() = certificatesDirectory / "rootcert.pem"
} }

View File

@ -1,9 +1,11 @@
package net.corda.node.utilities.registration package net.corda.node.utilities.registration
import com.google.common.net.HostAndPort
import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto
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.toX509CertHolder import net.corda.core.internal.toX509CertHolder
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.minutes import net.corda.core.utilities.minutes
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair 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.internal.internalDriver
import net.corda.testing.node.network.NetworkMapServer import net.corda.testing.node.network.NetworkMapServer
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.bouncycastle.pkcs.PKCS10CertificationRequest import org.bouncycastle.pkcs.PKCS10CertificationRequest
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
import org.junit.After import org.junit.After
@ -41,13 +44,12 @@ class NodeRegistrationTest {
private val registrationHandler = RegistrationHandler(rootCertAndKeyPair) private val registrationHandler = RegistrationHandler(rootCertAndKeyPair)
private lateinit var server: NetworkMapServer private lateinit var server: NetworkMapServer
private lateinit var compatibilityZone: CompatibilityZoneParams private lateinit var serverHostAndPort: NetworkHostAndPort
@Before @Before
fun startServer() { fun startServer() {
server = NetworkMapServer(1.minutes, portAllocation.nextHostAndPort(), registrationHandler) server = NetworkMapServer(1.minutes, portAllocation.nextHostAndPort(), registrationHandler)
val address = server.start() serverHostAndPort = server.start()
compatibilityZone = CompatibilityZoneParams(URL("http://$address"), rootCert = rootCertAndKeyPair.certificate.cert)
} }
@After @After
@ -59,6 +61,7 @@ class NodeRegistrationTest {
// starting a second node hangs so that needs to be fixed. // starting a second node hangs so that needs to be fixed.
@Test @Test
fun `node registration correct root cert`() { fun `node registration correct root cert`() {
val compatibilityZone = CompatibilityZoneParams(URL("http://$serverHostAndPort"), rootCert = rootCertAndKeyPair.certificate.cert)
internalDriver( internalDriver(
portAllocation = portAllocation, portAllocation = portAllocation,
notarySpecs = emptyList(), 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 { private fun createSelfKeyAndSelfSignedCertificate(): CertificateAndKeyPair {
val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val rootCACert = X509Utilities.createSelfSignedCACertificate( 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" 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 requestIdStore = config.certificatesDirectory / "certificate-request-id.txt"
private val keystorePassword = config.keyStorePassword private val keystorePassword = config.keyStorePassword
// TODO: Use different password for private key. // TODO: Use different password for private key.
private val privateKeyPassword = config.keyStorePassword 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. * 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.addOrReplaceKey(CORDA_CLIENT_CA, keyPair.private, privateKeyPassword.toCharArray(), certificates)
caKeyStore.deleteEntry(SELF_SIGNED_PRIVATE_KEY) caKeyStore.deleteEntry(SELF_SIGNED_PRIVATE_KEY)
caKeyStore.save(config.nodeKeystore, keystorePassword) 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}.") 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.") println("Generating SSL certificate for node messaging service.")
val sslKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val sslKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val caCert = caKeyStore.getX509Certificate(CORDA_CLIENT_CA).toX509CertHolder() 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, * Poll Certificate Signing Server for approved certificate,
* enter a slow polling loop if server return null. * 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. * 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, class WrongRootCertException(expected: Certificate,
actual: 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 The Root CA returned back from the registration process does not match the expected Root CA
expected: $expected expected: $expected
actual: $actual actual: $actual
the expected certificate is stored in: $expectedFilePath the expected certificate is stored in: $expectedFilePath with alias $CORDA_ROOT_CA
""".trimMargin()) """.trimMargin())

View File

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

View File

@ -1,28 +1,45 @@
package net.corda.testing.driver package net.corda.testing.driver
import net.corda.core.concurrent.CordaFuture import net.corda.core.concurrent.CordaFuture
import net.corda.core.internal.copyTo
import net.corda.core.internal.div import net.corda.core.internal.div
import net.corda.core.internal.list import net.corda.core.internal.list
import net.corda.core.internal.readLines import net.corda.core.internal.readLines
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.seconds
import net.corda.node.internal.NodeStartup import net.corda.node.internal.NodeStartup
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.crypto.getX509Certificate
import net.corda.nodeapi.internal.crypto.loadOrCreateKeyStore
import net.corda.testing.DUMMY_BANK_A import net.corda.testing.DUMMY_BANK_A
import net.corda.testing.DUMMY_NOTARY import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.DUMMY_REGULATOR import net.corda.testing.DUMMY_REGULATOR
import net.corda.testing.common.internal.ProjectStructure.projectRootDir import net.corda.testing.common.internal.ProjectStructure.projectRootDir
import net.corda.testing.http.HttpApi import net.corda.testing.http.HttpApi
import net.corda.testing.internal.CompatibilityZoneParams
import net.corda.testing.internal.addressMustBeBound import net.corda.testing.internal.addressMustBeBound
import net.corda.testing.internal.addressMustNotBeBound import net.corda.testing.internal.addressMustNotBeBound
import net.corda.testing.internal.internalDriver import net.corda.testing.internal.internalDriver
import net.corda.testing.node.NotarySpec import net.corda.testing.node.NotarySpec
import net.corda.testing.node.network.NetworkMapServer
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.json.simple.JSONObject import org.json.simple.JSONObject
import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.net.URL
import java.util.concurrent.Executors import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.ScheduledExecutorService
import javax.ws.rs.GET
import javax.ws.rs.POST
import javax.ws.rs.Path
import javax.ws.rs.core.Response
import javax.ws.rs.core.Response.ok
class DriverTests { class DriverTests {
companion object { companion object {
private val executorService: ScheduledExecutorService = Executors.newScheduledThreadPool(2) private val executorService: ScheduledExecutorService = Executors.newScheduledThreadPool(2)

View File

@ -39,6 +39,9 @@ import net.corda.nodeapi.internal.config.User
import net.corda.nodeapi.internal.config.parseAs import net.corda.nodeapi.internal.config.parseAs
import net.corda.nodeapi.internal.config.toConfig import net.corda.nodeapi.internal.config.toConfig
import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.crypto.addOrReplaceCertificate
import net.corda.nodeapi.internal.crypto.loadOrCreateKeyStore
import net.corda.nodeapi.internal.crypto.save
import net.corda.testing.ALICE import net.corda.testing.ALICE
import net.corda.testing.BOB import net.corda.testing.BOB
import net.corda.testing.DUMMY_BANK_A import net.corda.testing.DUMMY_BANK_A
@ -220,8 +223,11 @@ class DriverDSLImpl(
) )
val configuration = config.parseAsNodeConfiguration() val configuration = config.parseAsNodeConfiguration()
configuration.rootCertFile.parent.createDirectories() configuration.trustStoreFile.parent.createDirectories()
X509Utilities.saveCertificateAsPEMFile(rootCert, configuration.rootCertFile) loadOrCreateKeyStore(configuration.trustStoreFile, configuration.trustStorePassword).also {
it.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCert)
it.save(configuration.trustStoreFile, configuration.trustStorePassword)
}
return if (startNodesInProcess) { return if (startNodesInProcess) {
// This is a bit cheating, we're not starting a full node, we're just calling the code nodes call // This is a bit cheating, we're not starting a full node, we're just calling the code nodes call