mirror of
https://github.com/corda/corda.git
synced 2025-06-14 13:18:18 +00:00
CORDA-833: SignedNodeInfo object for holding a list of signatures, one for each identity in the NodeInfo. This forms part of the network map.
This commit is contained in:
@ -4,6 +4,7 @@ import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.verify
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.SerializedBytes
|
||||
import net.corda.core.serialization.deserialize
|
||||
@ -59,7 +60,7 @@ data class NotaryInfo(val identity: Party, val validating: Boolean)
|
||||
* contained within.
|
||||
*/
|
||||
@CordaSerializable
|
||||
class SignedNetworkMap(val raw: SerializedBytes<NetworkMap>, val sig: DigitalSignatureWithCert) {
|
||||
class SignedNetworkMap(val raw: SerializedBytes<NetworkMap>, val signature: DigitalSignatureWithCert) {
|
||||
/**
|
||||
* Return the deserialized NetworkMap if the signature and certificate can be verified.
|
||||
*
|
||||
@ -68,13 +69,14 @@ class SignedNetworkMap(val raw: SerializedBytes<NetworkMap>, val sig: DigitalSig
|
||||
*/
|
||||
@Throws(SignatureException::class, CertPathValidatorException::class)
|
||||
fun verified(trustedRoot: X509Certificate): NetworkMap {
|
||||
sig.by.publicKey.verify(raw.bytes, sig)
|
||||
signature.by.publicKey.verify(raw.bytes, signature)
|
||||
// Assume network map cert is under the default trust root.
|
||||
X509Utilities.validateCertificateChain(trustedRoot, sig.by, trustedRoot)
|
||||
X509Utilities.validateCertificateChain(trustedRoot, signature.by, trustedRoot)
|
||||
return raw.deserialize()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This class should reside in the [DigitalSignature] class.
|
||||
// TODO: Removing the val from signatureBytes causes serialisation issues
|
||||
/** A digital signature that identifies who the public key is owned by, and the certificate which provides prove of the identity */
|
||||
class DigitalSignatureWithCert(val by: X509Certificate, val signatureBytes: ByteArray) : DigitalSignature(signatureBytes)
|
||||
class DigitalSignatureWithCert(val by: X509Certificate, val signatureBytes: ByteArray) : DigitalSignature(signatureBytes)
|
||||
|
@ -1,7 +1,6 @@
|
||||
package net.corda.nodeapi.internal
|
||||
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import net.corda.core.crypto.SignedData
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.list
|
||||
@ -78,7 +77,7 @@ class NetworkParametersGenerator {
|
||||
private fun processFile(file: Path): NodeInfo? {
|
||||
return try {
|
||||
logger.info("Reading NodeInfo from file: $file")
|
||||
val signedData = file.readAll().deserialize<SignedData<NodeInfo>>()
|
||||
val signedData = file.readAll().deserialize<SignedNodeInfo>()
|
||||
signedData.verified()
|
||||
} catch (e: Exception) {
|
||||
logger.warn("Exception parsing NodeInfo from file. $file", e)
|
||||
|
@ -0,0 +1,44 @@
|
||||
package net.corda.nodeapi.internal
|
||||
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.crypto.verify
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.SerializedBytes
|
||||
import net.corda.core.serialization.deserialize
|
||||
import java.security.SignatureException
|
||||
|
||||
/**
|
||||
* A signed [NodeInfo] object containing a signature for each identity. The list of signatures is expected
|
||||
* to be in the same order as the identities.
|
||||
*/
|
||||
// TODO Add signatures for composite keys. The current thinking is to make sure there is a signature for each leaf key
|
||||
// that the node owns. This check can only be done by the network map server as it can check with the doorman if a node
|
||||
// is part of a composite identity. This of course further requires the doorman being able to issue CSRs for composite
|
||||
// public keys.
|
||||
@CordaSerializable
|
||||
class SignedNodeInfo(val raw: SerializedBytes<NodeInfo>, val signatures: List<DigitalSignature>) {
|
||||
fun verified(): NodeInfo {
|
||||
val nodeInfo = raw.deserialize()
|
||||
val identities = nodeInfo.legalIdentities.filterNot { it.owningKey is CompositeKey }
|
||||
|
||||
if (identities.size < signatures.size) {
|
||||
throw SignatureException("Extra signatures. Found ${signatures.size} expected ${identities.size}")
|
||||
}
|
||||
if (identities.size > signatures.size) {
|
||||
throw SignatureException("Missing signatures. Found ${signatures.size} expected ${identities.size}")
|
||||
}
|
||||
|
||||
val rawBytes = raw.bytes // To avoid cloning the byte array multiple times
|
||||
identities.zip(signatures).forEach { (identity, signature) ->
|
||||
try {
|
||||
identity.owningKey.verify(rawBytes, signature)
|
||||
} catch (e: SignatureException) {
|
||||
throw SignatureException("$identity: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
return nodeInfo
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
package net.corda.nodeapi.internal
|
||||
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.testing.ALICE_NAME
|
||||
import net.corda.testing.BOB_NAME
|
||||
import net.corda.testing.SerializationEnvironmentRule
|
||||
import net.corda.testing.internal.TestNodeInfoBuilder
|
||||
import net.corda.testing.internal.signWith
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.security.SignatureException
|
||||
|
||||
class SignedNodeInfoTest {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
|
||||
private val nodeInfoBuilder = TestNodeInfoBuilder()
|
||||
|
||||
@Test
|
||||
fun `verifying single identity`() {
|
||||
nodeInfoBuilder.addIdentity(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)
|
||||
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 nodeInfo = nodeInfoBuilder.build()
|
||||
val signedNodeInfo = nodeInfo.signWith(listOf(aliceKey))
|
||||
assertThatThrownBy { signedNodeInfo.verified() }
|
||||
.isInstanceOf(SignatureException::class.java)
|
||||
.hasMessageContaining("Missing signatures")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `verifying extra signature`() {
|
||||
val (_, aliceKey) = nodeInfoBuilder.addIdentity(ALICE_NAME)
|
||||
val nodeInfo = nodeInfoBuilder.build()
|
||||
val signedNodeInfo = nodeInfo.signWith(listOf(aliceKey, generateKeyPair().private))
|
||||
assertThatThrownBy { signedNodeInfo.verified() }
|
||||
.isInstanceOf(SignatureException::class.java)
|
||||
.hasMessageContaining("Extra signatures")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `verifying incorrect signature`() {
|
||||
nodeInfoBuilder.addIdentity(ALICE_NAME)
|
||||
val nodeInfo = nodeInfoBuilder.build()
|
||||
val signedNodeInfo = nodeInfo.signWith(listOf(generateKeyPair().private))
|
||||
assertThatThrownBy { signedNodeInfo.verified() }
|
||||
.isInstanceOf(SignatureException::class.java)
|
||||
.hasMessageContaining(ALICE_NAME.toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `verifying with signatures in wrong order`() {
|
||||
val (_, aliceKey) = nodeInfoBuilder.addIdentity(ALICE_NAME)
|
||||
val (_, bobKey) = nodeInfoBuilder.addIdentity(BOB_NAME)
|
||||
val nodeInfo = nodeInfoBuilder.build()
|
||||
val signedNodeInfo = nodeInfo.signWith(listOf(bobKey, aliceKey))
|
||||
assertThatThrownBy { signedNodeInfo.verified() }
|
||||
.isInstanceOf(SignatureException::class.java)
|
||||
.hasMessageContaining(ALICE_NAME.toString())
|
||||
}
|
||||
|
||||
private fun generateKeyPair() = Crypto.generateKeyPair()
|
||||
}
|
Reference in New Issue
Block a user