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:
Patrick Kuo 2018-04-12 13:05:15 +01:00 committed by GitHub
parent 241f7738e4
commit f9ed55b731
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 118 additions and 36 deletions

View File

@ -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,12 +51,20 @@ class PersistentNodeInfoStorage(private val database: CordaPersistence) : NodeIn
}
// 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.
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")

View File

@ -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.")
}
internal fun createValidSignedNodeInfo(organisation: String,
storage: CertificateSigningRequestStorage): Pair<NodeInfoAndSigned, PrivateKey> {
val (csr, nodeKeyPair) = createRequest(organisation, certRole = CertRole.NODE_CA)
@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")
}
}
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 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 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 (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)
}

View File

@ -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() }

View File

@ -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))

View File

@ -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)
}