mirror of
https://github.com/corda/corda.git
synced 2024-12-18 20:47:57 +00:00
CORDA-3979: Support for multiple trust roots (#6772)
This commit is contained in:
parent
401d8b8856
commit
4193adf6fd
@ -42,8 +42,11 @@ class PartyAndCertificate(val certPath: CertPath) {
|
||||
override fun toString(): String = party.toString()
|
||||
|
||||
/** Verify the certificate path is valid. */
|
||||
fun verify(trustAnchor: TrustAnchor): PKIXCertPathValidatorResult {
|
||||
val result = certPath.validate(trustAnchor)
|
||||
fun verify(trustAnchor: TrustAnchor): PKIXCertPathValidatorResult = verify(setOf(trustAnchor))
|
||||
|
||||
/** Verify the certificate path is valid against one of the specified trust anchors. */
|
||||
fun verify(trustAnchors: Set<TrustAnchor>): PKIXCertPathValidatorResult {
|
||||
val result = certPath.validate(trustAnchors)
|
||||
// Apply Corda-specific validity rules to the chain. This only applies to chains with any roles present, so
|
||||
// an all-null chain is in theory valid.
|
||||
var parentRole: CertRole? = CertRole.extract(result.trustAnchor.trustedCert)
|
||||
|
@ -506,8 +506,8 @@ fun ExecutorService.join() {
|
||||
}
|
||||
|
||||
// TODO: Currently the certificate revocation status is not handled here. Nowhere in the code the second parameter is used. Consider adding the support in the future.
|
||||
fun CertPath.validate(trustAnchor: TrustAnchor, checkRevocation: Boolean = false): PKIXCertPathValidatorResult {
|
||||
val parameters = PKIXParameters(setOf(trustAnchor)).apply { isRevocationEnabled = checkRevocation }
|
||||
fun CertPath.validate(trustAnchors: Set<TrustAnchor>, checkRevocation: Boolean = false): PKIXCertPathValidatorResult {
|
||||
val parameters = PKIXParameters(trustAnchors).apply { isRevocationEnabled = checkRevocation }
|
||||
try {
|
||||
return CertPathValidator.getInstance("PKIX").validate(this, parameters) as PKIXCertPathValidatorResult
|
||||
} catch (e: CertPathValidatorException) {
|
||||
@ -517,8 +517,8 @@ Reason: ${e.reason}
|
||||
Offending cert index: ${e.index}
|
||||
Cert path: $this
|
||||
|
||||
Trust anchor:
|
||||
$trustAnchor""", e, this, e.index)
|
||||
Trust anchors:
|
||||
$trustAnchors""", e, this, e.index)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,5 +40,5 @@ interface NetworkParametersStorage : NetworkParametersService {
|
||||
/**
|
||||
* Set information that given parameters are current parameters for the network.
|
||||
*/
|
||||
fun setCurrentParameters(currentSignedParameters: SignedDataWithCert<NetworkParameters>, trustRoot: X509Certificate)
|
||||
fun setCurrentParameters(currentSignedParameters: SignedDataWithCert<NetworkParameters>, trustRoots: Set<X509Certificate>)
|
||||
}
|
||||
|
@ -358,7 +358,7 @@ class X509UtilitiesTest {
|
||||
val peerChain = clientSocket.session.peerCertificates.x509
|
||||
val peerX500Principal = peerChain[0].subjectX500Principal
|
||||
assertEquals(MEGA_CORP.name.x500Principal, peerX500Principal)
|
||||
X509Utilities.validateCertificateChain(rootCa.certificate, peerChain)
|
||||
X509Utilities.validateCertificateChain(setOf(rootCa.certificate), peerChain)
|
||||
val output = DataOutputStream(clientSocket.outputStream)
|
||||
output.writeUTF("Hello World")
|
||||
var timeout = 0
|
||||
@ -432,7 +432,7 @@ class X509UtilitiesTest {
|
||||
val peerChain = client.engine!!.session.peerCertificates.x509
|
||||
val peerX500Principal = peerChain[0].subjectX500Principal
|
||||
assertEquals(MEGA_CORP.name.x500Principal, peerX500Principal)
|
||||
X509Utilities.validateCertificateChain(rootCa.certificate, peerChain)
|
||||
X509Utilities.validateCertificateChain(setOf(rootCa.certificate), peerChain)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -372,7 +372,7 @@ class NetworkBootstrapperTest {
|
||||
private val Path.networkParameters: NetworkParameters
|
||||
get() {
|
||||
return (this / NETWORK_PARAMS_FILE_NAME).readObject<SignedNetworkParameters>()
|
||||
.verifiedNetworkParametersCert(DEV_ROOT_CA.certificate)
|
||||
.verifiedNetworkParametersCert(setOf(DEV_ROOT_CA.certificate))
|
||||
}
|
||||
|
||||
private val Path.nodeInfoFile: Path
|
||||
|
@ -105,6 +105,17 @@ fun createDevNodeCa(intermediateCa: CertificateAndKeyPair,
|
||||
return CertificateAndKeyPair(cert, nodeKeyPair)
|
||||
}
|
||||
|
||||
fun createDevNodeIdentity(nodeCa: CertificateAndKeyPair, legalName: CordaX500Name): CertificateAndKeyPair {
|
||||
val keyPair = generateKeyPair()
|
||||
val cert = X509Utilities.createCertificate(
|
||||
CertificateType.LEGAL_IDENTITY,
|
||||
nodeCa.certificate,
|
||||
nodeCa.keyPair,
|
||||
legalName.x500Principal,
|
||||
keyPair.public)
|
||||
return CertificateAndKeyPair(cert, keyPair)
|
||||
}
|
||||
|
||||
val DEV_INTERMEDIATE_CA: CertificateAndKeyPair get() = DevCaHelper.loadDevCa(X509Utilities.CORDA_INTERMEDIATE_CA)
|
||||
val DEV_ROOT_CA: CertificateAndKeyPair get() = DevCaHelper.loadDevCa(X509Utilities.CORDA_ROOT_CA)
|
||||
const val DEV_CA_PRIVATE_KEY_PASS: String = "cordacadevkeypass"
|
||||
|
@ -124,17 +124,17 @@ object X509Utilities {
|
||||
return createCertificate(CertificateType.ROOT_CA, subject, keyPair, subject, keyPair.public, window)
|
||||
}
|
||||
|
||||
fun validateCertificateChain(trustedRoot: X509Certificate, vararg certificates: X509Certificate) {
|
||||
validateCertificateChain(trustedRoot, certificates.asList())
|
||||
fun validateCertificateChain(trustedRoots: Set<X509Certificate>, vararg certificates: X509Certificate) {
|
||||
validateCertificateChain(trustedRoots, certificates.asList())
|
||||
}
|
||||
|
||||
fun validateCertificateChain(trustedRoot: X509Certificate, certificates: List<X509Certificate>) {
|
||||
fun validateCertificateChain(trustedRoots: Set<X509Certificate>, certificates: List<X509Certificate>) {
|
||||
require(certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" }
|
||||
validateCertPath(trustedRoot, buildCertPath(certificates))
|
||||
validateCertPath(trustedRoots, buildCertPath(certificates))
|
||||
}
|
||||
|
||||
fun validateCertPath(trustedRoot: X509Certificate, certPath: CertPath) {
|
||||
certPath.validate(TrustAnchor(trustedRoot, null))
|
||||
fun validateCertPath(trustedRoots: Set<X509Certificate>, certPath: CertPath) {
|
||||
certPath.validate(trustedRoots.map { TrustAnchor(it, null) }.toSet())
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -399,7 +399,7 @@ constructor(private val initSerEnv: Boolean,
|
||||
|
||||
when (netParamsFilesGrouped.size) {
|
||||
0 -> return null
|
||||
1 -> return netParamsFilesGrouped.keys.first().deserialize().verifiedNetworkParametersCert(DEV_ROOT_CA.certificate)
|
||||
1 -> return netParamsFilesGrouped.keys.first().deserialize().verifiedNetworkParametersCert(setOf(DEV_ROOT_CA.certificate))
|
||||
}
|
||||
|
||||
val msg = StringBuilder("Differing sets of network parameters were found. Make sure all the nodes have the same " +
|
||||
@ -409,7 +409,7 @@ constructor(private val initSerEnv: Boolean,
|
||||
netParamsFiles.map { it.parent.fileName }.joinTo(msg, ", ")
|
||||
msg.append(":\n")
|
||||
val netParamsString = try {
|
||||
bytes.deserialize().verifiedNetworkParametersCert(DEV_ROOT_CA.certificate).toString()
|
||||
bytes.deserialize().verifiedNetworkParametersCert(setOf(DEV_ROOT_CA.certificate)).toString()
|
||||
} catch (e: Exception) {
|
||||
"Invalid network parameters file: $e"
|
||||
}
|
||||
|
@ -55,25 +55,26 @@ data class ParametersUpdate(
|
||||
)
|
||||
|
||||
/** Verify that a certificate path and its [CertRole] is correct. */
|
||||
fun <T : Any> SignedDataWithCert<T>.verifiedCertWithRole(rootCert: X509Certificate, vararg certRoles: CertRole): T {
|
||||
fun <T : Any> SignedDataWithCert<T>.verifiedCertWithRole(rootCerts: Set<X509Certificate>, vararg certRoles: CertRole): T {
|
||||
require(CertRole.extract(sig.by) in certRoles) { "Incorrect cert role: ${CertRole.extract(sig.by)}" }
|
||||
val path = if (sig.parentCertsChain.isEmpty()) {
|
||||
listOf(sig.by, rootCert)
|
||||
val rootCandidate = rootCerts.firstOrNull { it.subjectX500Principal == sig.by.issuerX500Principal }
|
||||
listOf(sig.by, rootCandidate ?: rootCerts.first())
|
||||
} else {
|
||||
sig.fullCertChain
|
||||
}
|
||||
X509Utilities.validateCertificateChain(rootCert, path)
|
||||
X509Utilities.validateCertificateChain(rootCerts, path)
|
||||
return verified()
|
||||
}
|
||||
|
||||
/** Verify that a Network Map certificate path and its [CertRole] is correct. */
|
||||
fun <T : Any> SignedDataWithCert<T>.verifiedNetworkMapCert(rootCert: X509Certificate): T {
|
||||
return verifiedCertWithRole(rootCert, CertRole.NETWORK_MAP)
|
||||
fun <T : Any> SignedDataWithCert<T>.verifiedNetworkMapCert(rootCerts: Set<X509Certificate>): T {
|
||||
return verifiedCertWithRole(rootCerts, CertRole.NETWORK_MAP)
|
||||
}
|
||||
|
||||
/** Verify that a Network Parameters certificate path and its [CertRole] is correct. */
|
||||
fun <T : Any> SignedDataWithCert<T>.verifiedNetworkParametersCert(rootCert: X509Certificate): T {
|
||||
fun <T : Any> SignedDataWithCert<T>.verifiedNetworkParametersCert(rootCerts: Set<X509Certificate>): T {
|
||||
// for backwards compatibility we allow network parameters to be signed with
|
||||
// the networkmap cert, but going forwards we also accept the specific netparams cert as well
|
||||
return verifiedCertWithRole(rootCert, CertRole.NETWORK_PARAMETERS, CertRole.NETWORK_MAP)
|
||||
return verifiedCertWithRole(rootCerts, CertRole.NETWORK_PARAMETERS, CertRole.NETWORK_MAP)
|
||||
}
|
@ -33,7 +33,7 @@ class DevCertificatesTest {
|
||||
val certPath = X509Utilities.buildCertPath(*oldX509Certificates)
|
||||
|
||||
// when
|
||||
certPath.validate(newTrustAnchor)
|
||||
certPath.validate(setOf(newTrustAnchor))
|
||||
|
||||
// then no exception is thrown
|
||||
}
|
||||
|
@ -127,7 +127,7 @@ class TlsDiffAlgorithmsTest(private val serverAlgo: String, private val clientAl
|
||||
val peerChain = peerChainTry.getOrThrow()
|
||||
val peerX500Principal = peerChain[0].subjectX500Principal
|
||||
assertEquals(serverCa.certificate.subjectX500Principal, peerX500Principal)
|
||||
X509Utilities.validateCertificateChain(rootCa, peerChain)
|
||||
X509Utilities.validateCertificateChain(setOf(rootCa), peerChain)
|
||||
with(DataOutputStream(clientSocket.outputStream)) {
|
||||
writeUTF(testPhrase)
|
||||
}
|
||||
|
@ -175,7 +175,7 @@ class TlsDiffProtocolsTest(private val serverAlgo: String, private val clientAlg
|
||||
val peerChain = peerChainTry.getOrThrow()
|
||||
val peerX500Principal = peerChain[0].subjectX500Principal
|
||||
assertEquals(serverCa.certificate.subjectX500Principal, peerX500Principal)
|
||||
X509Utilities.validateCertificateChain(rootCa, peerChain)
|
||||
X509Utilities.validateCertificateChain(setOf(rootCa), peerChain)
|
||||
with(DataOutputStream(clientSocket.outputStream)) {
|
||||
writeUTF(testPhrase)
|
||||
}
|
||||
|
@ -279,7 +279,7 @@ class CordappConstraintsTests {
|
||||
printVault(alice, states)
|
||||
|
||||
// Claim the package, publish the new network parameters , and restart all nodes.
|
||||
val parameters = NetworkParametersReader(DEV_ROOT_CA.certificate, null, notary.baseDirectory).read().networkParameters
|
||||
val parameters = NetworkParametersReader(setOf(DEV_ROOT_CA.certificate), null, notary.baseDirectory).read().networkParameters
|
||||
|
||||
val newParams = parameters.copy(
|
||||
packageOwnership = mapOf("net.corda.finance.contracts.asset" to packageOwnerKey)
|
||||
|
@ -0,0 +1,215 @@
|
||||
package net.corda.node.services.identity
|
||||
|
||||
import com.nhaarman.mockito_kotlin.doReturn
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.coretesting.internal.stubs.CertificateStoreStubs
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.GBP
|
||||
import net.corda.finance.POUNDS
|
||||
import net.corda.finance.USD
|
||||
import net.corda.finance.flows.CashIssueAndPaymentFlow
|
||||
import net.corda.finance.workflows.getCashBalance
|
||||
import net.corda.node.services.config.NotaryConfig
|
||||
import net.corda.nodeapi.internal.createDevNetworkMapCa
|
||||
import net.corda.nodeapi.internal.createDevNodeCa
|
||||
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import net.corda.nodeapi.internal.installDevNodeCaCertPath
|
||||
import net.corda.nodeapi.internal.network.NetworkParametersCopier
|
||||
import net.corda.nodeapi.internal.registerDevP2pCertificates
|
||||
import net.corda.nodeapi.internal.storeLegalIdentity
|
||||
import net.corda.testing.common.internal.addNotary
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.BOB_NAME
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.internal.createDevIntermediateCaCertPath
|
||||
import net.corda.testing.node.internal.FINANCE_CORDAPPS
|
||||
import net.corda.testing.node.internal.InternalMockNetwork
|
||||
import net.corda.testing.node.internal.InternalMockNodeParameters
|
||||
import net.corda.testing.node.internal.TestStartedNode
|
||||
import net.corda.testing.node.internal.startFlow
|
||||
import org.junit.After
|
||||
import org.junit.Test
|
||||
import javax.security.auth.x500.X500Principal
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class TrustRootTest {
|
||||
private val ref = OpaqueBytes.of(0x01)
|
||||
|
||||
private val TestStartedNode.party get() = info.legalIdentities.first()
|
||||
|
||||
private lateinit var mockNet: InternalMockNetwork
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
mockNet.stopNodes()
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `can start flow between nodes with different roots`() {
|
||||
mockNet = InternalMockNetwork(cordappsForAllNodes = FINANCE_CORDAPPS, notarySpecs = listOf())
|
||||
val (rootCa1, intermediateCa1) = createDevIntermediateCaCertPath(X500Principal("CN=Root1"))
|
||||
val (rootCa2, intermediateCa2) = createDevIntermediateCaCertPath(X500Principal("CN=Root2"))
|
||||
|
||||
// Initiator and acceptor have different roots, both are present on all truststores.
|
||||
createKeyStores(0, DUMMY_NOTARY_NAME, intermediateCa1, rootCa1, rootCa2)
|
||||
createKeyStores(1, ALICE_NAME, intermediateCa1, rootCa1, rootCa2)
|
||||
createKeyStores(2, BOB_NAME, intermediateCa2, rootCa2, rootCa1)
|
||||
createNetworkParameters(rootCa1, 0, 1, 2)
|
||||
|
||||
val notary = mockNet.createNode(InternalMockNodeParameters(
|
||||
forcedID = 0,
|
||||
legalName = DUMMY_NOTARY_NAME,
|
||||
configOverrides = { doReturn(NotaryConfig(false)).whenever(it).notary }
|
||||
))
|
||||
val alice = mockNet.createNode(InternalMockNodeParameters(forcedID = 1, legalName = ALICE_NAME))
|
||||
val bob = mockNet.createNode(InternalMockNodeParameters(forcedID = 2, legalName = BOB_NAME))
|
||||
|
||||
assertEquals(rootCa1.certificate, notary.services.identityService.trustRoot)
|
||||
assertEquals(rootCa1.certificate, alice.services.identityService.trustRoot)
|
||||
assertEquals(rootCa2.certificate, bob.services.identityService.trustRoot)
|
||||
|
||||
alice.services.startFlow(CashIssueAndPaymentFlow(1000.DOLLARS, ref, bob.party, false, notary.party))
|
||||
mockNet.runNetwork()
|
||||
bob.services.startFlow(CashIssueAndPaymentFlow(1000.POUNDS, ref, alice.party, false, notary.party))
|
||||
mockNet.runNetwork()
|
||||
|
||||
// Ledger state was changed
|
||||
assertEquals(1000.POUNDS, alice.services.getCashBalance(GBP))
|
||||
assertEquals(1000.DOLLARS, bob.services.getCashBalance(USD))
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `fail to start flow when missing acceptor's root on the initiator side`() {
|
||||
mockNet = InternalMockNetwork(cordappsForAllNodes = FINANCE_CORDAPPS, notarySpecs = listOf())
|
||||
val (rootCa1, intermediateCa1) = createDevIntermediateCaCertPath(X500Principal("CN=Root1"))
|
||||
val (rootCa2, intermediateCa2) = createDevIntermediateCaCertPath(X500Principal("CN=Root2"))
|
||||
|
||||
// Acceptor's root is missing on the initiator side.
|
||||
createKeyStores(0, DUMMY_NOTARY_NAME, intermediateCa1, rootCa1, rootCa2)
|
||||
createKeyStores(1, ALICE_NAME, intermediateCa1, rootCa1)
|
||||
createKeyStores(2, BOB_NAME, intermediateCa2, rootCa2, rootCa1)
|
||||
createNetworkParameters(rootCa1, 0, 1, 2)
|
||||
|
||||
val notary = mockNet.createNode(InternalMockNodeParameters(
|
||||
forcedID = 0,
|
||||
legalName = DUMMY_NOTARY_NAME,
|
||||
configOverrides = { doReturn(NotaryConfig(false)).whenever(it).notary }
|
||||
))
|
||||
val alice = mockNet.createNode(InternalMockNodeParameters(forcedID = 1, legalName = ALICE_NAME))
|
||||
val bob = mockNet.createNode(InternalMockNodeParameters(forcedID = 2, legalName = BOB_NAME))
|
||||
|
||||
assertEquals(rootCa1.certificate, notary.services.identityService.trustRoot)
|
||||
assertEquals(rootCa1.certificate, alice.services.identityService.trustRoot)
|
||||
assertEquals(rootCa2.certificate, bob.services.identityService.trustRoot)
|
||||
|
||||
alice.services.startFlow(CashIssueAndPaymentFlow(1000.DOLLARS, ref, bob.party, false, notary.party))
|
||||
mockNet.runNetwork()
|
||||
|
||||
// Ledger state remains unchanged.
|
||||
assertEquals(1000.DOLLARS, alice.services.getCashBalance(USD))
|
||||
assertEquals(0.DOLLARS, bob.services.getCashBalance(USD))
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `fail to notarise when missing initiator's root on the notary side`() {
|
||||
mockNet = InternalMockNetwork(cordappsForAllNodes = FINANCE_CORDAPPS, notarySpecs = listOf())
|
||||
val (rootCa1, intermediateCa1) = createDevIntermediateCaCertPath(X500Principal("CN=Root1"))
|
||||
val (rootCa2, intermediateCa2) = createDevIntermediateCaCertPath(X500Principal("CN=Root2"))
|
||||
|
||||
// Initiator's root is missing on the notary side.
|
||||
createKeyStores(0, DUMMY_NOTARY_NAME, intermediateCa1, rootCa1)
|
||||
createKeyStores(1, ALICE_NAME, intermediateCa2, rootCa2, rootCa1)
|
||||
createKeyStores(2, BOB_NAME, intermediateCa1, rootCa1, rootCa2)
|
||||
createNetworkParameters(rootCa1, 0, 1, 2)
|
||||
|
||||
val notary = mockNet.createNode(InternalMockNodeParameters(
|
||||
forcedID = 0,
|
||||
legalName = DUMMY_NOTARY_NAME,
|
||||
configOverrides = { doReturn(NotaryConfig(false)).whenever(it).notary }
|
||||
))
|
||||
val alice = mockNet.createNode(InternalMockNodeParameters(forcedID = 1, legalName = ALICE_NAME))
|
||||
val bob = mockNet.createNode(InternalMockNodeParameters(forcedID = 2, legalName = BOB_NAME))
|
||||
|
||||
assertEquals(rootCa1.certificate, notary.services.identityService.trustRoot)
|
||||
assertEquals(rootCa2.certificate, alice.services.identityService.trustRoot)
|
||||
assertEquals(rootCa1.certificate, bob.services.identityService.trustRoot)
|
||||
|
||||
alice.services.startFlow(CashIssueAndPaymentFlow(1000.DOLLARS, ref, bob.party, false, notary.party))
|
||||
mockNet.runNetwork()
|
||||
|
||||
// Ledger state remains unchanged
|
||||
assertEquals(1000.DOLLARS, alice.services.getCashBalance(USD))
|
||||
assertEquals(0.DOLLARS, bob.services.getCashBalance(USD))
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `can notarise when missing acceptor's root on the notary side`() {
|
||||
mockNet = InternalMockNetwork(cordappsForAllNodes = FINANCE_CORDAPPS, notarySpecs = listOf())
|
||||
val (rootCa1, intermediateCa1) = createDevIntermediateCaCertPath(X500Principal("CN=Root1"))
|
||||
val (rootCa2, intermediateCa2) = createDevIntermediateCaCertPath(X500Principal("CN=Root2"))
|
||||
|
||||
// Acceptor's root is missing on the notary side.
|
||||
createKeyStores(0, DUMMY_NOTARY_NAME, intermediateCa1, rootCa1)
|
||||
createKeyStores(1, ALICE_NAME, intermediateCa1, rootCa1, rootCa2)
|
||||
createKeyStores(2, BOB_NAME, intermediateCa2, rootCa2, rootCa1)
|
||||
createNetworkParameters(rootCa1, 0, 1, 2)
|
||||
|
||||
val notary = mockNet.createNode(InternalMockNodeParameters(
|
||||
forcedID = 0,
|
||||
legalName = DUMMY_NOTARY_NAME,
|
||||
configOverrides = { doReturn(NotaryConfig(false)).whenever(it).notary }
|
||||
))
|
||||
val alice = mockNet.createNode(InternalMockNodeParameters(forcedID = 1, legalName = ALICE_NAME))
|
||||
val bob = mockNet.createNode(InternalMockNodeParameters(forcedID = 2, legalName = BOB_NAME))
|
||||
|
||||
assertEquals(rootCa1.certificate, notary.services.identityService.trustRoot)
|
||||
assertEquals(rootCa1.certificate, alice.services.identityService.trustRoot)
|
||||
assertEquals(rootCa2.certificate, bob.services.identityService.trustRoot)
|
||||
|
||||
alice.services.startFlow(CashIssueAndPaymentFlow(1000.DOLLARS, ref, bob.party, false, notary.party))
|
||||
mockNet.runNetwork()
|
||||
|
||||
// Ledger state was changed
|
||||
assertEquals(0.DOLLARS, alice.services.getCashBalance(USD))
|
||||
assertEquals(1000.DOLLARS, bob.services.getCashBalance(USD))
|
||||
}
|
||||
|
||||
private fun createKeyStores(nodeId: Int,
|
||||
legalName: CordaX500Name,
|
||||
intermediateCa: CertificateAndKeyPair,
|
||||
vararg rootCa: CertificateAndKeyPair) {
|
||||
val certDir = mockNet.baseDirectory(nodeId) / "certificates"
|
||||
val keyStore = CertificateStoreStubs.Signing.withCertificatesDirectory(certDir)
|
||||
val sslConfig = CertificateStoreStubs.P2P.withCertificatesDirectory(certDir)
|
||||
|
||||
val nodeCa = createDevNodeCa(intermediateCa, legalName)
|
||||
keyStore.get(true).apply {
|
||||
installDevNodeCaCertPath(legalName, rootCa.first().certificate, intermediateCa, nodeCa)
|
||||
storeLegalIdentity(X509Utilities.NODE_IDENTITY_KEY_ALIAS)
|
||||
}
|
||||
sslConfig.keyStore.get(true).apply {
|
||||
registerDevP2pCertificates(legalName, rootCa.first().certificate, intermediateCa, nodeCa)
|
||||
}
|
||||
sslConfig.trustStore.get(true).apply {
|
||||
rootCa.forEachIndexed { i, root ->
|
||||
this["${X509Utilities.CORDA_ROOT_CA}-$i"] = root.certificate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createNetworkParameters(rootCa: CertificateAndKeyPair, vararg nodeIds: Int) {
|
||||
val notaryCertDir = mockNet.baseDirectory(nodeIds.first()) / "certificates"
|
||||
val notaryKeyStore = CertificateStoreStubs.Signing.withCertificatesDirectory(notaryCertDir)
|
||||
val notary = Party(notaryKeyStore.get()[X509Utilities.NODE_IDENTITY_KEY_ALIAS])
|
||||
val networkParameters = testNetworkParameters(epoch = 2).addNotary(notary, false)
|
||||
NetworkParametersCopier(networkParameters, createDevNetworkMapCa(rootCa), overwriteFile = true).apply {
|
||||
nodeIds.forEach { install(mockNet.baseDirectory(it)) }
|
||||
}
|
||||
}
|
||||
}
|
@ -4,8 +4,10 @@ import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.toStringShort
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.createDirectories
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.exists
|
||||
import net.corda.core.internal.toX500Name
|
||||
import net.corda.coretesting.internal.configureTestSSL
|
||||
import net.corda.nodeapi.RPCApi
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_P2P_USER
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
|
||||
@ -16,8 +18,13 @@ import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import net.corda.nodeapi.internal.loadDevCaTrustStore
|
||||
import net.corda.coretesting.internal.stubs.CertificateStoreStubs
|
||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
|
||||
import net.corda.nodeapi.internal.registerDevP2pCertificates
|
||||
import net.corda.services.messaging.SimpleAMQPClient.Companion.sendAndVerify
|
||||
import net.corda.testing.core.BOB_NAME
|
||||
import net.corda.testing.core.CHARLIE_NAME
|
||||
import net.corda.testing.core.singleIdentity
|
||||
import net.corda.testing.internal.createDevIntermediateCaCertPath
|
||||
import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration
|
||||
import org.apache.activemq.artemis.api.core.ActiveMQClusterSecurityException
|
||||
import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException
|
||||
@ -29,6 +36,7 @@ import org.bouncycastle.asn1.x509.NameConstraints
|
||||
import org.junit.Test
|
||||
import java.nio.file.Files
|
||||
import javax.jms.JMSSecurityException
|
||||
import javax.security.auth.x500.X500Principal
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
/**
|
||||
@ -124,6 +132,51 @@ class MQSecurityAsNodeTest : P2PMQSecurityTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `login with invalid root`() {
|
||||
val legalName = CordaX500Name("MegaCorp", "London", "GB")
|
||||
val sslConfig = configureTestSSL(legalName)
|
||||
val (rootCa, intermediateCa) = createDevIntermediateCaCertPath(X500Principal("CN=Root2"))
|
||||
sslConfig.trustStore.get()[CORDA_ROOT_CA] = rootCa.certificate
|
||||
sslConfig.keyStore.get().registerDevP2pCertificates(legalName, rootCa.certificate, intermediateCa)
|
||||
|
||||
val attacker = clientTo(alice.node.configuration.p2pAddress, sslConfig)
|
||||
assertThatExceptionOfType(ActiveMQNotConnectedException::class.java).isThrownBy {
|
||||
attacker.start(PEER_USER, PEER_USER)
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `login with different roots`() {
|
||||
val (rootCa2, intermediateCa2) = createDevIntermediateCaCertPath(X500Principal("CN=Root2"))
|
||||
val (rootCa3, intermediateCa3) = createDevIntermediateCaCertPath(X500Principal("CN=Root3"))
|
||||
|
||||
val certificatesDirectory = baseDirectory(BOB_NAME).createDirectories() / "certificates"
|
||||
CertificateStoreStubs.P2P.TrustStore.withCertificatesDirectory(certificatesDirectory).get(true).let {
|
||||
it[CORDA_ROOT_CA] = DEV_ROOT_CA.certificate
|
||||
it["$CORDA_ROOT_CA-2"] = rootCa2.certificate
|
||||
}
|
||||
val bob = startNode(BOB_NAME)
|
||||
|
||||
// Login with different trusted root.
|
||||
configureTestSSL(CHARLIE_NAME).apply {
|
||||
trustStore.get()[CORDA_ROOT_CA] = rootCa2.certificate
|
||||
trustStore.get()["$CORDA_ROOT_CA-2"] = DEV_ROOT_CA.certificate
|
||||
keyStore.get().registerDevP2pCertificates(CHARLIE_NAME, rootCa2.certificate, intermediateCa2)
|
||||
clientTo(bob.node.configuration.p2pAddress, this).start(PEER_USER, PEER_USER)
|
||||
}
|
||||
|
||||
// Login with different non-trusted root.
|
||||
configureTestSSL(CHARLIE_NAME).apply {
|
||||
trustStore.get()[CORDA_ROOT_CA] = rootCa3.certificate
|
||||
trustStore.get()["$CORDA_ROOT_CA-2"] = DEV_ROOT_CA.certificate
|
||||
keyStore.get().registerDevP2pCertificates(CHARLIE_NAME, rootCa3.certificate, intermediateCa3)
|
||||
assertThatExceptionOfType(ActiveMQNotConnectedException::class.java).isThrownBy {
|
||||
clientTo(bob.node.configuration.p2pAddress, this).start(PEER_USER, PEER_USER)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun `send message to notifications address`() {
|
||||
assertProducerQueueCreationAttackFails(ArtemisMessagingComponent.NOTIFICATIONS_ADDRESS)
|
||||
}
|
||||
|
@ -437,9 +437,9 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
open fun generateAndSaveNodeInfo(): NodeInfo {
|
||||
check(started == null) { "Node has already been started" }
|
||||
log.info("Generating nodeInfo ...")
|
||||
val trustRoot = initKeyStores()
|
||||
val trustRoots = initKeyStores()
|
||||
startDatabase()
|
||||
identityService.start(trustRoot, keyStoreHandler.nodeIdentity, pkToIdCache = pkToIdCache)
|
||||
identityService.start(trustRoots, keyStoreHandler.nodeIdentity, pkToIdCache = pkToIdCache)
|
||||
return database.use {
|
||||
it.transaction {
|
||||
val nodeInfoAndSigned = updateNodeInfo(publish = false)
|
||||
@ -486,9 +486,9 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
logVendorString(database, log)
|
||||
if (allowHibernateToManageAppSchema) {
|
||||
Node.printBasicNodeInfo("Initialising CorDapps to get schemas created by hibernate")
|
||||
val trustRoot = initKeyStores()
|
||||
networkMapClient?.start(trustRoot)
|
||||
val (netParams, signedNetParams) = NetworkParametersReader(trustRoot, networkMapClient, configuration.networkParametersPath).read()
|
||||
val trustRoots = initKeyStores()
|
||||
networkMapClient?.start(trustRoots)
|
||||
val (netParams, signedNetParams) = NetworkParametersReader(trustRoots, networkMapClient, configuration.networkParametersPath).read()
|
||||
log.info("Loaded network parameters: $netParams")
|
||||
check(netParams.minimumPlatformVersion <= versionInfo.platformVersion) {
|
||||
"Node's platform version is lower than network's required minimumPlatformVersion"
|
||||
@ -496,7 +496,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
networkMapCache.start(netParams.notaries)
|
||||
|
||||
database.transaction {
|
||||
networkParametersStorage.setCurrentParameters(signedNetParams, trustRoot)
|
||||
networkParametersStorage.setCurrentParameters(signedNetParams, trustRoots)
|
||||
cordappProvider.start()
|
||||
}
|
||||
}
|
||||
@ -536,7 +536,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
nodeLifecycleEventsDistributor.distributeEvent(NodeLifecycleEvent.BeforeNodeStart(nodeServicesContext))
|
||||
log.info("Node starting up ...")
|
||||
|
||||
val trustRoot = initKeyStores()
|
||||
val trustRoots = initKeyStores()
|
||||
initialiseJolokia()
|
||||
|
||||
schemaService.mappedSchemasWarnings().forEach {
|
||||
@ -549,9 +549,9 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
registerCordappFlows()
|
||||
services.rpcFlows += cordappLoader.cordapps.flatMap { it.rpcFlows }
|
||||
startShell()
|
||||
networkMapClient?.start(trustRoot)
|
||||
networkMapClient?.start(trustRoots)
|
||||
|
||||
val networkParametersReader = NetworkParametersReader(trustRoot, networkMapClient, configuration.networkParametersPath)
|
||||
val networkParametersReader = NetworkParametersReader(trustRoots, networkMapClient, configuration.networkParametersPath)
|
||||
val (netParams, signedNetParams) = networkParametersReader.read()
|
||||
log.info("Loaded network parameters: $netParams")
|
||||
check(netParams.minimumPlatformVersion <= versionInfo.platformVersion) {
|
||||
@ -565,7 +565,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
schedulerService.closeOnStop()
|
||||
val rpcOps = makeRPCOps(cordappLoader)
|
||||
|
||||
identityService.start(trustRoot, keyStoreHandler.nodeIdentity, netParams.notaries.map { it.identity }, pkToIdCache)
|
||||
identityService.start(trustRoots, keyStoreHandler.nodeIdentity, netParams.notaries.map { it.identity }, pkToIdCache)
|
||||
|
||||
val nodeInfoAndSigned = database.transaction {
|
||||
updateNodeInfo(publish = true)
|
||||
@ -577,7 +577,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
val networkParametersHotloader = if (networkMapClient == null) {
|
||||
null
|
||||
} else {
|
||||
NetworkParametersHotloader(networkMapClient, trustRoot, netParams, networkParametersReader, networkParametersStorage).also {
|
||||
NetworkParametersHotloader(networkMapClient, trustRoots, netParams, networkParametersReader, networkParametersStorage).also {
|
||||
it.addNotaryUpdateListener(networkMapCache)
|
||||
it.addNotaryUpdateListener(identityService)
|
||||
it.addNetworkParametersChangedListeners(services)
|
||||
@ -586,7 +586,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
}
|
||||
|
||||
networkMapUpdater.start(
|
||||
trustRoot,
|
||||
trustRoots,
|
||||
signedNetParams.raw.hash,
|
||||
signedNodeInfo,
|
||||
netParams,
|
||||
@ -608,7 +608,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
|
||||
// Do all of this in a database transaction so anything that might need a connection has one.
|
||||
val (resultingNodeInfo, readyFuture) = database.transaction(recoverableFailureTolerance = 0) {
|
||||
networkParametersStorage.setCurrentParameters(signedNetParams, trustRoot)
|
||||
networkParametersStorage.setCurrentParameters(signedNetParams, trustRoots)
|
||||
identityService.loadIdentities(nodeInfo.legalIdentitiesAndCerts)
|
||||
attachments.start()
|
||||
cordappProvider.start()
|
||||
|
@ -33,7 +33,7 @@ class DBNetworkParametersStorage(
|
||||
// We could have historic parameters endpoint or always add parameters as an attachment to the transaction.
|
||||
private val networkMapClient: NetworkMapClient?
|
||||
) : NetworkParametersStorage, SingletonSerializeAsToken() {
|
||||
private lateinit var trustRoot: X509Certificate
|
||||
private lateinit var trustRoots: Set<X509Certificate>
|
||||
|
||||
companion object {
|
||||
private val log = contextLogger()
|
||||
@ -58,8 +58,8 @@ class DBNetworkParametersStorage(
|
||||
}
|
||||
}
|
||||
|
||||
override fun setCurrentParameters(currentSignedParameters: SignedDataWithCert<NetworkParameters>, trustRoot: X509Certificate) {
|
||||
this.trustRoot = trustRoot
|
||||
override fun setCurrentParameters(currentSignedParameters: SignedDataWithCert<NetworkParameters>, trustRoots: Set<X509Certificate>) {
|
||||
this.trustRoots = trustRoots
|
||||
saveParameters(currentSignedParameters)
|
||||
_currentHash = currentSignedParameters.raw.hash
|
||||
}
|
||||
@ -85,7 +85,7 @@ class DBNetworkParametersStorage(
|
||||
|
||||
override fun saveParameters(signedNetworkParameters: SignedNetworkParameters) {
|
||||
log.trace { "Saving new network parameters to network parameters storage." }
|
||||
val networkParameters = signedNetworkParameters.verifiedNetworkParametersCert(trustRoot)
|
||||
val networkParameters = signedNetworkParameters.verifiedNetworkParametersCert(trustRoots)
|
||||
val hash = signedNetworkParameters.raw.hash
|
||||
log.trace { "Parameters to save $networkParameters with hash $hash" }
|
||||
database.transaction {
|
||||
@ -97,7 +97,7 @@ class DBNetworkParametersStorage(
|
||||
return if (networkMapClient != null) {
|
||||
try {
|
||||
val signedParams = networkMapClient.getNetworkParameters(parametersHash)
|
||||
val networkParameters = signedParams.verifiedNetworkParametersCert(trustRoot)
|
||||
val networkParameters = signedParams.verifiedNetworkParametersCert(trustRoots)
|
||||
saveParameters(signedParams)
|
||||
networkParameters
|
||||
} catch (e: Exception) {
|
||||
|
@ -23,6 +23,7 @@ import java.nio.file.NoSuchFileException
|
||||
import java.security.GeneralSecurityException
|
||||
import java.security.PublicKey
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.Collections.unmodifiableSet
|
||||
|
||||
data class KeyAndAlias(val key: PublicKey, val alias: String)
|
||||
|
||||
@ -40,16 +41,16 @@ class KeyStoreHandler(private val configuration: NodeConfiguration, private val
|
||||
private val _signingKeys: MutableSet<KeyAndAlias> = mutableSetOf()
|
||||
val signingKeys: Set<KeyAndAlias> get() = _signingKeys.toSet()
|
||||
|
||||
private lateinit var trustRoot: X509Certificate
|
||||
private lateinit var trustRoots: Set<X509Certificate>
|
||||
|
||||
private lateinit var nodeKeyStore: CertificateStore
|
||||
|
||||
/**
|
||||
* Initialize key-stores and load identities.
|
||||
* @param devModeKeyEntropy entropy for legal identity key derivation
|
||||
* @return trust root certificate
|
||||
* @return trust root certificates
|
||||
*/
|
||||
fun init(devModeKeyEntropy: BigInteger? = null): X509Certificate {
|
||||
fun init(devModeKeyEntropy: BigInteger? = null): Set<X509Certificate> {
|
||||
if (configuration.devMode) {
|
||||
configuration.configureWithDevSSLCertificate(cryptoService, devModeKeyEntropy)
|
||||
// configureWithDevSSLCertificate is a devMode process that writes directly to keystore files, so
|
||||
@ -59,10 +60,10 @@ class KeyStoreHandler(private val configuration: NodeConfiguration, private val
|
||||
}
|
||||
}
|
||||
val certStores = getCertificateStores()
|
||||
trustRoot = validateKeyStores(certStores)
|
||||
trustRoots = validateKeyStores(certStores)
|
||||
nodeKeyStore = certStores.nodeKeyStore
|
||||
loadIdentities()
|
||||
return trustRoot
|
||||
return unmodifiableSet(trustRoots)
|
||||
}
|
||||
|
||||
private data class AllCertificateStores(val trustStore: CertificateStore,
|
||||
@ -88,12 +89,15 @@ class KeyStoreHandler(private val configuration: NodeConfiguration, private val
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateKeyStores(certStores: AllCertificateStores): X509Certificate {
|
||||
private fun validateKeyStores(certStores: AllCertificateStores): Set<X509Certificate> {
|
||||
// Check that trustStore contains the correct key-alias entry.
|
||||
require(X509Utilities.CORDA_ROOT_CA in certStores.trustStore) {
|
||||
val trustRoots = certStores.trustStore.filter { it.first.startsWith(X509Utilities.CORDA_ROOT_CA) }.map {
|
||||
log.info("Loaded trusted root certificate: ${it.second.publicKey.toStringShort()}, alias: ${it.first}")
|
||||
it.second
|
||||
}.toSet()
|
||||
require(trustRoots.isNotEmpty()) {
|
||||
"Alias for trustRoot key not found. Please ensure you have an updated trustStore file."
|
||||
}
|
||||
val trustRoot = certStores.trustStore[X509Utilities.CORDA_ROOT_CA]
|
||||
|
||||
certStores.sslKeyStore.let {
|
||||
val tlsKeyAlias = CORDA_CLIENT_TLS
|
||||
@ -112,10 +116,10 @@ class KeyStoreHandler(private val configuration: NodeConfiguration, private val
|
||||
|
||||
// Check TLS cert path chains to the trusted root.
|
||||
val sslCertChainRoot = it.query { getCertificateChain(tlsKeyAlias) }.last()
|
||||
require(sslCertChainRoot == trustRoot) { "TLS certificate must chain to the trusted root." }
|
||||
require(sslCertChainRoot in trustRoots) { "TLS certificate must chain to the trusted root." }
|
||||
}
|
||||
|
||||
return trustRoot
|
||||
return trustRoots
|
||||
}
|
||||
|
||||
/**
|
||||
@ -159,7 +163,7 @@ class KeyStoreHandler(private val configuration: NodeConfiguration, private val
|
||||
check(certificates.first() == certificate) {
|
||||
"Certificates from key store do not line up!"
|
||||
}
|
||||
check(certificates.last() == trustRoot) {
|
||||
check(certificates.last() in trustRoots) {
|
||||
"Certificate for node identity must chain to the trusted root."
|
||||
}
|
||||
|
||||
@ -169,7 +173,7 @@ class KeyStoreHandler(private val configuration: NodeConfiguration, private val
|
||||
}
|
||||
|
||||
val identity = PartyAndCertificate(X509Utilities.buildCertPath(certificates))
|
||||
X509Utilities.validateCertPath(trustRoot, identity.certPath)
|
||||
X509Utilities.validateCertPath(trustRoots, identity.certPath)
|
||||
return identity
|
||||
}
|
||||
|
||||
@ -224,7 +228,7 @@ class KeyStoreHandler(private val configuration: NodeConfiguration, private val
|
||||
val certificates: List<X509Certificate> = uncheckedCast(listOf(certificate) + baseIdentity.certPath.certificates.drop(1))
|
||||
|
||||
val identity = PartyAndCertificate(X509Utilities.buildCertPath(certificates))
|
||||
X509Utilities.validateCertPath(trustRoot, identity.certPath)
|
||||
X509Utilities.validateCertPath(trustRoots, identity.certPath)
|
||||
return identity
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@ import java.nio.file.Path
|
||||
import java.nio.file.StandardCopyOption
|
||||
import java.security.cert.X509Certificate
|
||||
|
||||
class NetworkParametersReader(private val trustRoot: X509Certificate,
|
||||
class NetworkParametersReader(private val trustRoots: Set<X509Certificate>,
|
||||
private val networkMapClient: NetworkMapClient?,
|
||||
private val networkParamsPath: Path) {
|
||||
companion object {
|
||||
@ -68,7 +68,7 @@ class NetworkParametersReader(private val trustRoot: X509Certificate,
|
||||
signedParametersFromFile ?: throw Error.ParamsNotConfigured()
|
||||
}
|
||||
|
||||
return NetworkParametersAndSigned(signedParameters, trustRoot)
|
||||
return NetworkParametersAndSigned(signedParameters, trustRoots)
|
||||
}
|
||||
|
||||
private fun readParametersUpdate(advertisedParametersHash: SecureHash, previousParametersHash: SecureHash): SignedNetworkParameters {
|
||||
@ -90,7 +90,7 @@ class NetworkParametersReader(private val trustRoot: X509Certificate,
|
||||
logger.info("No network-parameters file found. Expecting network parameters to be available from the network map.")
|
||||
networkMapClient ?: throw Error.NetworkMapNotConfigured()
|
||||
val signedParams = networkMapClient.getNetworkParameters(parametersHash)
|
||||
signedParams.verifiedNetworkParametersCert(trustRoot)
|
||||
signedParams.verifiedNetworkParametersCert(trustRoots)
|
||||
networkParamsFile.parent.toFile().mkdirs()
|
||||
signedParams.serialize().open().copyTo(networkParamsFile)
|
||||
logger.info("Saved network parameters into: $networkParamsFile")
|
||||
@ -99,10 +99,10 @@ class NetworkParametersReader(private val trustRoot: X509Certificate,
|
||||
|
||||
// By passing in just the SignedNetworkParameters object, this class guarantees that the networkParameters property
|
||||
// could have only been derived from it.
|
||||
class NetworkParametersAndSigned(val signed: SignedNetworkParameters, trustRoot: X509Certificate) {
|
||||
class NetworkParametersAndSigned(val signed: SignedNetworkParameters, trustRoots: Set<X509Certificate>) {
|
||||
// for backwards compatibility we allow netparams to be signed with the networkmap cert,
|
||||
// but going forwards we also accept the distinct netparams cert as well
|
||||
val networkParameters: NetworkParameters = signed.verifiedNetworkParametersCert(trustRoot)
|
||||
val networkParameters: NetworkParameters = signed.verifiedNetworkParametersCert(trustRoots)
|
||||
operator fun component1() = networkParameters
|
||||
operator fun component2() = signed
|
||||
}
|
||||
|
@ -41,12 +41,13 @@ sealed class CertificateChainCheckPolicy {
|
||||
|
||||
object RootMustMatch : CertificateChainCheckPolicy() {
|
||||
override fun createCheck(keyStore: KeyStore, trustStore: KeyStore): Check {
|
||||
val rootPublicKey = trustStore.getCertificate(X509Utilities.CORDA_ROOT_CA).publicKey
|
||||
val rootAliases = trustStore.aliases().asSequence().filter { it.startsWith(X509Utilities.CORDA_ROOT_CA) }
|
||||
val rootPublicKeys = rootAliases.map { trustStore.getCertificate(it).publicKey }.toSet()
|
||||
return object : Check {
|
||||
@Suppress("DEPRECATION") // should use java.security.cert.X509Certificate
|
||||
override fun checkCertificateChain(theirChain: Array<javax.security.cert.X509Certificate>) {
|
||||
val theirRoot = theirChain.last().publicKey
|
||||
if (rootPublicKey != theirRoot) {
|
||||
if (theirRoot !in rootPublicKeys) {
|
||||
throw CertificateException("Root certificate mismatch, their root = $theirRoot")
|
||||
}
|
||||
}
|
||||
|
@ -6,8 +6,11 @@ import net.corda.core.node.services.IdentityService
|
||||
import java.security.InvalidAlgorithmParameterException
|
||||
import java.security.cert.CertificateExpiredException
|
||||
import java.security.cert.CertificateNotYetValidException
|
||||
import java.security.cert.TrustAnchor
|
||||
|
||||
interface IdentityServiceInternal : IdentityService {
|
||||
val trustAnchors: Set<TrustAnchor>
|
||||
|
||||
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
|
||||
fun verifyAndRegisterNewRandomIdentity(identity: PartyAndCertificate)
|
||||
|
||||
|
@ -40,6 +40,7 @@ class InMemoryIdentityService(
|
||||
*/
|
||||
override val caCertStore: CertStore = CertStore.getInstance("Collection", CollectionCertStoreParameters(setOf(trustRoot)))
|
||||
override val trustAnchor: TrustAnchor = TrustAnchor(trustRoot, null)
|
||||
override val trustAnchors = setOf(trustAnchor)
|
||||
private val keyToExternalId = ConcurrentHashMap<String, UUID>()
|
||||
private val keyToPartyAndCerts = ConcurrentHashMap<PublicKey, PartyAndCertificate>()
|
||||
private val nameToKey = ConcurrentHashMap<CordaX500Name, PublicKey>()
|
||||
@ -55,22 +56,23 @@ class InMemoryIdentityService(
|
||||
|
||||
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
|
||||
override fun verifyAndRegisterIdentity(identity: PartyAndCertificate): PartyAndCertificate? {
|
||||
return verifyAndRegisterIdentity(trustAnchor, identity)
|
||||
return verifyAndRegisterIdentity(trustAnchors, identity)
|
||||
}
|
||||
|
||||
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
|
||||
override fun verifyAndRegisterNewRandomIdentity(identity: PartyAndCertificate) {
|
||||
verifyAndRegisterIdentity(trustAnchor, identity)
|
||||
verifyAndRegisterIdentity(trustAnchors, identity)
|
||||
}
|
||||
|
||||
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
|
||||
private fun verifyAndRegisterIdentity(trustAnchor: TrustAnchor, identity: PartyAndCertificate): PartyAndCertificate? {
|
||||
private fun verifyAndRegisterIdentity(trustAnchors: Set<TrustAnchor>, identity: PartyAndCertificate): PartyAndCertificate? {
|
||||
// Validate the chain first, before we do anything clever with it
|
||||
val identityCertChain = identity.certPath.x509Certificates
|
||||
try {
|
||||
identity.verify(trustAnchor)
|
||||
} catch (e: CertPathValidatorException) {
|
||||
log.warn("Certificate validation failed for ${identity.name} against trusted root ${trustAnchor.trustedCert.subjectX500Principal}.")
|
||||
val roots = trustAnchors.map { it.trustedCert.subjectX500Principal }
|
||||
log.warn("Certificate validation failed for ${identity.name} against trusted roots $roots.")
|
||||
log.warn("Certificate path :")
|
||||
identityCertChain.reversed().forEachIndexed { index, certificate ->
|
||||
val space = (0 until index).joinToString("") { " " }
|
||||
@ -83,7 +85,7 @@ class InMemoryIdentityService(
|
||||
if (wellKnownCert != identity.certificate) {
|
||||
val idx = identityCertChain.lastIndexOf(wellKnownCert)
|
||||
val firstPath = X509Utilities.buildCertPath(identityCertChain.slice(idx until identityCertChain.size))
|
||||
verifyAndRegisterIdentity(trustAnchor, PartyAndCertificate(firstPath))
|
||||
verifyAndRegisterIdentity(trustAnchors, PartyAndCertificate(firstPath))
|
||||
}
|
||||
return registerIdentity(identity, false)
|
||||
}
|
||||
|
@ -185,6 +185,9 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
|
||||
private lateinit var _trustAnchor: TrustAnchor
|
||||
override val trustAnchor: TrustAnchor get() = _trustAnchor
|
||||
|
||||
private lateinit var _trustAnchors: Set<TrustAnchor>
|
||||
override val trustAnchors: Set<TrustAnchor> get() = _trustAnchors
|
||||
|
||||
private lateinit var ourParty: Party
|
||||
|
||||
/** Stores notary identities obtained from the network parameters, for which we don't need to perform a database lookup. */
|
||||
@ -202,13 +205,14 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
|
||||
private val hashToKey = createHashToKeyMap(cacheFactory)
|
||||
|
||||
fun start(
|
||||
trustRoot: X509Certificate,
|
||||
trustRoots: Set<X509Certificate>,
|
||||
ourIdentity: PartyAndCertificate,
|
||||
notaryIdentities: List<Party> = emptyList(),
|
||||
pkToIdCache: WritablePublicKeyToOwningIdentityCache
|
||||
) {
|
||||
_trustRoot = trustRoot
|
||||
_trustRoot = ourIdentity.certPath.certificates.last() as X509Certificate
|
||||
_trustAnchor = TrustAnchor(trustRoot, null)
|
||||
_trustAnchors = trustRoots.map { TrustAnchor(it, null) }.toSet()
|
||||
// Extract Node CA certificate from node identity certificate path
|
||||
val certificates = setOf(ourIdentity.certificate, ourIdentity.certPath.certificates[1], trustRoot)
|
||||
_caCertStore = CertStore.getInstance("Collection", CollectionCertStoreParameters(certificates))
|
||||
@ -229,23 +233,24 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
|
||||
|
||||
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
|
||||
override fun verifyAndRegisterIdentity(identity: PartyAndCertificate): PartyAndCertificate? {
|
||||
return verifyAndRegisterIdentity(trustAnchor, identity)
|
||||
return verifyAndRegisterIdentity(trustAnchors, identity)
|
||||
}
|
||||
|
||||
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
|
||||
override fun verifyAndRegisterNewRandomIdentity(identity: PartyAndCertificate) {
|
||||
verifyAndRegisterIdentity(trustAnchor, identity, true)
|
||||
verifyAndRegisterIdentity(trustAnchors, identity, true)
|
||||
}
|
||||
|
||||
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
|
||||
private fun verifyAndRegisterIdentity(trustAnchor: TrustAnchor, identity: PartyAndCertificate, isNewRandomIdentity: Boolean = false):
|
||||
private fun verifyAndRegisterIdentity(trustAnchors: Set<TrustAnchor>, identity: PartyAndCertificate, isNewRandomIdentity: Boolean = false):
|
||||
PartyAndCertificate? {
|
||||
// Validate the chain first, before we do anything clever with it
|
||||
val identityCertChain = identity.certPath.x509Certificates
|
||||
try {
|
||||
identity.verify(trustAnchor)
|
||||
identity.verify(trustAnchors)
|
||||
} catch (e: CertPathValidatorException) {
|
||||
log.warn("Certificate validation failed for ${identity.name} against trusted root ${trustAnchor.trustedCert.subjectX500Principal}.")
|
||||
val roots = trustAnchors.map { it.trustedCert.subjectX500Principal }
|
||||
log.warn("Certificate validation failed for ${identity.name} against trusted roots $roots.")
|
||||
log.warn("Certificate path :")
|
||||
identityCertChain.reversed().forEachIndexed { index, certificate ->
|
||||
val space = (0 until index).joinToString("") { " " }
|
||||
@ -258,7 +263,7 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
|
||||
if (wellKnownCert != identity.certificate && !isNewRandomIdentity) {
|
||||
val idx = identityCertChain.lastIndexOf(wellKnownCert)
|
||||
val firstPath = X509Utilities.buildCertPath(identityCertChain.slice(idx until identityCertChain.size))
|
||||
verifyAndRegisterIdentity(trustAnchor, PartyAndCertificate(firstPath))
|
||||
verifyAndRegisterIdentity(trustAnchors, PartyAndCertificate(firstPath))
|
||||
}
|
||||
return registerIdentity(identity, isNewRandomIdentity)
|
||||
}
|
||||
|
@ -32,10 +32,10 @@ class NetworkMapClient(compatibilityZoneURL: URL, private val versionInfo: Versi
|
||||
}
|
||||
|
||||
private val networkMapUrl = URL("$compatibilityZoneURL/network-map")
|
||||
private lateinit var trustRoot: X509Certificate
|
||||
private lateinit var trustRoots: Set<X509Certificate>
|
||||
|
||||
fun start(trustRoot: X509Certificate) {
|
||||
this.trustRoot = trustRoot
|
||||
fun start(trustRoots: Set<X509Certificate>) {
|
||||
this.trustRoots = trustRoots
|
||||
}
|
||||
|
||||
fun publish(signedNodeInfo: SignedNodeInfo) {
|
||||
@ -61,7 +61,7 @@ class NetworkMapClient(compatibilityZoneURL: URL, private val versionInfo: Versi
|
||||
logger.trace { "Fetching network map update from $url." }
|
||||
val connection = url.openHttpConnection()
|
||||
val signedNetworkMap = connection.responseAs<SignedNetworkMap>()
|
||||
val networkMap = signedNetworkMap.verifiedNetworkMapCert(trustRoot)
|
||||
val networkMap = signedNetworkMap.verifiedNetworkMapCert(trustRoots)
|
||||
val timeout = connection.cacheControl.maxAgeSeconds().seconds
|
||||
val version = connection.cordaServerVersion
|
||||
logger.trace { "Fetched network map update from $url successfully: $networkMap" }
|
||||
@ -89,7 +89,7 @@ class NetworkMapClient(compatibilityZoneURL: URL, private val versionInfo: Versi
|
||||
logger.trace { "Fetching node infos from $url." }
|
||||
val verifiedNodeInfo = url.openHttpConnection().responseAs<Pair<SignedNetworkMap, List<SignedNodeInfo>>>()
|
||||
.also {
|
||||
val verifiedNodeInfoHashes = it.first.verifiedNetworkMapCert(trustRoot).nodeInfoHashes
|
||||
val verifiedNodeInfoHashes = it.first.verifiedNetworkMapCert(trustRoots).nodeInfoHashes
|
||||
val nodeInfoHashes = it.second.map { signedNodeInfo -> signedNodeInfo.verified().serialize().sha256() }
|
||||
require(
|
||||
verifiedNodeInfoHashes.containsAll(nodeInfoHashes) &&
|
||||
|
@ -80,7 +80,7 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
|
||||
private var newNetworkParameters: Pair<ParametersUpdate, SignedNetworkParameters>? = null
|
||||
private val fileWatcherSubscription = AtomicReference<Subscription?>()
|
||||
private var autoAcceptNetworkParameters: Boolean = true
|
||||
private lateinit var trustRoot: X509Certificate
|
||||
private lateinit var trustRoots: Set<X509Certificate>
|
||||
@Volatile
|
||||
private lateinit var currentParametersHash: SecureHash
|
||||
private lateinit var ourNodeInfo: SignedNodeInfo
|
||||
@ -103,7 +103,7 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
|
||||
MoreExecutors.shutdownAndAwaitTermination(networkMapPoller, 50, TimeUnit.SECONDS)
|
||||
}
|
||||
@Suppress("LongParameterList")
|
||||
fun start(trustRoot: X509Certificate,
|
||||
fun start(trustRoots: Set<X509Certificate>,
|
||||
currentParametersHash: SecureHash,
|
||||
ourNodeInfo: SignedNodeInfo,
|
||||
networkParameters: NetworkParameters,
|
||||
@ -113,7 +113,7 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
|
||||
) {
|
||||
fileWatcherSubscription.updateAndGet { subscription ->
|
||||
require(subscription == null) { "Should not call this method twice" }
|
||||
this.trustRoot = trustRoot
|
||||
this.trustRoots = trustRoots
|
||||
this.currentParametersHash = currentParametersHash
|
||||
this.ourNodeInfo = ourNodeInfo
|
||||
this.ourNodeInfoHash = ourNodeInfo.raw.hash
|
||||
@ -342,7 +342,7 @@ The node will shutdown now.""")
|
||||
return
|
||||
}
|
||||
val newSignedNetParams = networkMapClient.getNetworkParameters(update.newParametersHash)
|
||||
val newNetParams = newSignedNetParams.verifiedNetworkParametersCert(trustRoot)
|
||||
val newNetParams = newSignedNetParams.verifiedNetworkParametersCert(trustRoots)
|
||||
networkParametersStorage.saveParameters(newSignedNetParams)
|
||||
logger.info("Downloaded new network parameters: $newNetParams from the update: $update")
|
||||
newNetworkParameters = Pair(update, newSignedNetParams)
|
||||
@ -369,7 +369,7 @@ The node will shutdown now.""")
|
||||
// Add persisting of newest parameters from update.
|
||||
val (update, signedNewNetParams) = requireNotNull(newNetworkParameters) { "Couldn't find parameters update for the hash: $parametersHash" }
|
||||
// We should check that we sign the right data structure hash.
|
||||
val newNetParams = signedNewNetParams.verifiedNetworkParametersCert(trustRoot)
|
||||
val newNetParams = signedNewNetParams.verifiedNetworkParametersCert(trustRoots)
|
||||
val newParametersHash = signedNewNetParams.raw.hash
|
||||
if (parametersHash == newParametersHash) {
|
||||
// The latest parameters have priority.
|
||||
|
@ -16,7 +16,7 @@ import kotlin.reflect.jvm.javaGetter
|
||||
* Currently only hotloading notary changes are supported.
|
||||
*/
|
||||
class NetworkParametersHotloader(private val networkMapClient: NetworkMapClient,
|
||||
private val trustRoot: X509Certificate,
|
||||
private val trustRoots: Set<X509Certificate>,
|
||||
@Volatile private var networkParameters: NetworkParameters,
|
||||
private val networkParametersReader: NetworkParametersReader,
|
||||
private val networkParametersStorage: NetworkParametersStorage) {
|
||||
@ -42,7 +42,7 @@ class NetworkParametersHotloader(private val networkMapClient: NetworkMapClient,
|
||||
fun attemptHotload(newNetworkParameterHash: SecureHash): Boolean {
|
||||
|
||||
val newSignedNetParams = networkMapClient.getNetworkParameters(newNetworkParameterHash)
|
||||
val newNetParams = newSignedNetParams.verifiedNetworkParametersCert(trustRoot)
|
||||
val newNetParams = newSignedNetParams.verifiedNetworkParametersCert(trustRoots)
|
||||
|
||||
if (canHotload(newNetParams)) {
|
||||
logger.info("All changed parameters are hotloadable")
|
||||
@ -81,7 +81,7 @@ class NetworkParametersHotloader(private val networkMapClient: NetworkMapClient,
|
||||
|
||||
networkParameters = newNetworkParameters
|
||||
val networkParametersAndSigned = networkParametersReader.read()
|
||||
networkParametersStorage.setCurrentParameters(networkParametersAndSigned.signed, trustRoot)
|
||||
networkParametersStorage.setCurrentParameters(networkParametersAndSigned.signed, trustRoots)
|
||||
notifyListenersFor(newNetworkParameters)
|
||||
notifyListenersFor(newNetworkParameters.notaries)
|
||||
}
|
||||
|
@ -268,7 +268,7 @@ open class PersistentNetworkMapCache(cacheFactory: NamedCacheFactory,
|
||||
private fun verifyIdentities(node: NodeInfo): Boolean {
|
||||
for (identity in node.legalIdentitiesAndCerts) {
|
||||
try {
|
||||
identity.verify(identityService.trustAnchor)
|
||||
identity.verify(identityService.trustAnchors)
|
||||
} catch (e: CertPathValidatorException) {
|
||||
logger.warn("$node has invalid identity:\nError:$e\nIdentity:${identity.certPath}")
|
||||
return false
|
||||
|
@ -63,7 +63,7 @@ open class NetworkRegistrationHelper(
|
||||
private val certificateStore = config.certificateStore
|
||||
private val requestIdStore = certificatesDirectory / "certificate-request-id.txt"
|
||||
protected val rootTrustStore: X509KeyStore
|
||||
protected val rootCert: X509Certificate
|
||||
protected val rootCerts: Set<X509Certificate>
|
||||
private val notaryServiceConfig: NotaryServiceConfig? = config.notaryServiceConfig
|
||||
|
||||
init {
|
||||
@ -72,7 +72,8 @@ open class NetworkRegistrationHelper(
|
||||
"Please contact your CZ operator."
|
||||
}
|
||||
rootTrustStore = X509KeyStore.fromFile(networkRootTrustStorePath, networkRootTrustStorePassword)
|
||||
rootCert = rootTrustStore.getCertificate(CORDA_ROOT_CA)
|
||||
val rootAliases = rootTrustStore.aliases().asSequence().filter { it.startsWith(CORDA_ROOT_CA) }
|
||||
rootCerts = rootAliases.map { rootTrustStore.getCertificate(it) }.toSet()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -194,7 +195,7 @@ open class NetworkRegistrationHelper(
|
||||
logger.info("Generated Node Identity certificate: $nodeIdentityCert")
|
||||
|
||||
val nodeIdentityCertificateChain: List<X509Certificate> = listOf(nodeIdentityCert) + nodeCaCertChain
|
||||
X509Utilities.validateCertificateChain(rootCert, nodeIdentityCertificateChain)
|
||||
X509Utilities.validateCertificateChain(rootCerts, nodeIdentityCertificateChain)
|
||||
certStore.setCertPathOnly(nodeIdentityAlias, nodeIdentityCertificateChain)
|
||||
}
|
||||
logProgress("Node identity private key and certificate chain stored in $nodeIdentityAlias.")
|
||||
@ -252,7 +253,7 @@ open class NetworkRegistrationHelper(
|
||||
}
|
||||
|
||||
// Validate certificate chain returned from the doorman with the root cert obtained via out-of-band process, to prevent MITM attack on doorman server.
|
||||
X509Utilities.validateCertificateChain(rootCert, certificates)
|
||||
X509Utilities.validateCertificateChain(rootCerts, certificates)
|
||||
logProgress("Certificate signing request approved, storing private key with the certificate chain.")
|
||||
}
|
||||
|
||||
@ -423,7 +424,7 @@ class NodeRegistrationHelper(
|
||||
|
||||
override fun onSuccess(publicKey: PublicKey, contentSigner: ContentSigner, certificates: List<X509Certificate>, tlsCrlCertificateIssuer: X500Name?) {
|
||||
createSSLKeystore(publicKey, contentSigner, certificates, tlsCrlCertificateIssuer)
|
||||
createTruststore(certificates.last())
|
||||
createTruststore()
|
||||
}
|
||||
|
||||
private fun createSSLKeystore(nodeCaPublicKey: PublicKey, nodeCaContentSigner: ContentSigner, nodeCaCertificateChain: List<X509Certificate>, tlsCertCrlIssuer: X500Name?) {
|
||||
@ -449,23 +450,21 @@ class NodeRegistrationHelper(
|
||||
logger.info("Generated TLS certificate: $sslCert")
|
||||
|
||||
val sslCertificateChain: List<X509Certificate> = listOf(sslCert) + nodeCaCertificateChain
|
||||
X509Utilities.validateCertificateChain(rootCert, sslCertificateChain)
|
||||
X509Utilities.validateCertificateChain(rootCerts, sslCertificateChain)
|
||||
setPrivateKey(CORDA_CLIENT_TLS, sslKeyPair.private, sslCertificateChain, keyStore.entryPassword)
|
||||
}
|
||||
logProgress("SSL private key and certificate chain stored in ${keyStore.path}.")
|
||||
}
|
||||
|
||||
private fun createTruststore(rootCertificate: X509Certificate) {
|
||||
private fun createTruststore() {
|
||||
// Save root certificates to trust store.
|
||||
config.p2pSslOptions.trustStore.get(createNew = true).update {
|
||||
if (this.aliases().hasNext()) {
|
||||
logger.warn("The node's trust store already exists. The following certificates will be overridden: ${this.aliases().asSequence()}")
|
||||
}
|
||||
logProgress("Generating trust store for corda node.")
|
||||
// Assumes certificate chain always starts with client certificate and end with root certificate.
|
||||
setCertificate(CORDA_ROOT_CA, rootCertificate)
|
||||
// Copy remaining certificates from the network-trust-store
|
||||
rootTrustStore.aliases().asSequence().filter { it != CORDA_ROOT_CA }.forEach {
|
||||
// Copy all certificates from the network-trust-store.
|
||||
rootTrustStore.aliases().forEach {
|
||||
val certificate = rootTrustStore.getCertificate(it)
|
||||
logger.info("Copying trusted certificate to the node's trust store: Alias: $it, Certificate: $certificate")
|
||||
setCertificate(it, certificate)
|
||||
@ -477,8 +476,10 @@ class NodeRegistrationHelper(
|
||||
override fun validateAndGetTlsCrlIssuerCert(): X509Certificate? {
|
||||
val tlsCertCrlIssuer = config.tlsCertCrlIssuer
|
||||
tlsCertCrlIssuer ?: return null
|
||||
if (principalMatchesCertificatePrincipal(tlsCertCrlIssuer, rootCert)) {
|
||||
return rootCert
|
||||
for (rootCert in rootCerts) {
|
||||
if (principalMatchesCertificatePrincipal(tlsCertCrlIssuer, rootCert)) {
|
||||
return rootCert
|
||||
}
|
||||
}
|
||||
return findMatchingCertificate(tlsCertCrlIssuer, rootTrustStore)
|
||||
}
|
||||
|
@ -171,11 +171,20 @@ class KeyStoreHandlerTest {
|
||||
@Test(timeout = 300_000)
|
||||
fun `valid trust root is returned`() {
|
||||
val expectedRoot = config.p2pSslOptions.trustStore.get()[CORDA_ROOT_CA]
|
||||
val actualRoot = keyStoreHandler.init()
|
||||
val actualRoot = keyStoreHandler.init().first()
|
||||
|
||||
assertThat(actualRoot).isEqualTo(expectedRoot)
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `valid multiple trust roots are returned`() {
|
||||
val trustStore = config.p2pSslOptions.trustStore.get()
|
||||
trustStore["$CORDA_ROOT_CA-2"] = X509Utilities.createSelfSignedCACertificate(ALICE_NAME.x500Principal, Crypto.generateKeyPair())
|
||||
trustStore["non-root"] = X509Utilities.createSelfSignedCACertificate(BOB_NAME.x500Principal, Crypto.generateKeyPair())
|
||||
|
||||
assertThat(keyStoreHandler.init()).containsExactlyInAnyOrder(trustStore[CORDA_ROOT_CA], trustStore["$CORDA_ROOT_CA-2"])
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `keystore creation in dev mode`() {
|
||||
val devCertificateDir = tempFolder.root.toPath() / "certificates-dev"
|
||||
|
@ -14,6 +14,7 @@ import net.corda.coretesting.internal.DEV_INTERMEDIATE_CA
|
||||
import net.corda.coretesting.internal.DEV_ROOT_CA
|
||||
import net.corda.node.services.network.PersistentNetworkMapCache
|
||||
import net.corda.node.services.persistence.PublicKeyToOwningIdentityCacheImpl
|
||||
import net.corda.nodeapi.internal.createDevNodeIdentity
|
||||
import net.corda.nodeapi.internal.crypto.CertificateType
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import net.corda.nodeapi.internal.crypto.x509Certificates
|
||||
@ -21,18 +22,24 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.BOB_NAME
|
||||
import net.corda.testing.core.CHARLIE_NAME
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.core.SerializationEnvironmentRule
|
||||
import net.corda.testing.core.TestIdentity
|
||||
import net.corda.testing.core.getTestPartyAndCertificate
|
||||
import net.corda.testing.internal.TestingNamedCacheFactory
|
||||
import net.corda.testing.internal.configureDatabase
|
||||
import net.corda.testing.internal.createDevIntermediateCaCertPath
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.assertDoesNotThrow
|
||||
import java.security.cert.CertPathValidatorException
|
||||
import java.security.cert.X509CertSelector
|
||||
import javax.security.auth.x500.X500Principal
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertNull
|
||||
@ -70,7 +77,7 @@ class PersistentIdentityServiceTests {
|
||||
)
|
||||
identityService.database = database
|
||||
identityService.start(
|
||||
DEV_ROOT_CA.certificate,
|
||||
setOf(DEV_ROOT_CA.certificate),
|
||||
alice.identity,
|
||||
listOf(notary.party),
|
||||
PublicKeyToOwningIdentityCacheImpl(database, cacheFactory)
|
||||
@ -221,7 +228,11 @@ class PersistentIdentityServiceTests {
|
||||
// Create new identity service mounted onto same DB
|
||||
val newPersistentIdentityService = PersistentIdentityService(TestingNamedCacheFactory()).also {
|
||||
it.database = database
|
||||
it.start(DEV_ROOT_CA.certificate, Companion.alice.identity, pkToIdCache = PublicKeyToOwningIdentityCacheImpl(database, cacheFactory))
|
||||
it.start(
|
||||
setOf(DEV_ROOT_CA.certificate),
|
||||
Companion.alice.identity,
|
||||
pkToIdCache = PublicKeyToOwningIdentityCacheImpl(database, cacheFactory)
|
||||
)
|
||||
}
|
||||
|
||||
newPersistentIdentityService.assertOwnership(alice.party, anonymousAlice.party.anonymise())
|
||||
@ -350,4 +361,39 @@ class PersistentIdentityServiceTests {
|
||||
|
||||
assertEquals(setOf(alice2.party), identityService.partiesFromName("Alice Corp", true))
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `multiple trust roots`() {
|
||||
val (root2, doorman2) = createDevIntermediateCaCertPath(X500Principal("CN=Root2"))
|
||||
val node2 = createDevNodeIdentity(doorman2, BOB_NAME)
|
||||
val bob2 = PartyAndCertificate(X509Utilities.buildCertPath(node2.certificate, doorman2.certificate, root2.certificate))
|
||||
|
||||
val newIdentityService = PersistentIdentityService(cacheFactory)
|
||||
newIdentityService.database = database
|
||||
newIdentityService.start(
|
||||
setOf(DEV_ROOT_CA.certificate, root2.certificate),
|
||||
bob2,
|
||||
listOf(),
|
||||
PublicKeyToOwningIdentityCacheImpl(database, cacheFactory))
|
||||
|
||||
// Trust root should be taken from bobWithRoot2 identity.
|
||||
assertEquals(root2.certificate, newIdentityService.trustRoot)
|
||||
assertEquals(root2.certificate, newIdentityService.trustAnchor.trustedCert)
|
||||
assertEquals(listOf(DEV_ROOT_CA.certificate, root2.certificate), newIdentityService.trustAnchors.map { it.trustedCert })
|
||||
assertThat(newIdentityService.caCertStore.getCertificates(X509CertSelector())).contains(bob2.certificate, root2.certificate)
|
||||
|
||||
|
||||
// Verify identities with trusted root.
|
||||
newIdentityService.verifyAndRegisterIdentity(bob2)
|
||||
newIdentityService.verifyAndRegisterIdentity(ALICE_IDENTITY)
|
||||
|
||||
val (root3, doorman3) = createDevIntermediateCaCertPath(X500Principal("CN=Root3"))
|
||||
val node3 = createDevNodeIdentity(doorman3, CHARLIE_NAME)
|
||||
val charlie3 = PartyAndCertificate(X509Utilities.buildCertPath(node3.certificate, doorman3.certificate, root3.certificate))
|
||||
|
||||
// Fail to verify identities with untrusted root.
|
||||
assertFailsWith<CertPathValidatorException> {
|
||||
newIdentityService.verifyAndRegisterIdentity(charlie3)
|
||||
}
|
||||
}
|
||||
}
|
@ -61,7 +61,7 @@ class DBNetworkParametersStorageTest {
|
||||
networkMapClient = createMockNetworkMapClient()
|
||||
networkParametersService = DBNetworkParametersStorage(TestingNamedCacheFactory(), database, networkMapClient).apply {
|
||||
database.transaction {
|
||||
setCurrentParameters(netParams1, DEV_ROOT_CA.certificate)
|
||||
setCurrentParameters(netParams1, setOf(DEV_ROOT_CA.certificate))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ class NetworkMapClientTest {
|
||||
server = NetworkMapServer(cacheTimeout)
|
||||
val address = server.start()
|
||||
networkMapClient = NetworkMapClient(URL("http://$address"),
|
||||
VersionInfo(1, "TEST", "TEST", "TEST")).apply { start(DEV_ROOT_CA.certificate) }
|
||||
VersionInfo(1, "TEST", "TEST", "TEST")).apply { start(setOf(DEV_ROOT_CA.certificate)) }
|
||||
}
|
||||
|
||||
@After
|
||||
|
@ -113,7 +113,7 @@ class NetworkMapUpdaterTest {
|
||||
server = NetworkMapServer(cacheExpiryMs.millis)
|
||||
val address = server.start()
|
||||
networkMapClient = NetworkMapClient(URL("http://$address"),
|
||||
VersionInfo(1, "TEST", "TEST", "TEST")).apply { start(DEV_ROOT_CA.certificate) }
|
||||
VersionInfo(1, "TEST", "TEST", "TEST")).apply { start(setOf(DEV_ROOT_CA.certificate)) }
|
||||
}
|
||||
|
||||
@After
|
||||
@ -132,7 +132,7 @@ class NetworkMapUpdaterTest {
|
||||
autoAcceptNetworkParameters: Boolean = true,
|
||||
excludedAutoAcceptNetworkParameters: Set<String> = emptySet()) {
|
||||
|
||||
updater!!.start(DEV_ROOT_CA.certificate,
|
||||
updater!!.start(setOf(DEV_ROOT_CA.certificate),
|
||||
server.networkParameters.serialize().hash,
|
||||
ourNodeInfo,
|
||||
networkParameters,
|
||||
@ -363,7 +363,7 @@ class NetworkMapUpdaterTest {
|
||||
updater!!.acceptNewNetworkParameters(newHash) { it.serialize().sign(ourKeyPair) }
|
||||
verify(networkParametersStorage, times(1)).saveParameters(any())
|
||||
val signedNetworkParams = updateFile.readObject<SignedNetworkParameters>()
|
||||
val paramsFromFile = signedNetworkParams.verifiedNetworkParametersCert(DEV_ROOT_CA.certificate)
|
||||
val paramsFromFile = signedNetworkParams.verifiedNetworkParametersCert(setOf(DEV_ROOT_CA.certificate))
|
||||
assertEquals(newParameters, paramsFromFile)
|
||||
assertEquals(newHash, server.latestParametersAccepted(ourKeyPair.public))
|
||||
}
|
||||
@ -381,7 +381,7 @@ class NetworkMapUpdaterTest {
|
||||
val newHash = newParameters.serialize().hash
|
||||
val updateFile = baseDir / NETWORK_PARAMS_UPDATE_FILE_NAME
|
||||
val signedNetworkParams = updateFile.readObject<SignedNetworkParameters>()
|
||||
val paramsFromFile = signedNetworkParams.verifiedNetworkParametersCert(DEV_ROOT_CA.certificate)
|
||||
val paramsFromFile = signedNetworkParams.verifiedNetworkParametersCert(setOf(DEV_ROOT_CA.certificate))
|
||||
assertEquals(newParameters, paramsFromFile)
|
||||
assertEquals(newHash, server.latestParametersAccepted(ourKeyPair.public))
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ class NetworkParametersHotloaderTest {
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule(true)
|
||||
private val networkMapCertAndKeyPair: CertificateAndKeyPair = createDevNetworkMapCa()
|
||||
private val trustRoot = DEV_ROOT_CA.certificate
|
||||
private val trustRoots = setOf(DEV_ROOT_CA.certificate)
|
||||
|
||||
private val originalNetworkParameters = testNetworkParameters()
|
||||
private val notary: Party = TestIdentity.fresh("test notary").party
|
||||
@ -118,8 +118,9 @@ class NetworkParametersHotloaderTest {
|
||||
Mockito.`when`(networkMapClient.getNetworkParameters(newNetworkParameters.serialize().hash)).thenReturn(signedNetworkParameters)
|
||||
val networkParametersReader = Mockito.mock(NetworkParametersReader::class.java)
|
||||
Mockito.`when`(networkParametersReader.read())
|
||||
.thenReturn(NetworkParametersReader.NetworkParametersAndSigned(signedNetworkParameters, trustRoot))
|
||||
return NetworkParametersHotloader(networkMapClient, trustRoot, originalNetworkParameters, networkParametersReader, networkParametersStorage)
|
||||
.thenReturn(NetworkParametersReader.NetworkParametersAndSigned(signedNetworkParameters, trustRoots))
|
||||
return NetworkParametersHotloader(networkMapClient, trustRoots, originalNetworkParameters, networkParametersReader,
|
||||
networkParametersStorage)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,7 +54,7 @@ class NetworkParametersReaderTest {
|
||||
server = NetworkMapServer(cacheTimeout)
|
||||
val address = server.start()
|
||||
networkMapClient = NetworkMapClient(URL("http://$address"), VersionInfo(1, "TEST", "TEST", "TEST"))
|
||||
networkMapClient.start(DEV_ROOT_CA.certificate)
|
||||
networkMapClient.start(setOf(DEV_ROOT_CA.certificate))
|
||||
}
|
||||
|
||||
@After
|
||||
@ -69,13 +69,13 @@ class NetworkParametersReaderTest {
|
||||
val oldParameters = testNetworkParameters(epoch = 1)
|
||||
NetworkParametersCopier(oldParameters).install(networkParamsPath)
|
||||
NetworkParametersCopier(server.networkParameters, update = true).install(networkParamsPath) // Parameters update file.
|
||||
val parameters = NetworkParametersReader(DEV_ROOT_CA.certificate, networkMapClient, networkParamsPath).read().networkParameters
|
||||
val parameters = NetworkParametersReader(setOf(DEV_ROOT_CA.certificate), networkMapClient, networkParamsPath).read().networkParameters
|
||||
assertFalse((networkParamsPath / NETWORK_PARAMS_UPDATE_FILE_NAME).exists())
|
||||
assertEquals(server.networkParameters, parameters)
|
||||
// Parameters from update should be moved to `network-parameters` file.
|
||||
val parametersFromFile = (networkParamsPath / NETWORK_PARAMS_FILE_NAME)
|
||||
.readObject<SignedNetworkParameters>()
|
||||
.verifiedNetworkParametersCert(DEV_ROOT_CA.certificate)
|
||||
.verifiedNetworkParametersCert(setOf(DEV_ROOT_CA.certificate))
|
||||
assertEquals(server.networkParameters, parametersFromFile)
|
||||
}
|
||||
|
||||
@ -85,13 +85,13 @@ class NetworkParametersReaderTest {
|
||||
val oldParameters = testNetworkParameters(epoch = 1)
|
||||
NetworkParametersCopier(oldParameters).install(networkParamsPath)
|
||||
NetworkParametersCopier(server.networkParameters, update = true).install(networkParamsPath) // Parameters update file.
|
||||
val parameters = NetworkParametersReader(DEV_ROOT_CA.certificate, networkMapClient, networkParamsPath).read().networkParameters
|
||||
val parameters = NetworkParametersReader(setOf(DEV_ROOT_CA.certificate), networkMapClient, networkParamsPath).read().networkParameters
|
||||
assertFalse((networkParamsPath / NETWORK_PARAMS_UPDATE_FILE_NAME).exists())
|
||||
assertEquals(server.networkParameters, parameters)
|
||||
// Parameters from update should be moved to `network-parameters` file.
|
||||
val parametersFromFile = (networkParamsPath / NETWORK_PARAMS_FILE_NAME)
|
||||
.readObject<SignedNetworkParameters>()
|
||||
.verifiedNetworkParametersCert(DEV_ROOT_CA.certificate)
|
||||
.verifiedNetworkParametersCert(setOf(DEV_ROOT_CA.certificate))
|
||||
assertEquals(server.networkParameters, parametersFromFile)
|
||||
}
|
||||
|
||||
@ -101,7 +101,7 @@ class NetworkParametersReaderTest {
|
||||
val networkParamsPath = fs.getPath("/node").createDirectories()
|
||||
val fileParameters = testNetworkParameters(epoch = 1)
|
||||
NetworkParametersCopier(fileParameters).install(networkParamsPath)
|
||||
val parameters = NetworkParametersReader(DEV_ROOT_CA.certificate, networkMapClient, networkParamsPath).read().networkParameters
|
||||
val parameters = NetworkParametersReader(setOf(DEV_ROOT_CA.certificate), networkMapClient, networkParamsPath).read().networkParameters
|
||||
assertThat(parameters).isEqualTo(fileParameters)
|
||||
}
|
||||
|
||||
@ -120,17 +120,17 @@ class NetworkParametersReaderTest {
|
||||
val netParameters = testNetworkParameters(epoch = 1)
|
||||
val certKeyPairNetworkParameters: CertificateAndKeyPair = createDevNetworkParametersCa()
|
||||
val netParamsForNetworkParameters= certKeyPairNetworkParameters.sign(netParameters)
|
||||
netParamsForNetworkParameters.verifiedNetworkParametersCert(DEV_ROOT_CA.certificate)
|
||||
netParamsForNetworkParameters.verifiedNetworkParametersCert(setOf(DEV_ROOT_CA.certificate))
|
||||
|
||||
val certKeyPairNetworkMap: CertificateAndKeyPair = createDevNetworkMapCa()
|
||||
val netParamsForNetworkMap = certKeyPairNetworkMap.sign(netParameters)
|
||||
netParamsForNetworkMap.verifiedNetworkParametersCert(DEV_ROOT_CA.certificate)
|
||||
netParamsForNetworkMap.verifiedNetworkParametersCert(setOf(DEV_ROOT_CA.certificate))
|
||||
|
||||
val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
|
||||
val x = createDevNodeCa(DEV_INTERMEDIATE_CA, megaCorp.name)
|
||||
val netParamsForNode = x.sign(netParameters)
|
||||
assertFailsWith(IllegalArgumentException::class, "Incorrect cert role: NODE_CA") {
|
||||
netParamsForNode.verifiedNetworkParametersCert(DEV_ROOT_CA.certificate)
|
||||
netParamsForNode.verifiedNetworkParametersCert(setOf(DEV_ROOT_CA.certificate))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -188,7 +188,7 @@ open class MockServices private constructor(
|
||||
// Create a persistent identity service and add all the supplied identities.
|
||||
identityService.apply {
|
||||
database = persistence
|
||||
start(DEV_ROOT_CA.certificate, initialIdentity.identity, pkToIdCache = pkToIdCache)
|
||||
start(setOf(DEV_ROOT_CA.certificate), initialIdentity.identity, pkToIdCache = pkToIdCache)
|
||||
persistence.transaction { identityService.loadIdentities(moreIdentities + initialIdentity.identity) }
|
||||
}
|
||||
val networkMapCache = PersistentNetworkMapCache(cacheFactory, persistence, identityService)
|
||||
|
@ -29,8 +29,8 @@ class MockNetworkParametersStorage(private var currentParameters: NetworkParamet
|
||||
storeCurrentParameters()
|
||||
}
|
||||
|
||||
override fun setCurrentParameters(currentSignedParameters: SignedDataWithCert<NetworkParameters>, trustRoot: X509Certificate) {
|
||||
setCurrentParametersUnverified(currentSignedParameters.verifiedNetworkParametersCert(trustRoot))
|
||||
override fun setCurrentParameters(currentSignedParameters: SignedDataWithCert<NetworkParameters>, trustRoots: Set<X509Certificate>) {
|
||||
setCurrentParametersUnverified(currentSignedParameters.verifiedNetworkParametersCert(trustRoots))
|
||||
}
|
||||
|
||||
override fun lookupSigned(hash: SecureHash): SignedDataWithCert<NetworkParameters>? {
|
||||
|
Loading…
Reference in New Issue
Block a user