mirror of
https://github.com/corda/corda.git
synced 2024-12-20 05:28:21 +00:00
ENT-1323 Network map service to check all identities in submitted node info (#499)
* ENT-1323 Network map service to check all identities in submitted node info * fixup after rebase * address PR issues, refactored createValidNodeInfo * address PR issues
This commit is contained in:
parent
241f7738e4
commit
f9ed55b731
@ -34,10 +34,11 @@ import java.security.cert.CertPath
|
|||||||
class PersistentNodeInfoStorage(private val database: CordaPersistence) : NodeInfoStorage {
|
class PersistentNodeInfoStorage(private val database: CordaPersistence) : NodeInfoStorage {
|
||||||
override fun putNodeInfo(nodeInfoAndSigned: NodeInfoAndSigned): SecureHash {
|
override fun putNodeInfo(nodeInfoAndSigned: NodeInfoAndSigned): SecureHash {
|
||||||
val (nodeInfo, signedNodeInfo) = nodeInfoAndSigned
|
val (nodeInfo, signedNodeInfo) = nodeInfoAndSigned
|
||||||
val nodeCaCert = nodeInfo.legalIdentitiesAndCerts[0].certPath.x509Certificates.find { CertRole.extract(it) == NODE_CA }
|
|
||||||
nodeCaCert ?: throw IllegalArgumentException("Missing Node CA")
|
|
||||||
val nodeInfoHash = signedNodeInfo.raw.hash
|
val nodeInfoHash = signedNodeInfo.raw.hash
|
||||||
|
|
||||||
|
// Extract identities issued by the intermediate CAs (doorman).
|
||||||
|
val registeredIdentities = nodeInfo.legalIdentitiesAndCerts.map { it.certPath.x509Certificates.single { CertRole.extract(it) in setOf(CertRole.SERVICE_IDENTITY, NODE_CA) } }
|
||||||
|
|
||||||
database.transaction {
|
database.transaction {
|
||||||
val count = session.createQuery(
|
val count = session.createQuery(
|
||||||
"select count(*) from ${NodeInfoEntity::class.java.name} where nodeInfoHash = :nodeInfoHash and isCurrent = true", java.lang.Long::class.java)
|
"select count(*) from ${NodeInfoEntity::class.java.name} where nodeInfoHash = :nodeInfoHash and isCurrent = true", java.lang.Long::class.java)
|
||||||
@ -50,13 +51,21 @@ class PersistentNodeInfoStorage(private val database: CordaPersistence) : NodeIn
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO Move these checks out of data access layer
|
// TODO Move these checks out of data access layer
|
||||||
val request = requireNotNull(getSignedRequestByPublicHash(nodeCaCert.publicKey.encoded.sha256())) {
|
// For each identity known by the doorman, validate against it's CSR.
|
||||||
"Node-info not registered with us"
|
val requests = registeredIdentities.map {
|
||||||
}
|
val request = requireNotNull(getSignedRequestByPublicHash(it.publicKey.hash)) {
|
||||||
request.certificateData?.certificateStatus.let {
|
"Node-info not registered with us"
|
||||||
require(it == CertificateStatus.VALID) { "Certificate is no longer valid: $it" }
|
}
|
||||||
|
request.certificateData?.certificateStatus.let {
|
||||||
|
require(it == CertificateStatus.VALID) { "Certificate is no longer valid: $it" }
|
||||||
|
}
|
||||||
|
CertRole.extract(it) to request
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure only 1 NodeCA identity.
|
||||||
|
// TODO: make this support multiple node identities.
|
||||||
|
val (_, request) = requireNotNull(requests.singleOrNull { it.first == CertRole.NODE_CA }) { "Require exactly 1 Node CA identity in the node-info." }
|
||||||
|
|
||||||
val existingNodeInfos = session.fromQuery<NodeInfoEntity>(
|
val existingNodeInfos = session.fromQuery<NodeInfoEntity>(
|
||||||
"n where n.certificateSigningRequest = :csr and n.isCurrent = true order by n.publishedAt desc")
|
"n where n.certificateSigningRequest = :csr and n.isCurrent = true order by n.publishedAt desc")
|
||||||
.setParameter("csr", request)
|
.setParameter("csr", request)
|
||||||
|
@ -13,10 +13,10 @@ package com.r3.corda.networkmanage.common.persistence
|
|||||||
import com.r3.corda.networkmanage.TestBase
|
import com.r3.corda.networkmanage.TestBase
|
||||||
import com.r3.corda.networkmanage.common.persistence.entity.NodeInfoEntity
|
import com.r3.corda.networkmanage.common.persistence.entity.NodeInfoEntity
|
||||||
import net.corda.core.crypto.Crypto
|
import net.corda.core.crypto.Crypto
|
||||||
import net.corda.core.crypto.sha256
|
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.internal.CertRole
|
import net.corda.core.internal.CertRole
|
||||||
import net.corda.core.internal.hash
|
import net.corda.core.internal.hash
|
||||||
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.core.utilities.days
|
import net.corda.core.utilities.days
|
||||||
import net.corda.nodeapi.internal.NodeInfoAndSigned
|
import net.corda.nodeapi.internal.NodeInfoAndSigned
|
||||||
@ -31,6 +31,7 @@ import net.corda.testing.internal.createDevIntermediateCaCertPath
|
|||||||
import net.corda.testing.internal.signWith
|
import net.corda.testing.internal.signWith
|
||||||
import net.corda.testing.node.MockServices
|
import net.corda.testing.node.MockServices
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@ -216,17 +217,66 @@ class PersistentNodeInfoStorageTest : TestBase() {
|
|||||||
val acceptedUpdate = nodeInfoStorage.getAcceptedParametersUpdate(nodeInfoHash2)
|
val acceptedUpdate = nodeInfoStorage.getAcceptedParametersUpdate(nodeInfoHash2)
|
||||||
assertThat(acceptedUpdate?.networkParameters?.hash).isEqualTo(netParamsHash.toString())
|
assertThat(acceptedUpdate?.networkParameters?.hash).isEqualTo(netParamsHash.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `persist node info with multiple node CA identities`() {
|
||||||
|
val (nodeInfo1, nodeKeyPair1) = createValidNodeInfo("Alice", requestStorage)
|
||||||
|
val (nodeInfo2, nodeKeyPair2) = createValidNodeInfo("Bob", requestStorage)
|
||||||
|
|
||||||
|
val multiIdentityNodeInfo = nodeInfo1.copy(legalIdentitiesAndCerts = nodeInfo1.legalIdentitiesAndCerts + nodeInfo2.legalIdentitiesAndCerts)
|
||||||
|
val signedNodeInfo = multiIdentityNodeInfo.signWith(listOf(nodeKeyPair1, nodeKeyPair2))
|
||||||
|
|
||||||
|
assertThatThrownBy { nodeInfoStorage.putNodeInfo(NodeInfoAndSigned(signedNodeInfo)) }
|
||||||
|
.isInstanceOf(IllegalArgumentException::class.java)
|
||||||
|
.hasMessageContaining("Require exactly 1 Node CA identity in the node-info.")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `persist node info with service identity`() {
|
||||||
|
val (nodeInfo, nodeKeyPairs) = createValidNodeInfo(requestStorage, CertRole.NODE_CA to "Alice", CertRole.SERVICE_IDENTITY to "Alice Notary")
|
||||||
|
val signedNodeInfo = nodeInfo.signWith(nodeKeyPairs)
|
||||||
|
nodeInfoStorage.putNodeInfo(NodeInfoAndSigned(signedNodeInfo))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `persist node info with unregistered service identity`() {
|
||||||
|
val (nodeInfo1, nodeKeyPair1) = createValidNodeInfo("Alice", requestStorage)
|
||||||
|
// Create a unregistered cert path with valid intermediate cert.
|
||||||
|
val (identity, key) = TestNodeInfoBuilder().addServiceIdentity(CordaX500Name("Test", "London", "GB"), Crypto.generateKeyPair())
|
||||||
|
|
||||||
|
val multiIdentityNodeInfo = nodeInfo1.copy(legalIdentitiesAndCerts = nodeInfo1.legalIdentitiesAndCerts + identity)
|
||||||
|
val signedNodeInfo = multiIdentityNodeInfo.signWith(listOf(nodeKeyPair1, key))
|
||||||
|
|
||||||
|
assertThatThrownBy { nodeInfoStorage.putNodeInfo(NodeInfoAndSigned(signedNodeInfo)) }
|
||||||
|
.isInstanceOf(IllegalArgumentException::class.java)
|
||||||
|
.hasMessageContaining("Node-info not registered with us")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun createValidSignedNodeInfo(organisation: String,
|
private fun createValidNodeInfo(organisation: String, storage: CertificateSigningRequestStorage): Pair<NodeInfo, PrivateKey> {
|
||||||
storage: CertificateSigningRequestStorage): Pair<NodeInfoAndSigned, PrivateKey> {
|
val (nodeInfo, keys) = createValidNodeInfo(storage, CertRole.NODE_CA to organisation)
|
||||||
val (csr, nodeKeyPair) = createRequest(organisation, certRole = CertRole.NODE_CA)
|
return Pair(nodeInfo, keys.single())
|
||||||
val requestId = storage.saveRequest(csr)
|
}
|
||||||
storage.markRequestTicketCreated(requestId)
|
|
||||||
storage.approveRequest(requestId, "TestUser")
|
private fun createValidNodeInfo(storage: CertificateSigningRequestStorage, vararg identities: Pair<CertRole, String>): Pair<NodeInfo, List<PrivateKey>> {
|
||||||
val nodeInfoBuilder = TestNodeInfoBuilder()
|
val nodeInfoBuilder = TestNodeInfoBuilder()
|
||||||
val (identity, key) = nodeInfoBuilder.addIdentity(CordaX500Name.build(X500Principal(csr.subject.encoded)), nodeKeyPair)
|
val keys = identities.map { (certRole, name) ->
|
||||||
storage.putCertificatePath(requestId, identity.certPath, "Test")
|
val (csr, nodeKeyPair) = createRequest(name, certRole = certRole)
|
||||||
val (_, signedNodeInfo) = nodeInfoBuilder.buildWithSigned(1)
|
val requestId = storage.saveRequest(csr)
|
||||||
return Pair(NodeInfoAndSigned(signedNodeInfo), key)
|
storage.markRequestTicketCreated(requestId)
|
||||||
}
|
storage.approveRequest(requestId, "TestUser")
|
||||||
|
val (identity, key) = when (certRole) {
|
||||||
|
CertRole.NODE_CA -> nodeInfoBuilder.addLegalIdentity(CordaX500Name.build(X500Principal(csr.subject.encoded)), nodeKeyPair)
|
||||||
|
CertRole.SERVICE_IDENTITY -> nodeInfoBuilder.addServiceIdentity(CordaX500Name.build(X500Principal(csr.subject.encoded)), nodeKeyPair)
|
||||||
|
else -> throw IllegalArgumentException("Unsupported cert role $certRole.")
|
||||||
|
}
|
||||||
|
storage.putCertificatePath(requestId, identity.certPath, "Test")
|
||||||
|
key
|
||||||
|
}
|
||||||
|
return Pair(nodeInfoBuilder.build(), keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun createValidSignedNodeInfo(organisation: String, storage: CertificateSigningRequestStorage): Pair<NodeInfoAndSigned, PrivateKey> {
|
||||||
|
val (nodeInfo, key) = createValidNodeInfo(organisation, storage)
|
||||||
|
return Pair(NodeInfoAndSigned(nodeInfo.signWith(listOf(key))), key)
|
||||||
|
}
|
||||||
|
@ -41,23 +41,23 @@ class SignedNodeInfoTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `verifying single identity`() {
|
fun `verifying single identity`() {
|
||||||
nodeInfoBuilder.addIdentity(ALICE_NAME)
|
nodeInfoBuilder.addLegalIdentity(ALICE_NAME)
|
||||||
val (nodeInfo, signedNodeInfo) = nodeInfoBuilder.buildWithSigned()
|
val (nodeInfo, signedNodeInfo) = nodeInfoBuilder.buildWithSigned()
|
||||||
assertThat(signedNodeInfo.verified()).isEqualTo(nodeInfo)
|
assertThat(signedNodeInfo.verified()).isEqualTo(nodeInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `verifying multiple identities`() {
|
fun `verifying multiple identities`() {
|
||||||
nodeInfoBuilder.addIdentity(ALICE_NAME)
|
nodeInfoBuilder.addLegalIdentity(ALICE_NAME)
|
||||||
nodeInfoBuilder.addIdentity(BOB_NAME)
|
nodeInfoBuilder.addLegalIdentity(BOB_NAME)
|
||||||
val (nodeInfo, signedNodeInfo) = nodeInfoBuilder.buildWithSigned()
|
val (nodeInfo, signedNodeInfo) = nodeInfoBuilder.buildWithSigned()
|
||||||
assertThat(signedNodeInfo.verified()).isEqualTo(nodeInfo)
|
assertThat(signedNodeInfo.verified()).isEqualTo(nodeInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `verifying missing signature`() {
|
fun `verifying missing signature`() {
|
||||||
val (_, aliceKey) = nodeInfoBuilder.addIdentity(ALICE_NAME)
|
val (_, aliceKey) = nodeInfoBuilder.addLegalIdentity(ALICE_NAME)
|
||||||
nodeInfoBuilder.addIdentity(BOB_NAME)
|
nodeInfoBuilder.addLegalIdentity(BOB_NAME)
|
||||||
val nodeInfo = nodeInfoBuilder.build()
|
val nodeInfo = nodeInfoBuilder.build()
|
||||||
val signedNodeInfo = nodeInfo.signWith(listOf(aliceKey))
|
val signedNodeInfo = nodeInfo.signWith(listOf(aliceKey))
|
||||||
assertThatThrownBy { signedNodeInfo.verified() }
|
assertThatThrownBy { signedNodeInfo.verified() }
|
||||||
@ -80,7 +80,7 @@ class SignedNodeInfoTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `verifying extra signature`() {
|
fun `verifying extra signature`() {
|
||||||
val (_, aliceKey) = nodeInfoBuilder.addIdentity(ALICE_NAME)
|
val (_, aliceKey) = nodeInfoBuilder.addLegalIdentity(ALICE_NAME)
|
||||||
val nodeInfo = nodeInfoBuilder.build()
|
val nodeInfo = nodeInfoBuilder.build()
|
||||||
val signedNodeInfo = nodeInfo.signWith(listOf(aliceKey, generateKeyPair().private))
|
val signedNodeInfo = nodeInfo.signWith(listOf(aliceKey, generateKeyPair().private))
|
||||||
assertThatThrownBy { signedNodeInfo.verified() }
|
assertThatThrownBy { signedNodeInfo.verified() }
|
||||||
@ -90,7 +90,7 @@ class SignedNodeInfoTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `verifying incorrect signature`() {
|
fun `verifying incorrect signature`() {
|
||||||
nodeInfoBuilder.addIdentity(ALICE_NAME)
|
nodeInfoBuilder.addLegalIdentity(ALICE_NAME)
|
||||||
val nodeInfo = nodeInfoBuilder.build()
|
val nodeInfo = nodeInfoBuilder.build()
|
||||||
val signedNodeInfo = nodeInfo.signWith(listOf(generateKeyPair().private))
|
val signedNodeInfo = nodeInfo.signWith(listOf(generateKeyPair().private))
|
||||||
assertThatThrownBy { signedNodeInfo.verified() }
|
assertThatThrownBy { signedNodeInfo.verified() }
|
||||||
@ -100,8 +100,8 @@ class SignedNodeInfoTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `verifying with signatures in wrong order`() {
|
fun `verifying with signatures in wrong order`() {
|
||||||
val (_, aliceKey) = nodeInfoBuilder.addIdentity(ALICE_NAME)
|
val (_, aliceKey) = nodeInfoBuilder.addLegalIdentity(ALICE_NAME)
|
||||||
val (_, bobKey) = nodeInfoBuilder.addIdentity(BOB_NAME)
|
val (_, bobKey) = nodeInfoBuilder.addLegalIdentity(BOB_NAME)
|
||||||
val nodeInfo = nodeInfoBuilder.build()
|
val nodeInfo = nodeInfoBuilder.build()
|
||||||
val signedNodeInfo = nodeInfo.signWith(listOf(bobKey, aliceKey))
|
val signedNodeInfo = nodeInfo.signWith(listOf(bobKey, aliceKey))
|
||||||
assertThatThrownBy { signedNodeInfo.verified() }
|
assertThatThrownBy { signedNodeInfo.verified() }
|
||||||
|
@ -83,8 +83,8 @@ class NetworkMapClientTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `errors return a meaningful error message`() {
|
fun `errors return a meaningful error message`() {
|
||||||
val nodeInfoBuilder = TestNodeInfoBuilder()
|
val nodeInfoBuilder = TestNodeInfoBuilder()
|
||||||
val (_, aliceKey) = nodeInfoBuilder.addIdentity(ALICE_NAME)
|
val (_, aliceKey) = nodeInfoBuilder.addLegalIdentity(ALICE_NAME)
|
||||||
nodeInfoBuilder.addIdentity(BOB_NAME)
|
nodeInfoBuilder.addLegalIdentity(BOB_NAME)
|
||||||
val nodeInfo3 = nodeInfoBuilder.build()
|
val nodeInfo3 = nodeInfoBuilder.build()
|
||||||
val signedNodeInfo3 = nodeInfo3.signWith(listOf(aliceKey))
|
val signedNodeInfo3 = nodeInfo3.signWith(listOf(aliceKey))
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ import java.security.cert.X509Certificate
|
|||||||
class TestNodeInfoBuilder(private val intermediateAndRoot: Pair<CertificateAndKeyPair, X509Certificate> = DEV_INTERMEDIATE_CA to DEV_ROOT_CA.certificate) {
|
class TestNodeInfoBuilder(private val intermediateAndRoot: Pair<CertificateAndKeyPair, X509Certificate> = DEV_INTERMEDIATE_CA to DEV_ROOT_CA.certificate) {
|
||||||
private val identitiesAndPrivateKeys = ArrayList<Pair<PartyAndCertificate, PrivateKey>>()
|
private val identitiesAndPrivateKeys = ArrayList<Pair<PartyAndCertificate, PrivateKey>>()
|
||||||
|
|
||||||
fun addIdentity(name: CordaX500Name, nodeKeyPair: KeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)): Pair<PartyAndCertificate, PrivateKey> {
|
fun addLegalIdentity(name: CordaX500Name, nodeKeyPair: KeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)): Pair<PartyAndCertificate, PrivateKey> {
|
||||||
val nodeCertificateAndKeyPair = createDevNodeCa(intermediateAndRoot.first, name, nodeKeyPair)
|
val nodeCertificateAndKeyPair = createDevNodeCa(intermediateAndRoot.first, name, nodeKeyPair)
|
||||||
val identityKeyPair = Crypto.generateKeyPair()
|
val identityKeyPair = Crypto.generateKeyPair()
|
||||||
val identityCert = X509Utilities.createCertificate(
|
val identityCert = X509Utilities.createCertificate(
|
||||||
@ -39,12 +39,35 @@ class TestNodeInfoBuilder(private val intermediateAndRoot: Pair<CertificateAndKe
|
|||||||
nodeCertificateAndKeyPair.keyPair,
|
nodeCertificateAndKeyPair.keyPair,
|
||||||
nodeCertificateAndKeyPair.certificate.subjectX500Principal,
|
nodeCertificateAndKeyPair.certificate.subjectX500Principal,
|
||||||
identityKeyPair.public)
|
identityKeyPair.public)
|
||||||
val certPath = X509Utilities.buildCertPath(
|
|
||||||
identityCert,
|
val certs = arrayOf(identityCert, nodeCertificateAndKeyPair.certificate)
|
||||||
nodeCertificateAndKeyPair.certificate,
|
val key = identityKeyPair.private
|
||||||
|
|
||||||
|
val certPath = X509Utilities.buildCertPath(*certs,
|
||||||
intermediateAndRoot.first.certificate,
|
intermediateAndRoot.first.certificate,
|
||||||
intermediateAndRoot.second)
|
intermediateAndRoot.second)
|
||||||
return Pair(PartyAndCertificate(certPath), identityKeyPair.private).also {
|
|
||||||
|
return Pair(PartyAndCertificate(certPath), key).also {
|
||||||
|
identitiesAndPrivateKeys += it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addServiceIdentity(name: CordaX500Name, nodeKeyPair: KeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)): Pair<PartyAndCertificate, PrivateKey> {
|
||||||
|
val serviceCert = X509Utilities.createCertificate(
|
||||||
|
CertificateType.SERVICE_IDENTITY,
|
||||||
|
intermediateAndRoot.first.certificate,
|
||||||
|
intermediateAndRoot.first.keyPair,
|
||||||
|
name.x500Principal,
|
||||||
|
nodeKeyPair.public)
|
||||||
|
|
||||||
|
val certs = arrayOf(serviceCert)
|
||||||
|
val key = nodeKeyPair.private
|
||||||
|
|
||||||
|
val certPath = X509Utilities.buildCertPath(*certs,
|
||||||
|
intermediateAndRoot.first.certificate,
|
||||||
|
intermediateAndRoot.second)
|
||||||
|
|
||||||
|
return Pair(PartyAndCertificate(certPath), key).also {
|
||||||
identitiesAndPrivateKeys += it
|
identitiesAndPrivateKeys += it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -72,7 +95,7 @@ class TestNodeInfoBuilder(private val intermediateAndRoot: Pair<CertificateAndKe
|
|||||||
|
|
||||||
fun createNodeInfoAndSigned(vararg names: CordaX500Name, serial: Long = 1, platformVersion: Int = 1): NodeInfoAndSigned {
|
fun createNodeInfoAndSigned(vararg names: CordaX500Name, serial: Long = 1, platformVersion: Int = 1): NodeInfoAndSigned {
|
||||||
val nodeInfoBuilder = TestNodeInfoBuilder()
|
val nodeInfoBuilder = TestNodeInfoBuilder()
|
||||||
names.forEach { nodeInfoBuilder.addIdentity(it) }
|
names.forEach { nodeInfoBuilder.addLegalIdentity(it) }
|
||||||
return nodeInfoBuilder.buildWithSigned(serial, platformVersion)
|
return nodeInfoBuilder.buildWithSigned(serial, platformVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user