mirror of
https://github.com/corda/corda.git
synced 2025-02-01 00:45:59 +00:00
Changed logic to check for initial certificate, now using the truststore instead of the .cer file (#2213)
This commit is contained in:
parent
5b12c5177e
commit
2dc73ecf3b
@ -15,6 +15,4 @@ interface SSLConfiguration {
|
||||
interface NodeSSLConfiguration : SSLConfiguration {
|
||||
val baseDirectory: Path
|
||||
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"
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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())
|
||||
|
@ -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)
|
||||
|
@ -1,28 +1,45 @@
|
||||
package net.corda.testing.driver
|
||||
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.internal.copyTo
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.list
|
||||
import net.corda.core.internal.readLines
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.seconds
|
||||
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_NOTARY
|
||||
import net.corda.testing.DUMMY_REGULATOR
|
||||
import net.corda.testing.common.internal.ProjectStructure.projectRootDir
|
||||
import net.corda.testing.http.HttpApi
|
||||
import net.corda.testing.internal.CompatibilityZoneParams
|
||||
import net.corda.testing.internal.addressMustBeBound
|
||||
import net.corda.testing.internal.addressMustNotBeBound
|
||||
import net.corda.testing.internal.internalDriver
|
||||
import net.corda.testing.node.NotarySpec
|
||||
import net.corda.testing.node.network.NetworkMapServer
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.json.simple.JSONObject
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import java.net.URL
|
||||
import java.util.concurrent.Executors
|
||||
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 {
|
||||
|
||||
companion object {
|
||||
private val executorService: ScheduledExecutorService = Executors.newScheduledThreadPool(2)
|
||||
|
||||
|
@ -39,6 +39,9 @@ import net.corda.nodeapi.internal.config.User
|
||||
import net.corda.nodeapi.internal.config.parseAs
|
||||
import net.corda.nodeapi.internal.config.toConfig
|
||||
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.BOB
|
||||
import net.corda.testing.DUMMY_BANK_A
|
||||
@ -220,8 +223,11 @@ class DriverDSLImpl(
|
||||
)
|
||||
val configuration = config.parseAsNodeConfiguration()
|
||||
|
||||
configuration.rootCertFile.parent.createDirectories()
|
||||
X509Utilities.saveCertificateAsPEMFile(rootCert, configuration.rootCertFile)
|
||||
configuration.trustStoreFile.parent.createDirectories()
|
||||
loadOrCreateKeyStore(configuration.trustStoreFile, configuration.trustStorePassword).also {
|
||||
it.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCert)
|
||||
it.save(configuration.trustStoreFile, configuration.trustStorePassword)
|
||||
}
|
||||
|
||||
return if (startNodesInProcess) {
|
||||
// This is a bit cheating, we're not starting a full node, we're just calling the code nodes call
|
||||
|
Loading…
x
Reference in New Issue
Block a user