mirror of
https://github.com/corda/corda.git
synced 2024-12-28 16:58:55 +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 {
|
||||
override fun putNodeInfo(nodeInfoAndSigned: NodeInfoAndSigned): SecureHash {
|
||||
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
|
||||
|
||||
// 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 {
|
||||
val count = session.createQuery(
|
||||
"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
|
||||
val request = requireNotNull(getSignedRequestByPublicHash(nodeCaCert.publicKey.encoded.sha256())) {
|
||||
"Node-info not registered with us"
|
||||
}
|
||||
request.certificateData?.certificateStatus.let {
|
||||
require(it == CertificateStatus.VALID) { "Certificate is no longer valid: $it" }
|
||||
// For each identity known by the doorman, validate against it's CSR.
|
||||
val requests = registeredIdentities.map {
|
||||
val request = requireNotNull(getSignedRequestByPublicHash(it.publicKey.hash)) {
|
||||
"Node-info not registered with us"
|
||||
}
|
||||
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>(
|
||||
"n where n.certificateSigningRequest = :csr and n.isCurrent = true order by n.publishedAt desc")
|
||||
.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.common.persistence.entity.NodeInfoEntity
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.sha256
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.CertRole
|
||||
import net.corda.core.internal.hash
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.days
|
||||
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.node.MockServices
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
@ -216,17 +217,66 @@ class PersistentNodeInfoStorageTest : TestBase() {
|
||||
val acceptedUpdate = nodeInfoStorage.getAcceptedParametersUpdate(nodeInfoHash2)
|
||||
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,
|
||||
storage: CertificateSigningRequestStorage): Pair<NodeInfoAndSigned, PrivateKey> {
|
||||
val (csr, nodeKeyPair) = createRequest(organisation, certRole = CertRole.NODE_CA)
|
||||
val requestId = storage.saveRequest(csr)
|
||||
storage.markRequestTicketCreated(requestId)
|
||||
storage.approveRequest(requestId, "TestUser")
|
||||
private fun createValidNodeInfo(organisation: String, storage: CertificateSigningRequestStorage): Pair<NodeInfo, PrivateKey> {
|
||||
val (nodeInfo, keys) = createValidNodeInfo(storage, CertRole.NODE_CA to organisation)
|
||||
return Pair(nodeInfo, keys.single())
|
||||
}
|
||||
|
||||
private fun createValidNodeInfo(storage: CertificateSigningRequestStorage, vararg identities: Pair<CertRole, String>): Pair<NodeInfo, List<PrivateKey>> {
|
||||
val nodeInfoBuilder = TestNodeInfoBuilder()
|
||||
val (identity, key) = nodeInfoBuilder.addIdentity(CordaX500Name.build(X500Principal(csr.subject.encoded)), nodeKeyPair)
|
||||
storage.putCertificatePath(requestId, identity.certPath, "Test")
|
||||
val (_, signedNodeInfo) = nodeInfoBuilder.buildWithSigned(1)
|
||||
return Pair(NodeInfoAndSigned(signedNodeInfo), key)
|
||||
}
|
||||
val keys = identities.map { (certRole, name) ->
|
||||
val (csr, nodeKeyPair) = createRequest(name, certRole = certRole)
|
||||
val requestId = storage.saveRequest(csr)
|
||||
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
|
||||
fun `verifying single identity`() {
|
||||
nodeInfoBuilder.addIdentity(ALICE_NAME)
|
||||
nodeInfoBuilder.addLegalIdentity(ALICE_NAME)
|
||||
val (nodeInfo, signedNodeInfo) = nodeInfoBuilder.buildWithSigned()
|
||||
assertThat(signedNodeInfo.verified()).isEqualTo(nodeInfo)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `verifying multiple identities`() {
|
||||
nodeInfoBuilder.addIdentity(ALICE_NAME)
|
||||
nodeInfoBuilder.addIdentity(BOB_NAME)
|
||||
nodeInfoBuilder.addLegalIdentity(ALICE_NAME)
|
||||
nodeInfoBuilder.addLegalIdentity(BOB_NAME)
|
||||
val (nodeInfo, signedNodeInfo) = nodeInfoBuilder.buildWithSigned()
|
||||
assertThat(signedNodeInfo.verified()).isEqualTo(nodeInfo)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `verifying missing signature`() {
|
||||
val (_, aliceKey) = nodeInfoBuilder.addIdentity(ALICE_NAME)
|
||||
nodeInfoBuilder.addIdentity(BOB_NAME)
|
||||
val (_, aliceKey) = nodeInfoBuilder.addLegalIdentity(ALICE_NAME)
|
||||
nodeInfoBuilder.addLegalIdentity(BOB_NAME)
|
||||
val nodeInfo = nodeInfoBuilder.build()
|
||||
val signedNodeInfo = nodeInfo.signWith(listOf(aliceKey))
|
||||
assertThatThrownBy { signedNodeInfo.verified() }
|
||||
@ -80,7 +80,7 @@ class SignedNodeInfoTest {
|
||||
|
||||
@Test
|
||||
fun `verifying extra signature`() {
|
||||
val (_, aliceKey) = nodeInfoBuilder.addIdentity(ALICE_NAME)
|
||||
val (_, aliceKey) = nodeInfoBuilder.addLegalIdentity(ALICE_NAME)
|
||||
val nodeInfo = nodeInfoBuilder.build()
|
||||
val signedNodeInfo = nodeInfo.signWith(listOf(aliceKey, generateKeyPair().private))
|
||||
assertThatThrownBy { signedNodeInfo.verified() }
|
||||
@ -90,7 +90,7 @@ class SignedNodeInfoTest {
|
||||
|
||||
@Test
|
||||
fun `verifying incorrect signature`() {
|
||||
nodeInfoBuilder.addIdentity(ALICE_NAME)
|
||||
nodeInfoBuilder.addLegalIdentity(ALICE_NAME)
|
||||
val nodeInfo = nodeInfoBuilder.build()
|
||||
val signedNodeInfo = nodeInfo.signWith(listOf(generateKeyPair().private))
|
||||
assertThatThrownBy { signedNodeInfo.verified() }
|
||||
@ -100,8 +100,8 @@ class SignedNodeInfoTest {
|
||||
|
||||
@Test
|
||||
fun `verifying with signatures in wrong order`() {
|
||||
val (_, aliceKey) = nodeInfoBuilder.addIdentity(ALICE_NAME)
|
||||
val (_, bobKey) = nodeInfoBuilder.addIdentity(BOB_NAME)
|
||||
val (_, aliceKey) = nodeInfoBuilder.addLegalIdentity(ALICE_NAME)
|
||||
val (_, bobKey) = nodeInfoBuilder.addLegalIdentity(BOB_NAME)
|
||||
val nodeInfo = nodeInfoBuilder.build()
|
||||
val signedNodeInfo = nodeInfo.signWith(listOf(bobKey, aliceKey))
|
||||
assertThatThrownBy { signedNodeInfo.verified() }
|
||||
|
@ -83,8 +83,8 @@ class NetworkMapClientTest {
|
||||
@Test
|
||||
fun `errors return a meaningful error message`() {
|
||||
val nodeInfoBuilder = TestNodeInfoBuilder()
|
||||
val (_, aliceKey) = nodeInfoBuilder.addIdentity(ALICE_NAME)
|
||||
nodeInfoBuilder.addIdentity(BOB_NAME)
|
||||
val (_, aliceKey) = nodeInfoBuilder.addLegalIdentity(ALICE_NAME)
|
||||
nodeInfoBuilder.addLegalIdentity(BOB_NAME)
|
||||
val nodeInfo3 = nodeInfoBuilder.build()
|
||||
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) {
|
||||
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 identityKeyPair = Crypto.generateKeyPair()
|
||||
val identityCert = X509Utilities.createCertificate(
|
||||
@ -39,12 +39,35 @@ class TestNodeInfoBuilder(private val intermediateAndRoot: Pair<CertificateAndKe
|
||||
nodeCertificateAndKeyPair.keyPair,
|
||||
nodeCertificateAndKeyPair.certificate.subjectX500Principal,
|
||||
identityKeyPair.public)
|
||||
val certPath = X509Utilities.buildCertPath(
|
||||
identityCert,
|
||||
nodeCertificateAndKeyPair.certificate,
|
||||
|
||||
val certs = arrayOf(identityCert, nodeCertificateAndKeyPair.certificate)
|
||||
val key = identityKeyPair.private
|
||||
|
||||
val certPath = X509Utilities.buildCertPath(*certs,
|
||||
intermediateAndRoot.first.certificate,
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -72,7 +95,7 @@ class TestNodeInfoBuilder(private val intermediateAndRoot: Pair<CertificateAndKe
|
||||
|
||||
fun createNodeInfoAndSigned(vararg names: CordaX500Name, serial: Long = 1, platformVersion: Int = 1): NodeInfoAndSigned {
|
||||
val nodeInfoBuilder = TestNodeInfoBuilder()
|
||||
names.forEach { nodeInfoBuilder.addIdentity(it) }
|
||||
names.forEach { nodeInfoBuilder.addLegalIdentity(it) }
|
||||
return nodeInfoBuilder.buildWithSigned(serial, platformVersion)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user