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:
Shams Asari
2017-12-12 22:07:20 +00:00
parent 8114a20abd
commit e9cead9055
15 changed files with 381 additions and 261 deletions

View File

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

View File

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

View File

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

View File

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