ENT-1226 Check minimum platform version when submitting node info (#300)

* Adding min platform version check when submitting node info

* Return error message to developer

* Fixing integration test

* Added todo to move checks out of data layer

* Added extra logging in nodeinfowebservice and use nodeinfo.verified instead of deserialize

* Tidy up tests

* Cache network parameters

* Add NodeInfoWithSigned class to stop calling to only verify node data once

* Fixing review comments

* Return correct response code if doorman not initialised properly

* Fix merge conflict
This commit is contained in:
Anthony Keenan 2018-01-09 13:31:26 +00:00 committed by GitHub
parent 2832eb489b
commit a65db712c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 118 additions and 44 deletions

View File

@ -1,7 +1,6 @@
package com.r3.corda.networkmanage.common.persistence package com.r3.corda.networkmanage.common.persistence
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.SignedData
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.SignedNodeInfo
import java.security.cert.CertPath import java.security.cert.CertPath
@ -27,5 +26,5 @@ interface NodeInfoStorage {
* @param signedNodeInfo signed node info data to be stored * @param signedNodeInfo signed node info data to be stored
* @return hash for the newly created node info entry * @return hash for the newly created node info entry
*/ */
fun putNodeInfo(signedNodeInfo: SignedNodeInfo): SecureHash fun putNodeInfo(signedNodeInfo: NodeInfoWithSigned): SecureHash
} }

View File

@ -0,0 +1,8 @@
package com.r3.corda.networkmanage.common.persistence
import net.corda.core.node.NodeInfo
import net.corda.nodeapi.internal.SignedNodeInfo
class NodeInfoWithSigned(val signedNodeInfo: SignedNodeInfo) {
val nodeInfo: NodeInfo = signedNodeInfo.verified()
}

View File

@ -17,10 +17,12 @@ import java.security.cert.CertPath
* Database implementation of the [NetworkMapStorage] interface * Database implementation of the [NetworkMapStorage] interface
*/ */
class PersistentNodeInfoStorage(private val database: CordaPersistence) : NodeInfoStorage { class PersistentNodeInfoStorage(private val database: CordaPersistence) : NodeInfoStorage {
override fun putNodeInfo(signedNodeInfo: SignedNodeInfo): SecureHash { override fun putNodeInfo(nodeInfoWithSigned: NodeInfoWithSigned): SecureHash {
val nodeInfo = signedNodeInfo.verified() val nodeInfo = nodeInfoWithSigned.nodeInfo
val signedNodeInfo = nodeInfoWithSigned.signedNodeInfo
val nodeCaCert = nodeInfo.legalIdentitiesAndCerts[0].certPath.certificates.find { CertRole.extract(it) == CertRole.NODE_CA } val nodeCaCert = nodeInfo.legalIdentitiesAndCerts[0].certPath.certificates.find { CertRole.extract(it) == CertRole.NODE_CA }
return database.transaction(TransactionIsolationLevel.SERIALIZABLE) { return database.transaction(TransactionIsolationLevel.SERIALIZABLE) {
// TODO Move these checks out of data access layer
val request = nodeCaCert?.let { val request = nodeCaCert?.let {
singleRequestWhere(CertificateDataEntity::class.java) { builder, path -> singleRequestWhere(CertificateDataEntity::class.java) { builder, path ->
val certPublicKeyHashEq = builder.equal(path.get<String>(CertificateDataEntity::publicKeyHash.name), it.publicKey.encoded.sha256().toString()) val certPublicKeyHashEq = builder.equal(path.get<String>(CertificateDataEntity::publicKeyHash.name), it.publicKey.encoded.sha256().toString())

View File

@ -5,12 +5,16 @@ import com.google.common.cache.CacheLoader
import com.google.common.cache.LoadingCache import com.google.common.cache.LoadingCache
import com.r3.corda.networkmanage.common.persistence.NetworkMapStorage import com.r3.corda.networkmanage.common.persistence.NetworkMapStorage
import com.r3.corda.networkmanage.common.persistence.NodeInfoStorage import com.r3.corda.networkmanage.common.persistence.NodeInfoStorage
import com.r3.corda.networkmanage.common.persistence.NodeInfoWithSigned
import com.r3.corda.networkmanage.doorman.NetworkMapConfig import com.r3.corda.networkmanage.doorman.NetworkMapConfig
import com.r3.corda.networkmanage.doorman.webservice.NodeInfoWebService.Companion.NETWORK_MAP_PATH import com.r3.corda.networkmanage.doorman.webservice.NodeInfoWebService.Companion.NETWORK_MAP_PATH
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.node.NodeInfo
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.utilities.contextLogger
import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.network.NetworkParameters
import net.corda.nodeapi.internal.network.SignedNetworkMap import net.corda.nodeapi.internal.network.SignedNetworkMap
import java.io.InputStream import java.io.InputStream
import java.security.InvalidKeyException import java.security.InvalidKeyException
@ -29,35 +33,41 @@ import javax.ws.rs.core.Response.status
class NodeInfoWebService(private val nodeInfoStorage: NodeInfoStorage, class NodeInfoWebService(private val nodeInfoStorage: NodeInfoStorage,
private val networkMapStorage: NetworkMapStorage, private val networkMapStorage: NetworkMapStorage,
private val config: NetworkMapConfig) { private val config: NetworkMapConfig) {
companion object { companion object {
val log = contextLogger()
const val NETWORK_MAP_PATH = "network-map" const val NETWORK_MAP_PATH = "network-map"
} }
private val networkMapCache: LoadingCache<Boolean, SignedNetworkMap?> = CacheBuilder.newBuilder() private val networkMapCache: LoadingCache<Boolean, Pair<SignedNetworkMap?, NetworkParameters?>> = CacheBuilder.newBuilder()
.expireAfterWrite(config.cacheTimeout, TimeUnit.MILLISECONDS) .expireAfterWrite(config.cacheTimeout, TimeUnit.MILLISECONDS)
.build(CacheLoader.from { _ -> networkMapStorage.getCurrentNetworkMap() }) .build(CacheLoader.from { _ -> Pair(networkMapStorage.getCurrentNetworkMap(), networkMapStorage.getCurrentNetworkParameters()) })
@POST @POST
@Path("publish") @Path("publish")
@Consumes(MediaType.APPLICATION_OCTET_STREAM) @Consumes(MediaType.APPLICATION_OCTET_STREAM)
fun registerNode(input: InputStream): Response { fun registerNode(input: InputStream): Response {
val registrationData = input.readBytes().deserialize<SignedNodeInfo>() val signedNodeInfo = input.readBytes().deserialize<SignedNodeInfo>()
return try { return try {
// Store the NodeInfo // Store the NodeInfo
nodeInfoStorage.putNodeInfo(registrationData) val nodeInfoWithSignature = NodeInfoWithSigned(signedNodeInfo)
verifyNodeInfo(nodeInfoWithSignature.nodeInfo)
nodeInfoStorage.putNodeInfo(nodeInfoWithSignature)
ok() ok()
} catch (e: Exception) { } catch (e: Exception) {
// Catch exceptions thrown by signature verification. // Catch exceptions thrown by signature verification.
when (e) { when (e) {
is NetworkMapNotInitialisedException -> status(Response.Status.SERVICE_UNAVAILABLE).entity(e.message)
is InvalidPlatformVersionException -> status(Response.Status.BAD_REQUEST).entity(e.message)
is IllegalArgumentException, is InvalidKeyException, is SignatureException -> status(Response.Status.UNAUTHORIZED).entity(e.message) is IllegalArgumentException, is InvalidKeyException, is SignatureException -> status(Response.Status.UNAUTHORIZED).entity(e.message)
// Rethrow e if its not one of the expected exception, the server will return http 500 internal error. // Rethrow e if its not one of the expected exception, the server will return http 500 internal error.
else -> throw e else -> throw e
} }
}.build() }.build()
} }
@GET @GET
fun getNetworkMap(): Response = createResponse(networkMapCache.get(true), addCacheTimeout = true) fun getNetworkMap(): Response = createResponse(networkMapCache.get(true).first, addCacheTimeout = true)
@GET @GET
@Path("node-info/{nodeInfoHash}") @Path("node-info/{nodeInfoHash}")
@ -80,6 +90,18 @@ class NodeInfoWebService(private val nodeInfoStorage: NodeInfoStorage,
return ok(request.getHeader("X-Forwarded-For")?.split(",")?.first() ?: "${request.remoteHost}:${request.remotePort}").build() return ok(request.getHeader("X-Forwarded-For")?.split(",")?.first() ?: "${request.remoteHost}:${request.remotePort}").build()
} }
private fun verifyNodeInfo(nodeInfo: NodeInfo) {
val minimumPlatformVersion = networkMapCache.get(true).second?.minimumPlatformVersion
if (minimumPlatformVersion == null) {
log.error("Network parameters have not been initialised")
throw NetworkMapNotInitialisedException("Network parameters have not been initialised")
}
if (nodeInfo.platformVersion < minimumPlatformVersion) {
log.error("Minimum platform version is $minimumPlatformVersion")
throw InvalidPlatformVersionException("Minimum platform version is $minimumPlatformVersion")
}
}
private fun createResponse(payload: Any?, addCacheTimeout: Boolean = false): Response { private fun createResponse(payload: Any?, addCacheTimeout: Boolean = false): Response {
return if (payload != null) { return if (payload != null) {
val ok = Response.ok(payload.serialize().bytes) val ok = Response.ok(payload.serialize().bytes)
@ -91,4 +113,7 @@ class NodeInfoWebService(private val nodeInfoStorage: NodeInfoStorage,
status(Response.Status.NOT_FOUND) status(Response.Status.NOT_FOUND)
}.build() }.build()
} }
class NetworkMapNotInitialisedException(message: String?) : Exception(message)
class InvalidPlatformVersionException(message: String?) : Exception(message)
} }

View File

@ -148,7 +148,7 @@ class PersistentNetworkMapStorageTest : TestBase() {
assertThat(validNodeInfoHash).containsOnly(nodeInfoHashA, nodeInfoHashB) assertThat(validNodeInfoHash).containsOnly(nodeInfoHashA, nodeInfoHashB)
} }
private fun createValidSignedNodeInfo(organisation: String): SignedNodeInfo { private fun createValidSignedNodeInfo(organisation: String): NodeInfoWithSigned {
val nodeInfoBuilder = TestNodeInfoBuilder() val nodeInfoBuilder = TestNodeInfoBuilder()
val requestId = requestStorage.saveRequest(createRequest(organisation).first) val requestId = requestStorage.saveRequest(createRequest(organisation).first)
requestStorage.markRequestTicketCreated(requestId) requestStorage.markRequestTicketCreated(requestId)
@ -156,6 +156,6 @@ class PersistentNetworkMapStorageTest : TestBase() {
val (identity) = nodeInfoBuilder.addIdentity(CordaX500Name(organisation, "London", "GB")) val (identity) = nodeInfoBuilder.addIdentity(CordaX500Name(organisation, "London", "GB"))
val nodeCaCertPath = X509CertificateFactory().generateCertPath(identity.certPath.certificates.drop(1)) val nodeCaCertPath = X509CertificateFactory().generateCertPath(identity.certPath.certificates.drop(1))
requestStorage.putCertificatePath(requestId, nodeCaCertPath, emptyList()) requestStorage.putCertificatePath(requestId, nodeCaCertPath, emptyList())
return nodeInfoBuilder.buildWithSigned().second return NodeInfoWithSigned(nodeInfoBuilder.buildWithSigned().second)
} }
} }

View File

@ -28,7 +28,7 @@ import kotlin.test.assertEquals
import kotlin.test.assertNotNull import kotlin.test.assertNotNull
import kotlin.test.assertNull import kotlin.test.assertNull
class PersitenceNodeInfoStorageTest : TestBase() { class PersistentNodeInfoStorageTest : TestBase() {
private lateinit var requestStorage: CertificationRequestStorage private lateinit var requestStorage: CertificationRequestStorage
private lateinit var nodeInfoStorage: PersistentNodeInfoStorage private lateinit var nodeInfoStorage: PersistentNodeInfoStorage
private lateinit var persistence: CordaPersistence private lateinit var persistence: CordaPersistence
@ -85,34 +85,34 @@ class PersitenceNodeInfoStorageTest : TestBase() {
@Test @Test
fun `getNodeInfo returns persisted SignedNodeInfo using the hash of just the NodeInfo`() { fun `getNodeInfo returns persisted SignedNodeInfo using the hash of just the NodeInfo`() {
// given // given
val (nodeInfoA, signedNodeInfoA) = createValidSignedNodeInfo("TestA") val (nodeA) = createValidSignedNodeInfo("TestA")
val (nodeInfoB, signedNodeInfoB) = createValidSignedNodeInfo("TestB") val (nodeB) = createValidSignedNodeInfo("TestB")
// Put signed node info data // Put signed node info data
nodeInfoStorage.putNodeInfo(signedNodeInfoA) nodeInfoStorage.putNodeInfo(nodeA)
nodeInfoStorage.putNodeInfo(signedNodeInfoB) nodeInfoStorage.putNodeInfo(nodeB)
// when // when
val persistedSignedNodeInfoA = nodeInfoStorage.getNodeInfo(nodeInfoA.serialize().hash) val persistedSignedNodeInfoA = nodeInfoStorage.getNodeInfo(nodeA.nodeInfo.serialize().hash)
val persistedSignedNodeInfoB = nodeInfoStorage.getNodeInfo(nodeInfoB.serialize().hash) val persistedSignedNodeInfoB = nodeInfoStorage.getNodeInfo(nodeB.nodeInfo.serialize().hash)
// then // then
assertEquals(persistedSignedNodeInfoA?.verified(), nodeInfoA) assertEquals(persistedSignedNodeInfoA?.verified(), nodeA.nodeInfo)
assertEquals(persistedSignedNodeInfoB?.verified(), nodeInfoB) assertEquals(persistedSignedNodeInfoB?.verified(), nodeB.nodeInfo)
} }
@Test @Test
fun `same public key with different node info`() { fun `same public key with different node info`() {
// Create node info. // Create node info.
val (nodeInfo1, signedNodeInfo1, key) = createValidSignedNodeInfo("Test", serial = 1) val (node1, key) = createValidSignedNodeInfo("Test", serial = 1)
val nodeInfo2 = nodeInfo1.copy(serial = 2) val nodeInfo2 = node1.nodeInfo.copy(serial = 2)
val signedNodeInfo2 = nodeInfo2.signWith(listOf(key)) val node2 = NodeInfoWithSigned(nodeInfo2.signWith(listOf(key)))
val nodeInfo1Hash = nodeInfoStorage.putNodeInfo(signedNodeInfo1) val nodeInfo1Hash = nodeInfoStorage.putNodeInfo(node1)
assertEquals(nodeInfo1, nodeInfoStorage.getNodeInfo(nodeInfo1Hash)?.verified()) assertEquals(node1.nodeInfo, nodeInfoStorage.getNodeInfo(nodeInfo1Hash)?.verified())
// This should replace the node info. // This should replace the node info.
nodeInfoStorage.putNodeInfo(signedNodeInfo2) nodeInfoStorage.putNodeInfo(node2)
// Old node info should be removed. // Old node info should be removed.
assertNull(nodeInfoStorage.getNodeInfo(nodeInfo1Hash)) assertNull(nodeInfoStorage.getNodeInfo(nodeInfo1Hash))
@ -122,17 +122,17 @@ class PersitenceNodeInfoStorageTest : TestBase() {
@Test @Test
fun `putNodeInfo persists SignedNodeInfo with its signature`() { fun `putNodeInfo persists SignedNodeInfo with its signature`() {
// given // given
val (_, signedNodeInfo) = createValidSignedNodeInfo("Test") val (nodeInfoWithSigned) = createValidSignedNodeInfo("Test")
// when // when
val nodeInfoHash = nodeInfoStorage.putNodeInfo(signedNodeInfo) val nodeInfoHash = nodeInfoStorage.putNodeInfo(nodeInfoWithSigned)
// then // then
val persistedSignedNodeInfo = nodeInfoStorage.getNodeInfo(nodeInfoHash) val persistedSignedNodeInfo = nodeInfoStorage.getNodeInfo(nodeInfoHash)
assertThat(persistedSignedNodeInfo?.signatures).isEqualTo(signedNodeInfo.signatures) assertThat(persistedSignedNodeInfo?.signatures).isEqualTo(nodeInfoWithSigned.signedNodeInfo.signatures)
} }
private fun createValidSignedNodeInfo(organisation: String, serial: Long = 1): Triple<NodeInfo, SignedNodeInfo, PrivateKey> { private fun createValidSignedNodeInfo(organisation: String, serial: Long = 1): Pair<NodeInfoWithSigned, PrivateKey> {
val nodeInfoBuilder = TestNodeInfoBuilder() val nodeInfoBuilder = TestNodeInfoBuilder()
val requestId = requestStorage.saveRequest(createRequest(organisation).first) val requestId = requestStorage.saveRequest(createRequest(organisation).first)
requestStorage.markRequestTicketCreated(requestId) requestStorage.markRequestTicketCreated(requestId)
@ -140,7 +140,7 @@ class PersitenceNodeInfoStorageTest : TestBase() {
val (identity, key) = nodeInfoBuilder.addIdentity(CordaX500Name(organisation, "London", "GB")) val (identity, key) = nodeInfoBuilder.addIdentity(CordaX500Name(organisation, "London", "GB"))
val nodeCaCertPath = X509CertificateFactory().generateCertPath(identity.certPath.certificates.drop(1)) val nodeCaCertPath = X509CertificateFactory().generateCertPath(identity.certPath.certificates.drop(1))
requestStorage.putCertificatePath(requestId, nodeCaCertPath, emptyList()) requestStorage.putCertificatePath(requestId, nodeCaCertPath, emptyList())
val (nodeInfo, signedNodeInfo) = nodeInfoBuilder.buildWithSigned(serial) val (_, signedNodeInfo) = nodeInfoBuilder.buildWithSigned(serial)
return Triple(nodeInfo, signedNodeInfo, key) return Pair(NodeInfoWithSigned(signedNodeInfo), key)
} }
} }

View File

@ -25,13 +25,15 @@ import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.internal.createDevIntermediateCaCertPath import net.corda.testing.internal.createDevIntermediateCaCertPath
import net.corda.testing.internal.createNodeInfoAndSigned import net.corda.testing.internal.createNodeInfoAndSigned
import org.assertj.core.api.Assertions.assertThat import org.apache.commons.io.IOUtils
import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.assertj.core.api.Assertions.*
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.io.IOException
import java.net.URL import java.net.URL
import java.nio.charset.Charset
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
import javax.ws.rs.core.MediaType import javax.ws.rs.core.MediaType
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -44,7 +46,7 @@ class NodeInfoWebServiceTest {
private lateinit var rootCaCert: X509Certificate private lateinit var rootCaCert: X509Certificate
private lateinit var intermediateCa: CertificateAndKeyPair private lateinit var intermediateCa: CertificateAndKeyPair
private val testNetworkMapConfig = NetworkMapConfig(10.seconds.toMillis(), 10.seconds.toMillis()) private val testNetworkMapConfig = NetworkMapConfig(10.seconds.toMillis(), 10.seconds.toMillis())
@Before @Before
fun init() { fun init() {
@ -55,10 +57,13 @@ class NodeInfoWebServiceTest {
@Test @Test
fun `submit nodeInfo`() { fun `submit nodeInfo`() {
val networkMapStorage: NetworkMapStorage = mock {
on { getCurrentNetworkParameters() }.thenReturn(testNetworkParameters(emptyList()))
}
// Create node info. // Create node info.
val (_, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name("Test", "London", "GB")) val (_, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name("Test", "London", "GB"))
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(mock(), mock(), testNetworkMapConfig)).use { NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(mock(), networkMapStorage, testNetworkMapConfig)).use {
it.start() it.start()
val nodeInfoAndSignature = signedNodeInfo.serialize().bytes val nodeInfoAndSignature = signedNodeInfo.serialize().bytes
// Post node info and signature to doorman, this should pass without any exception. // Post node info and signature to doorman, this should pass without any exception.
@ -66,6 +71,38 @@ class NodeInfoWebServiceTest {
} }
} }
@Test
fun `submit old nodeInfo`() {
val networkMapStorage: NetworkMapStorage = mock {
on { getCurrentNetworkParameters() }.thenReturn(testNetworkParameters(emptyList(), minimumPlatformVersion = 2))
}
// Create node info.
val (_, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name("Test", "London", "GB"), platformVersion = 1)
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(mock(), networkMapStorage, testNetworkMapConfig)).use {
it.start()
val nodeInfoAndSignature = signedNodeInfo.serialize().bytes
assertThatThrownBy { it.doPost("publish", nodeInfoAndSignature) }
.hasMessageStartingWith("Response Code 400: Minimum platform version is 2")
}
}
@Test
fun `submit nodeInfo when no network parameters`() {
val networkMapStorage: NetworkMapStorage = mock {
on { getCurrentNetworkParameters() }.thenReturn(null)
}
// Create node info.
val (_, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name("Test", "London", "GB"), platformVersion = 1)
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(mock(), networkMapStorage, testNetworkMapConfig)).use {
it.start()
val nodeInfoAndSignature = signedNodeInfo.serialize().bytes
assertThatThrownBy { it.doPost("publish", nodeInfoAndSignature) }
.hasMessageStartingWith("Response Code 503: Network parameters have not been initialised")
}
}
@Test @Test
fun `get network map`() { fun `get network map`() {
val networkMap = NetworkMap(listOf(randomSHA256(), randomSHA256()), randomSHA256()) val networkMap = NetworkMap(listOf(randomSHA256(), randomSHA256()), randomSHA256())
@ -136,7 +173,10 @@ class NodeInfoWebServiceTest {
requestMethod = "POST" requestMethod = "POST"
setRequestProperty("Content-Type", MediaType.APPLICATION_OCTET_STREAM) setRequestProperty("Content-Type", MediaType.APPLICATION_OCTET_STREAM)
outputStream.write(payload) outputStream.write(payload)
inputStream.close() // This will give us a nice IOException if the response isn't HTTP 200 if (responseCode != 200) {
throw IOException("Response Code $responseCode: ${IOUtils.toString(errorStream, Charset.defaultCharset())}")
}
inputStream.close()
} }
} }

View File

@ -22,17 +22,17 @@ class TestNodeInfoBuilder {
} }
} }
fun build(serial: Long = 1): NodeInfo { fun build(serial: Long = 1, platformVersion: Int = 1): NodeInfo {
return NodeInfo( return NodeInfo(
listOf(NetworkHostAndPort("my.${identitiesAndPrivateKeys[0].first.party.name.organisation}.com", 1234)), listOf(NetworkHostAndPort("my.${identitiesAndPrivateKeys[0].first.party.name.organisation}.com", 1234)),
identitiesAndPrivateKeys.map { it.first }, identitiesAndPrivateKeys.map { it.first },
1, platformVersion,
serial serial
) )
} }
fun buildWithSigned(serial: Long = 1): Pair<NodeInfo, SignedNodeInfo> { fun buildWithSigned(serial: Long = 1, platformVersion: Int = 1): Pair<NodeInfo, SignedNodeInfo> {
val nodeInfo = build(serial) val nodeInfo = build(serial, platformVersion)
val privateKeys = identitiesAndPrivateKeys.map { it.second } val privateKeys = identitiesAndPrivateKeys.map { it.second }
return Pair(nodeInfo, nodeInfo.signWith(privateKeys)) return Pair(nodeInfo, nodeInfo.signWith(privateKeys))
} }
@ -42,10 +42,10 @@ class TestNodeInfoBuilder {
} }
} }
fun createNodeInfoAndSigned(vararg names: CordaX500Name, serial: Long = 1): Pair<NodeInfo, SignedNodeInfo> { fun createNodeInfoAndSigned(vararg names: CordaX500Name, serial: Long = 1, platformVersion: Int = 1): Pair<NodeInfo, SignedNodeInfo> {
val nodeInfoBuilder = TestNodeInfoBuilder() val nodeInfoBuilder = TestNodeInfoBuilder()
names.forEach { nodeInfoBuilder.addIdentity(it) } names.forEach { nodeInfoBuilder.addIdentity(it) }
return nodeInfoBuilder.buildWithSigned(serial) return nodeInfoBuilder.buildWithSigned(serial, platformVersion)
} }
fun NodeInfo.signWith(keys: List<PrivateKey>): SignedNodeInfo { fun NodeInfo.signWith(keys: List<PrivateKey>): SignedNodeInfo {