Validating the entire cert path in node registration, rather just checking the root cert. (#2298)

Also reduced duplicate code when creating the node CA cert path for testing, and renamed IdentityGenerator to DevIdentityGenerator.
This commit is contained in:
Shams Asari 2017-12-29 14:38:30 +00:00 committed by GitHub
parent 39d25958e2
commit 4a2f157118
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 205 additions and 189 deletions

View File

@ -1,9 +1,9 @@
package net.corda.core.crypto
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.toTypedArray
import net.corda.core.internal.cert
import net.corda.nodeapi.internal.crypto.*
import net.corda.testing.internal.createDevIntermediateCaCertPath
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.GeneralName
import org.bouncycastle.asn1.x509.GeneralSubtree
@ -11,35 +11,40 @@ import org.bouncycastle.asn1.x509.NameConstraints
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.junit.Test
import java.security.KeyStore
import java.security.cert.*
import java.util.stream.Stream
import java.security.cert.CertPathValidator
import java.security.cert.CertPathValidatorException
import java.security.cert.PKIXParameters
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
class X509NameConstraintsTest {
private fun makeKeyStores(subjectName: X500Name, nameConstraints: NameConstraints): Pair<KeyStore, KeyStore> {
val rootKeys = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Corda Root CA", organisation = "R3 Ltd", locality = "London", country = "GB"), rootKeys)
val intermediateCAKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootKeys, CordaX500Name(commonName = "Corda Intermediate CA", organisation = "R3 Ltd", locality = "London", country = "GB"), intermediateCAKeyPair.public)
val clientCAKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val clientCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, intermediateCACert, intermediateCAKeyPair, CordaX500Name(commonName = "Corda Client CA", organisation = "R3 Ltd", locality = "London", country = "GB"), clientCAKeyPair.public, nameConstraints = nameConstraints)
val (rootCa, intermediateCa) = createDevIntermediateCaCertPath()
val nodeCaKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val nodeCaCert = X509Utilities.createCertificate(
CertificateType.NODE_CA,
intermediateCa.certificate,
intermediateCa.keyPair,
CordaX500Name("Corda Client CA", "R3 Ltd", "London", "GB"),
nodeCaKeyPair.public,
nameConstraints = nameConstraints)
val keyPass = "password"
val trustStore = KeyStore.getInstance(KEYSTORE_TYPE)
trustStore.load(null, keyPass.toCharArray())
trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCACert.cert)
trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCa.certificate.cert)
val tlsKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val tlsCert = X509Utilities.createCertificate(CertificateType.TLS, clientCACert, clientCAKeyPair, subjectName, tlsKey.public)
val tlsKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val tlsCert = X509Utilities.createCertificate(CertificateType.TLS, nodeCaCert, nodeCaKeyPair, subjectName, tlsKeyPair.public)
val keyStore = KeyStore.getInstance(KEYSTORE_TYPE)
keyStore.load(null, keyPass.toCharArray())
keyStore.addOrReplaceKey(X509Utilities.CORDA_CLIENT_TLS, tlsKey.private, keyPass.toCharArray(),
Stream.of(tlsCert, clientCACert, intermediateCACert, rootCACert).map { it.cert }.toTypedArray<Certificate>())
keyStore.addOrReplaceKey(
X509Utilities.CORDA_CLIENT_TLS,
tlsKeyPair.private,
keyPass.toCharArray(),
arrayOf(tlsCert, nodeCaCert, intermediateCa.certificate, rootCa.certificate))
return Pair(keyStore, trustStore)
}

View File

@ -19,10 +19,9 @@ import java.security.cert.X509Certificate
/**
* Contains utility methods for generating identities for a node.
*
* WARNING: This is not application for production use and must never called by the node.
* WARNING: This is not application for production use.
*/
// TODO Rename to DevIdentityGenerator
object IdentityGenerator {
object DevIdentityGenerator {
private val log = LoggerFactory.getLogger(javaClass)
// TODO These don't need to be prefixes but can be the full aliases

View File

@ -15,14 +15,7 @@ import org.bouncycastle.cert.X509CertificateHolder
* the given legal name), and the SSL key store will store the TLS cert which is a sub-cert of the node CA.
*/
fun SSLConfiguration.createDevKeyStores(rootCert: X509CertificateHolder, intermediateCa: CertificateAndKeyPair, legalName: CordaX500Name) {
val nodeCaKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName.x500Name))), arrayOf())
val nodeCaCert = X509Utilities.createCertificate(CertificateType.NODE_CA,
intermediateCa.certificate,
intermediateCa.keyPair,
legalName,
nodeCaKeyPair.public,
nameConstraints = nameConstraints)
val (nodeCaCert, nodeCaKeyPair) = createDevNodeCa(intermediateCa, legalName)
loadOrCreateKeyStore(nodeKeystore, keyStorePassword).apply {
addOrReplaceKey(
@ -44,4 +37,21 @@ fun SSLConfiguration.createDevKeyStores(rootCert: X509CertificateHolder, interme
arrayOf(tlsCert, nodeCaCert, intermediateCa.certificate, rootCert))
save(sslKeystore, keyStorePassword)
}
}
}
/**
* Create a dev node CA cert, as a sub-cert of the given [intermediateCa], and matching key pair using the given
* [CordaX500Name] as the cert subject.
*/
fun createDevNodeCa(intermediateCa: CertificateAndKeyPair, legalName: CordaX500Name): CertificateAndKeyPair {
val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName.x500Name))), arrayOf())
val cert = X509Utilities.createCertificate(
CertificateType.NODE_CA,
intermediateCa.certificate,
intermediateCa.keyPair,
legalName,
keyPair.public,
nameConstraints = nameConstraints)
return CertificateAndKeyPair(cert, keyPair)
}

View File

@ -21,6 +21,7 @@ import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
import net.corda.testing.ALICE_NAME
import net.corda.testing.BOB_NAME
import net.corda.testing.TestIdentity
import net.corda.testing.internal.createDevIntermediateCaCertPath
import org.assertj.core.api.Assertions.assertThat
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.BasicConstraints
@ -169,10 +170,10 @@ class X509UtilitiesTest {
override val trustStorePassword = "trustpass"
}
val (rootCert, intermediateCa) = createRootCertAndIntermediateCa()
val (rootCert, intermediateCa) = createDevIntermediateCaCertPath()
// Generate server cert and private key and populate another keystore suitable for SSL
sslConfig.createDevKeyStores(rootCert, intermediateCa, MEGA_CORP.name)
sslConfig.createDevKeyStores(rootCert.certificate, intermediateCa, MEGA_CORP.name)
// Load back server certificate
val serverKeyStore = loadKeyStore(sslConfig.nodeKeystore, sslConfig.keyStorePassword)
@ -205,11 +206,11 @@ class X509UtilitiesTest {
override val trustStorePassword = "trustpass"
}
val (rootCert, intermediateCa) = createRootCertAndIntermediateCa()
val (rootCert, intermediateCa) = createDevIntermediateCaCertPath()
// Generate server cert and private key and populate another keystore suitable for SSL
sslConfig.createDevKeyStores(rootCert, intermediateCa, MEGA_CORP.name)
sslConfig.createTrustStore(rootCert.cert)
sslConfig.createDevKeyStores(rootCert.certificate, intermediateCa, MEGA_CORP.name)
sslConfig.createTrustStore(rootCert.certificate.cert)
val keyStore = loadKeyStore(sslConfig.sslKeystore, sslConfig.keyStorePassword)
val trustStore = loadKeyStore(sslConfig.trustStoreFile, sslConfig.trustStorePassword)
@ -293,22 +294,6 @@ class X509UtilitiesTest {
private fun tempFile(name: String): Path = tempFolder.root.toPath() / name
private fun createRootCertAndIntermediateCa(): Pair<X509CertificateHolder, CertificateAndKeyPair> {
val rootKeyPair = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val baseName = CordaX500Name(organisation = "R3 Ltd", locality = "London", country = "GB")
val rootCert = X509Utilities.createSelfSignedCACertificate(baseName.copy(commonName = "Corda Root CA"), rootKeyPair)
val intermediateCaKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val intermediateCaCert = X509Utilities.createCertificate(
CertificateType.INTERMEDIATE_CA,
rootCert,
rootKeyPair,
baseName.copy(commonName = "Corda Intermediate CA"),
intermediateCaKeyPair.public)
return Pair(rootCert, CertificateAndKeyPair(intermediateCaCert, intermediateCaKeyPair))
}
private fun SSLConfiguration.createTrustStore(rootCert: X509Certificate) {
val trustStore = loadOrCreateKeyStore(trustStoreFile, trustStorePassword)
trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCert)

View File

@ -10,23 +10,23 @@ import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.crypto.*
import net.corda.testing.ALICE_NAME
import net.corda.testing.driver.driver
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Test
import java.nio.file.Path
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
class NodeKeystoreCheckTest {
@Test
fun `starting node in non-dev mode with no key store`() {
driver(startNodesInProcess = true) {
assertThatThrownBy {
startNode(customOverrides = mapOf("devMode" to false)).getOrThrow()
}.hasMessageContaining("Identity certificate not found")
}
}
@Test
fun `node should throw exception if cert path doesn't chain to the trust root`() {
driver(startNodesInProcess = true) {
// This will fail because there are no keystore configured.
assertFailsWith(IllegalArgumentException::class) {
startNode(customOverrides = mapOf("devMode" to false)).getOrThrow()
}.apply {
assertTrue(message?.startsWith("Identity certificate not found. ") ?: false)
}
// Create keystores
val keystorePassword = "password"
val config = object : SSLConfiguration {
@ -37,9 +37,12 @@ class NodeKeystoreCheckTest {
config.configureDevKeyAndTrustStores(ALICE_NAME)
// This should pass with correct keystore.
val node = startNode(providedName = ALICE_NAME, customOverrides = mapOf("devMode" to false,
"keyStorePassword" to keystorePassword,
"trustStorePassword" to keystorePassword)).get()
val node = startNode(
providedName = ALICE_NAME,
customOverrides = mapOf("devMode" to false,
"keyStorePassword" to keystorePassword,
"trustStorePassword" to keystorePassword)
).getOrThrow()
node.stop()
// Fiddle with node keystore.
@ -53,11 +56,9 @@ class NodeKeystoreCheckTest {
keystore.setKeyEntry(X509Utilities.CORDA_CLIENT_CA, nodeCA.keyPair.private, config.keyStorePassword.toCharArray(), arrayOf(badNodeCACert.cert, badRoot.cert))
keystore.save(config.nodeKeystore, config.keyStorePassword)
assertFailsWith(IllegalArgumentException::class) {
assertThatThrownBy {
startNode(providedName = ALICE_NAME, customOverrides = mapOf("devMode" to false)).getOrThrow()
}.apply {
assertEquals("Client CA certificate must chain to the trusted root.", message)
}
}.hasMessage("Client CA certificate must chain to the trusted root.")
}
}
}

View File

@ -23,7 +23,7 @@ import net.corda.node.services.config.BFTSMaRtConfiguration
import net.corda.node.services.config.NotaryConfig
import net.corda.node.services.transactions.minClusterSize
import net.corda.node.services.transactions.minCorrectReplicas
import net.corda.nodeapi.internal.IdentityGenerator
import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.nodeapi.internal.network.NetworkParametersCopier
import net.corda.nodeapi.internal.network.NotaryInfo
import net.corda.testing.chooseIdentity
@ -60,7 +60,7 @@ class BFTNotaryServiceTests {
(Paths.get("config") / "currentView").deleteIfExists() // XXX: Make config object warn if this exists?
val replicaIds = (0 until clusterSize)
notary = IdentityGenerator.generateDistributedNotaryIdentity(
notary = DevIdentityGenerator.generateDistributedNotaryIdentity(
replicaIds.map { mockNet.baseDirectory(mockNet.nextNodeId + it) },
CordaX500Name("BFT", "Zurich", "CH"))

View File

@ -6,7 +6,10 @@ import net.corda.core.internal.cert
import net.corda.core.internal.concurrent.transpose
import net.corda.core.internal.toX509CertHolder
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.*
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.minutes
import net.corda.finance.DOLLARS
import net.corda.finance.flows.CashIssueAndPaymentFlow
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
@ -16,7 +19,6 @@ import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_INTERMEDIATE_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
import net.corda.nodeapi.internal.network.NotaryInfo
import net.corda.testing.ROOT_CA
import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.driver.PortAllocation
@ -25,7 +27,6 @@ import net.corda.testing.node.internal.CompatibilityZoneParams
import net.corda.testing.node.internal.internalDriver
import net.corda.testing.node.internal.network.NetworkMapServer
import net.corda.testing.singleIdentity
import net.corda.testing.singleIdentityAndCert
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.bouncycastle.pkcs.PKCS10CertificationRequest
@ -39,6 +40,7 @@ import java.io.InputStream
import java.net.URL
import java.security.KeyPair
import java.security.cert.CertPath
import java.security.cert.CertPathValidatorException
import java.security.cert.Certificate
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
@ -50,8 +52,10 @@ class NodeRegistrationTest {
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule(true)
private val portAllocation = PortAllocation.Incremental(13000)
private val registrationHandler = RegistrationHandler(ROOT_CA)
private lateinit var server: NetworkMapServer
private lateinit var serverHostAndPort: NetworkHostAndPort
@ -73,72 +77,63 @@ class NodeRegistrationTest {
portAllocation = portAllocation,
compatibilityZone = compatibilityZone,
initialiseSerialization = false,
notarySpecs = listOf(NotarySpec(CordaX500Name(organisation = "NotaryService", locality = "Zurich", country = "CH"), validating = false)),
notarySpecs = listOf(NotarySpec(CordaX500Name("NotaryService", "Zurich", "CH"), validating = false)),
extraCordappPackagesToScan = listOf("net.corda.finance"),
onNetworkParametersGeneration = { server.networkParameters = it }
) {
val notary = defaultNotaryNode.get()
val aliceName = "Alice"
val genevieveName = "Genevieve"
val ALICE_NAME = "Alice"
val GENEVIEVE_NAME = "Genevieve"
val nodesFutures = listOf(startNode(providedName = CordaX500Name(ALICE_NAME, "London", "GB")),
startNode(providedName = CordaX500Name(GENEVIEVE_NAME, "London", "GB")))
val nodes = listOf(
startNode(providedName = CordaX500Name(aliceName, "London", "GB")),
startNode(providedName = CordaX500Name(genevieveName, "London", "GB")),
defaultNotaryNode
).transpose().getOrThrow()
val (alice, genevieve) = nodes
val (alice, genevieve) = nodesFutures.transpose().get()
val nodes = listOf(alice, genevieve, notary)
assertThat(registrationHandler.idsPolled).contains(ALICE_NAME, GENEVIEVE_NAME)
assertThat(registrationHandler.idsPolled).contains(aliceName, genevieveName)
// Notary identities are generated beforehand hence notary nodes don't go through registration.
// This test isn't specifically testing this, or relying on this behavior, though if this check fail,
// this will probably lead to the rest of the test to fail.
assertThat(registrationHandler.idsPolled).doesNotContain("NotaryService")
// Check each node has each other identity in their network map cache.
val nodeIdentities = nodes.map { it.nodeInfo.singleIdentity() }
for (node in nodes) {
assertThat(node.rpc.networkMapSnapshot().map { it.singleIdentity() }).containsAll(nodeIdentities)
assertThat(node.rpc.networkMapSnapshot()).containsOnlyElementsOf(nodes.map { it.nodeInfo })
}
// Check we nodes communicate among themselves (and the notary).
val anonymous = false
genevieve.rpc.startFlow(::CashIssueAndPaymentFlow, 1000.DOLLARS, OpaqueBytes.of(12),
genevieve.rpc.startFlow(
::CashIssueAndPaymentFlow,
1000.DOLLARS,
OpaqueBytes.of(12),
alice.nodeInfo.singleIdentity(),
anonymous,
notary.nodeInfo.singleIdentity())
.returnValue
.getOrThrow()
defaultNotaryIdentity
).returnValue.getOrThrow()
}
}
@Test
fun `node registration wrong root cert`() {
val someCert = createSelfKeyAndSelfSignedCertificate().certificate.cert
val compatibilityZone = CompatibilityZoneParams(URL("http://$serverHostAndPort"), rootCert = someCert)
val someRootCert = X509Utilities.createSelfSignedCACertificate(
CordaX500Name("Integration Test Corda Node Root CA", "R3 Ltd", "London", "GB"),
Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME))
val compatibilityZone = CompatibilityZoneParams(URL("http://$serverHostAndPort"), rootCert = someRootCert.cert)
internalDriver(
portAllocation = portAllocation,
notarySpecs = emptyList(),
compatibilityZone = compatibilityZone,
initialiseSerialization = false,
// Changing the content of the truststore makes the node fail in a number of ways if started out process.
startNodesInProcess = true
startNodesInProcess = true // We need to run the nodes in the same process so that we can capture the correct exception
) {
assertThatThrownBy {
startNode(providedName = CordaX500Name("Alice", "London", "GB")).getOrThrow()
}.isInstanceOf(WrongRootCertException::class.java)
}.isInstanceOf(CertPathValidatorException::class.java)
}
}
private fun createSelfKeyAndSelfSignedCertificate(): CertificateAndKeyPair {
val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val rootCACert = X509Utilities.createSelfSignedCACertificate(
CordaX500Name(
commonName = "Integration Test Corda Node Root CA",
organisation = "R3 Ltd",
locality = "London",
country = "GB"),
rootCAKey)
return CertificateAndKeyPair(rootCACert, rootCAKey)
}
}
@Path("certificate")
@ -185,13 +180,14 @@ class RegistrationHandler(private val rootCertAndKeyPair: CertificateAndKeyPair)
caCertPath: Array<Certificate>): Pair<CertPath, CordaX500Name> {
val request = JcaPKCS10CertificationRequest(certificationRequest)
val name = CordaX500Name.parse(request.subject.toString())
val x509CertificateHolder = X509Utilities.createCertificate(CertificateType.NODE_CA,
val nodeCaCert = X509Utilities.createCertificate(
CertificateType.NODE_CA,
caCertPath.first().toX509CertHolder(),
caKeyPair,
name,
request.publicKey,
nameConstraints = null)
val certPath = X509CertificateFactory().generateCertPath(x509CertificateHolder.cert, *caCertPath)
val certPath = X509CertificateFactory().generateCertPath(nodeCaCert.cert, *caCertPath)
return Pair(certPath, name)
}
}

View File

@ -59,7 +59,7 @@ import net.corda.node.services.vault.NodeVaultService
import net.corda.node.services.vault.VaultSoftLockManager
import net.corda.node.shell.InteractiveShell
import net.corda.node.utilities.AffinityExecutor
import net.corda.nodeapi.internal.IdentityGenerator
import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.crypto.KeyStoreWrapper
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
@ -726,10 +726,10 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
val (id, singleName) = if (notaryConfig == null || !notaryConfig.isClusterConfig) {
// Node's main identity or if it's a single node notary
Pair(IdentityGenerator.NODE_IDENTITY_ALIAS_PREFIX, configuration.myLegalName)
Pair(DevIdentityGenerator.NODE_IDENTITY_ALIAS_PREFIX, configuration.myLegalName)
} else {
// The node is part of a distributed notary whose identity must already be generated beforehand.
Pair(IdentityGenerator.DISTRIBUTED_NOTARY_ALIAS_PREFIX, null)
Pair(DevIdentityGenerator.DISTRIBUTED_NOTARY_ALIAS_PREFIX, null)
}
// TODO: Integrate with Key management service?
val privateKeyAlias = "$id-private-key"

View File

@ -12,10 +12,10 @@ import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
import org.bouncycastle.util.io.pem.PemObject
import java.io.StringWriter
import java.nio.file.Path
import java.security.KeyPair
import java.security.KeyStore
import java.security.cert.Certificate
import java.security.cert.X509Certificate
/**
* Helper for managing the node registration process, which checks for any existing certificates and requests them if
@ -32,7 +32,7 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
// TODO: Use different password for private key.
private val privateKeyPassword = config.keyStorePassword
private val trustStore: KeyStore
private val rootCert: Certificate
private val rootCert: X509Certificate
init {
require(config.trustStoreFile.exists()) {
@ -46,7 +46,7 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
"This file must contain the root CA cert of your compatibility zone. " +
"Please contact your CZ operator."
}
this.rootCert = rootCert
this.rootCert = rootCert as X509Certificate
}
/**
@ -94,12 +94,8 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v
caKeyStore.save(config.nodeKeystore, keystorePassword)
println("Node private key and certificate stored in ${config.nodeKeystore}.")
// TODO This should actually be using X509Utilities.validateCertificateChain
// 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("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)
@ -169,17 +165,3 @@ 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 that there has been a Man-in-the-middle attack when contacting the doorman.
*/
class WrongRootCertException(expected: Certificate,
actual: Certificate,
expectedFilePath: Path):
Exception("""
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 with alias $CORDA_ROOT_CA
""".trimMargin())

View File

@ -14,12 +14,14 @@ import net.corda.core.internal.createDirectories
import net.corda.node.services.config.NodeConfiguration
import net.corda.nodeapi.internal.crypto.*
import net.corda.testing.ALICE_NAME
import net.corda.testing.internal.createDevNodeCaCertPath
import net.corda.testing.internal.rigorousMock
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.After
import org.junit.Before
import org.junit.Test
import java.security.cert.CertPathValidatorException
import java.security.cert.Certificate
import java.security.cert.X509Certificate
import kotlin.test.assertFalse
@ -29,16 +31,19 @@ class NetworkRegistrationHelperTest {
private val fs = Jimfs.newFileSystem(unix())
private val requestId = SecureHash.randomSHA256().toString()
private val nodeLegalName = ALICE_NAME
private val intermediateCaName = CordaX500Name("CORDA_INTERMEDIATE_CA", "R3 Ltd", "London", "GB")
private val rootCaName = CordaX500Name("CORDA_ROOT_CA", "R3 Ltd", "London", "GB")
private val nodeCaCert = createCaCert(nodeLegalName)
private val intermediateCaCert = createCaCert(intermediateCaName)
private val rootCaCert = createCaCert(rootCaName)
private lateinit var rootCaCert: X509Certificate
private lateinit var intermediateCaCert: X509Certificate
private lateinit var nodeCaCert: X509Certificate
private lateinit var config: NodeConfiguration
@Before
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()
abstract class AbstractNodeConfiguration : NodeConfiguration
config = rigorousMock<AbstractNodeConfiguration>().also {
@ -108,11 +113,13 @@ class NetworkRegistrationHelperTest {
@Test
fun `wrong root cert in truststore`() {
saveTrustStoreWithRootCa(createCaCert(CordaX500Name("Foo", "MU", "GB")))
val rootKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val rootCert = X509Utilities.createSelfSignedCACertificate(CordaX500Name("Foo", "MU", "GB"), rootKeyPair)
saveTrustStoreWithRootCa(rootCert.cert)
val registrationHelper = createRegistrationHelper()
assertThatThrownBy {
registrationHelper.buildKeystore()
}.isInstanceOf(WrongRootCertException::class.java)
}.isInstanceOf(CertPathValidatorException::class.java)
}
private fun createRegistrationHelper(): NetworkRegistrationHelper {
@ -123,15 +130,11 @@ class NetworkRegistrationHelperTest {
return NetworkRegistrationHelper(config, certService)
}
private fun saveTrustStoreWithRootCa(rootCa: X509Certificate) {
config.trustStoreFile.parent.createDirectories()
private fun saveTrustStoreWithRootCa(rootCert: X509Certificate) {
config.certificatesDirectory.createDirectories()
loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword).also {
it.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCa)
it.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCert)
it.save(config.trustStoreFile, config.trustStorePassword)
}
}
private fun createCaCert(name: CordaX500Name): X509Certificate {
return X509Utilities.createSelfSignedCACertificate(name, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)).cert
}
}

View File

@ -8,7 +8,7 @@ import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.services.config.BFTSMaRtConfiguration
import net.corda.node.services.config.NotaryConfig
import net.corda.node.services.transactions.minCorrectReplicas
import net.corda.nodeapi.internal.IdentityGenerator
import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.testing.node.internal.demorun.*
import net.corda.testing.ALICE_NAME
import net.corda.testing.BOB_NAME
@ -62,7 +62,7 @@ class BFTNotaryCordform : CordformDefinition() {
}
override fun setup(context: CordformContext) {
IdentityGenerator.generateDistributedNotaryIdentity(
DevIdentityGenerator.generateDistributedNotaryIdentity(
notaryNames.map { context.baseDirectory(it.toString()) },
clusterName,
minCorrectReplicas(clusterSize)

View File

@ -7,7 +7,7 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.services.config.NotaryConfig
import net.corda.node.services.config.RaftConfig
import net.corda.nodeapi.internal.IdentityGenerator
import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.testing.node.internal.demorun.*
import net.corda.testing.ALICE_NAME
import net.corda.testing.BOB_NAME
@ -58,7 +58,7 @@ class RaftNotaryCordform : CordformDefinition() {
}
override fun setup(context: CordformContext) {
IdentityGenerator.generateDistributedNotaryIdentity(
DevIdentityGenerator.generateDistributedNotaryIdentity(
notaryNames.map { context.baseDirectory(it.toString()) },
clusterName
)

View File

@ -38,7 +38,7 @@ import net.corda.node.services.transactions.BFTSMaRt
import net.corda.node.services.transactions.InMemoryTransactionVerifierService
import net.corda.node.utilities.AffinityExecutor
import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor
import net.corda.nodeapi.internal.IdentityGenerator
import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.nodeapi.internal.config.User
import net.corda.nodeapi.internal.network.NetworkParametersCopier
import net.corda.nodeapi.internal.network.NotaryInfo
@ -235,7 +235,7 @@ open class MockNetwork(private val cordappPackages: List<String>,
private fun generateNotaryIdentities(): List<NotaryInfo> {
return notarySpecs.mapIndexed { index, (name, validating) ->
val identity = IdentityGenerator.installKeyStoreWithNodeIdentity(baseDirectory(nextNodeId + index), name)
val identity = DevIdentityGenerator.installKeyStoreWithNodeIdentity(baseDirectory(nextNodeId + index), name)
NotaryInfo(identity, validating)
}
}

View File

@ -10,10 +10,12 @@ import net.corda.cordform.CordformContext
import net.corda.cordform.CordformNode
import net.corda.core.concurrent.CordaFuture
import net.corda.core.concurrent.firstOf
import net.corda.core.crypto.random63BitValue
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.*
import net.corda.core.internal.ThreadBox
import net.corda.core.internal.concurrent.*
import net.corda.core.internal.copyTo
import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.node.services.NetworkMapCache
import net.corda.core.toFuture
@ -28,7 +30,7 @@ import net.corda.node.services.Permissions
import net.corda.node.services.config.*
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
import net.corda.node.utilities.registration.NetworkRegistrationHelper
import net.corda.nodeapi.internal.IdentityGenerator
import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.nodeapi.internal.addShutdownHook
import net.corda.nodeapi.internal.config.User
import net.corda.nodeapi.internal.config.parseAs
@ -37,13 +39,13 @@ 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.nodeapi.internal.network.NetworkParameters
import net.corda.nodeapi.internal.network.NetworkParametersCopier
import net.corda.nodeapi.internal.network.NodeInfoFilesCopier
import net.corda.nodeapi.internal.network.NotaryInfo
import net.corda.testing.ALICE_NAME
import net.corda.testing.BOB_NAME
import net.corda.testing.DUMMY_BANK_A_NAME
import net.corda.nodeapi.internal.network.NetworkParameters
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.driver.*
import net.corda.testing.node.ClusterSpec
@ -66,7 +68,7 @@ import java.nio.file.StandardCopyOption
import java.security.cert.X509Certificate
import java.time.Duration
import java.time.Instant
import java.time.ZoneOffset
import java.time.ZoneOffset.UTC
import java.time.format.DateTimeFormatter
import java.util.*
import java.util.concurrent.Executors
@ -266,13 +268,13 @@ class DriverDSLImpl(
clusterNodes.put(ClusterType.NON_VALIDATING_BFT, name)
} else {
// We have all we need here to generate the identity for single node notaries
val identity = IdentityGenerator.installKeyStoreWithNodeIdentity(baseDirectory(name), legalName = name)
val identity = DevIdentityGenerator.installKeyStoreWithNodeIdentity(baseDirectory(name), legalName = name)
notaryInfos += NotaryInfo(identity, notaryConfig.validating)
}
}
clusterNodes.asMap().forEach { type, nodeNames ->
val identity = IdentityGenerator.generateDistributedNotaryIdentity(
val identity = DevIdentityGenerator.generateDistributedNotaryIdentity(
dirs = nodeNames.map { baseDirectory(it) },
notaryName = type.clusterName
)
@ -360,9 +362,9 @@ class DriverDSLImpl(
private fun generateNotaryIdentities(): List<NotaryInfo> {
return notarySpecs.map { spec ->
val identity = if (spec.cluster == null) {
IdentityGenerator.installKeyStoreWithNodeIdentity(baseDirectory(spec.name), spec.name, compatibilityZone?.rootCert)
DevIdentityGenerator.installKeyStoreWithNodeIdentity(baseDirectory(spec.name), spec.name, compatibilityZone?.rootCert)
} else {
IdentityGenerator.generateDistributedNotaryIdentity(
DevIdentityGenerator.generateDistributedNotaryIdentity(
dirs = generateNodeNames(spec).map { baseDirectory(it) },
notaryName = spec.name,
customRootCert = compatibilityZone?.rootCert
@ -890,8 +892,7 @@ fun <A> internalDriver(
}
fun getTimestampAsDirectoryName(): String {
// Add a random number in case 2 tests are started in the same instant.
return DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(ZoneOffset.UTC).format(Instant.now()) + random63BitValue()
return DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss.SSS").withZone(UTC).format(Instant.now())
}
fun writeConfig(path: Path, filename: String, config: Config) {

View File

@ -1,26 +1,21 @@
package net.corda.testing.node.internal.network
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.SignedData
import net.corda.core.crypto.sha256
import net.corda.core.identity.CordaX500Name
import net.corda.core.crypto.*
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.cert
import net.corda.core.internal.toX509CertHolder
import net.corda.core.node.NodeInfo
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.network.DigitalSignatureWithCert
import net.corda.nodeapi.internal.network.NetworkMap
import net.corda.nodeapi.internal.network.NetworkParameters
import net.corda.nodeapi.internal.network.SignedNetworkMap
import net.corda.nodeapi.internal.*
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.testing.ROOT_CA
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.server.ServerConnector
@ -50,7 +45,7 @@ class NetworkMapServer(cacheTimeout: Duration,
private fun networkMapKeyAndCert(rootCAKeyAndCert: CertificateAndKeyPair): CertificateAndKeyPair {
val networkMapKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val networkMapCert = X509Utilities.createCertificate(
CertificateType.INTERMEDIATE_CA,
CertificateType.NETWORK_MAP,
rootCAKeyAndCert.certificate,
rootCAKeyAndCert.keyPair,
CordaX500Name("Corda Network Map", "R3 Ltd", "London","GB"),

View File

@ -5,24 +5,24 @@ package net.corda.testing
import net.corda.core.contracts.PartyAndReference
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.*
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.entropyToKeyPair
import net.corda.core.crypto.generateKeyPair
import net.corda.core.crypto.toStringShort
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.cert
import net.corda.core.internal.unspecifiedCountry
import net.corda.core.internal.x500Name
import net.corda.core.node.NodeInfo
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.services.config.configureDevKeyAndTrustStores
import net.corda.nodeapi.internal.config.SSLConfiguration
import net.corda.nodeapi.internal.createDevNodeCa
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
import net.corda.nodeapi.internal.crypto.X509Utilities
import org.bouncycastle.asn1.x509.GeneralName
import org.bouncycastle.asn1.x509.GeneralSubtree
import org.bouncycastle.asn1.x509.NameConstraints
import org.bouncycastle.cert.X509CertificateHolder
import java.math.BigInteger
import java.nio.file.Files
@ -92,15 +92,7 @@ fun getTestPartyAndCertificate(party: Party): PartyAndCertificate {
val trustRoot: X509CertificateHolder = DEV_TRUST_ROOT
val intermediate: CertificateAndKeyPair = DEV_CA
val nodeCaKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val nodeCaCert = X509Utilities.createCertificate(
CertificateType.NODE_CA,
intermediate.certificate,
intermediate.keyPair,
party.name,
nodeCaKeyPair.public,
nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, party.name.x500Name))), arrayOf()))
val (nodeCaCert, nodeCaKeyPair) = createDevNodeCa(intermediate, party.name)
val identityCert = X509Utilities.createCertificate(
CertificateType.LEGAL_IDENTITY,

View File

@ -1,6 +1,12 @@
package net.corda.testing.internal
import net.corda.core.crypto.Crypto
import net.corda.core.identity.CordaX500Name
import net.corda.core.utilities.loggerFor
import net.corda.nodeapi.internal.createDevNodeCa
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.serialization.amqp.AMQP_ENABLED
import org.mockito.Mockito
import org.mockito.internal.stubbing.answers.ThrowsException
@ -42,3 +48,44 @@ fun <T> rigorousMock(clazz: Class<T>): T = Mockito.mock(clazz) {
it.callRealMethod()
}
}
private val defaultRootCaName = CordaX500Name("Corda Root CA", "R3 Ltd", "London", "GB")
private val defaultIntermediateCaName = CordaX500Name("Corda Intermediate CA", "R3 Ltd", "London", "GB")
/**
* Returns a pair of [CertificateAndKeyPair]s, the first being the root CA and the second the intermediate CA.
* @param rootCaName The subject name for the root CA cert.
* @param intermediateCaName The subject name for the intermediate CA cert.
*/
fun createDevIntermediateCaCertPath(
rootCaName: CordaX500Name = defaultRootCaName,
intermediateCaName: CordaX500Name = defaultIntermediateCaName
): Pair<CertificateAndKeyPair, CertificateAndKeyPair> {
val rootKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val rootCert = X509Utilities.createSelfSignedCACertificate(rootCaName, rootKeyPair)
val intermediateCaKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val intermediateCaCert = X509Utilities.createCertificate(
CertificateType.INTERMEDIATE_CA,
rootCert,
rootKeyPair,
intermediateCaName,
intermediateCaKeyPair.public)
return Pair(CertificateAndKeyPair(rootCert, rootKeyPair), CertificateAndKeyPair(intermediateCaCert, intermediateCaKeyPair))
}
/**
* Returns a triple of [CertificateAndKeyPair]s, the first being the root CA, the second the intermediate CA and the third
* the node CA.
* @param legalName The subject name for the node CA cert.
*/
fun createDevNodeCaCertPath(
legalName: CordaX500Name,
rootCaName: CordaX500Name = defaultRootCaName,
intermediateCaName: CordaX500Name = defaultIntermediateCaName
): Triple<CertificateAndKeyPair, CertificateAndKeyPair, CertificateAndKeyPair> {
val (rootCa, intermediateCa) = createDevIntermediateCaCertPath(rootCaName, intermediateCaName)
val nodeCa = createDevNodeCa(intermediateCa, legalName)
return Triple(rootCa, intermediateCa, nodeCa)
}

View File

@ -13,7 +13,7 @@ import net.corda.demobench.pty.R3Pty
import net.corda.nodeapi.internal.network.NetworkParameters
import net.corda.nodeapi.internal.network.NetworkParametersCopier
import net.corda.nodeapi.internal.network.NotaryInfo
import net.corda.nodeapi.internal.IdentityGenerator
import net.corda.nodeapi.internal.DevIdentityGenerator
import tornadofx.*
import java.io.IOException
import java.lang.management.ManagementFactory
@ -153,7 +153,7 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() {
// Generate notary identity and save it into node's directory. This identity will be used in network parameters.
private fun getNotaryIdentity(config: NodeConfigWrapper): Party {
return IdentityGenerator.installKeyStoreWithNodeIdentity(config.nodeDir, config.nodeConfig.myLegalName)
return DevIdentityGenerator.installKeyStoreWithNodeIdentity(config.nodeDir, config.nodeConfig.myLegalName)
}
fun reset() {