Added network-parameter REST endpoint for vending signed network parameters. (#252)

For now this only works with the local signer.
This commit is contained in:
Shams Asari
2017-12-22 14:53:36 +00:00
committed by GitHub
parent c545a58c1d
commit 19df02541a
10 changed files with 203 additions and 161 deletions

View File

@ -6,13 +6,10 @@ import com.r3.corda.networkmanage.common.persistence.CertificateSigningRequest
import com.r3.corda.networkmanage.common.persistence.CertificateStatus
import com.r3.corda.networkmanage.common.persistence.RequestStatus
import net.corda.core.crypto.SecureHash
import net.corda.nodeapi.internal.network.NetworkParameters
import net.corda.nodeapi.internal.network.NotaryInfo
import net.corda.testing.SerializationEnvironmentRule
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import org.junit.Rule
import java.security.cert.CertPath
import java.time.Instant
abstract class TestBase {
@Rule
@ -48,21 +45,4 @@ abstract class TestBase {
certPath = certPath
)
}
// TODO remove this once testNetworkParameters are updated with default parameters
protected fun createNetworkParameters(minimumPlatformVersion: Int = 1,
notaries: List<NotaryInfo> = emptyList(),
maxMessageSize: Int = 10485760,
maxTransactionSize: Int = 40000,
modifiedTime: Instant = Instant.now(),
epoch: Int = 1): NetworkParameters {
return NetworkParameters(
minimumPlatformVersion = minimumPlatformVersion,
notaries = notaries,
maxMessageSize = maxMessageSize,
maxTransactionSize = maxTransactionSize,
modifiedTime = modifiedTime,
epoch = epoch
)
}
}

View File

@ -2,6 +2,7 @@ package com.r3.corda.networkmanage.common.persistence
import com.r3.corda.networkmanage.TestBase
import com.r3.corda.networkmanage.common.utils.withCert
import com.r3.corda.networkmanage.doorman.signer.LocalSigner
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.sign
import net.corda.core.identity.CordaX500Name
@ -23,21 +24,21 @@ import org.junit.Before
import org.junit.Test
import kotlin.test.assertEquals
class DBNetworkMapStorageTest : TestBase() {
private lateinit var networkMapStorage: NetworkMapStorage
private lateinit var requestStorage: CertificationRequestStorage
private lateinit var nodeInfoStorage: NodeInfoStorage
class PersistentNetworkMapStorageTest : TestBase() {
private lateinit var persistence: CordaPersistence
private lateinit var networkMapStorage: PersistentNetworkMapStorage
private lateinit var nodeInfoStorage: PersistentNodeInfoStorage
private lateinit var requestStorage: PersistentCertificateRequestStorage
private val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
private val rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Corda Node Root CA", locality = "London", organisation = "R3 LTD", country = "GB"), rootCAKey)
private val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
private val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey, CordaX500Name(commonName = "Corda Node Intermediate CA", locality = "London", organisation = "R3 LTD", country = "GB"), intermediateCAKey.public)
private val rootCaKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
private val rootCaCert = X509Utilities.createSelfSignedCACertificate(CordaX500Name("Corda Node Root CA", "R3 LTD", "London", "GB"), rootCaKeyPair)
private val intermediateCaKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
private val intermediateCaCert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCaCert, rootCaKeyPair, CordaX500Name("Corda Node Intermediate CA", "R3 LTD", "London", "GB"), intermediateCaKeyPair.public)
@Before
fun startDb() {
persistence = configureDatabase(makeTestDataSourceProperties())
networkMapStorage = PersistentNetworkMapStorage(persistence)
networkMapStorage = PersistentNetworkMapStorage(persistence, LocalSigner(intermediateCaKeyPair, arrayOf(intermediateCaCert.cert, rootCaCert.cert)))
nodeInfoStorage = PersistentNodeInfoStorage(persistence)
requestStorage = PersistentCertificateRequestStorage(persistence)
}
@ -59,7 +60,7 @@ class DBNetworkMapStorageTest : TestBase() {
val networkMap = NetworkMap(listOf(nodeInfoHash), networkParametersHash)
val serializedNetworkMap = networkMap.serialize()
val signatureData = intermediateCAKey.sign(serializedNetworkMap).withCert(intermediateCACert.cert)
val signatureData = intermediateCaKeyPair.sign(serializedNetworkMap).withCert(intermediateCaCert.cert)
val signedNetworkMap = SignedNetworkMap(serializedNetworkMap, signatureData)
// when
@ -69,14 +70,14 @@ class DBNetworkMapStorageTest : TestBase() {
val persistedSignedNetworkMap = networkMapStorage.getCurrentNetworkMap()
assertEquals(signedNetworkMap.signature, persistedSignedNetworkMap?.signature)
assertEquals(signedNetworkMap.verified(rootCACert.cert), persistedSignedNetworkMap?.verified(rootCACert.cert))
assertEquals(signedNetworkMap.verified(rootCaCert.cert), persistedSignedNetworkMap?.verified(rootCaCert.cert))
}
@Test
fun `getLatestNetworkParameters returns last inserted`() {
// given
networkMapStorage.saveNetworkParameters(createNetworkParameters(minimumPlatformVersion = 1))
networkMapStorage.saveNetworkParameters(createNetworkParameters(minimumPlatformVersion = 2))
networkMapStorage.saveNetworkParameters(testNetworkParameters(emptyList(), minimumPlatformVersion = 1))
networkMapStorage.saveNetworkParameters(testNetworkParameters(emptyList(), minimumPlatformVersion = 2))
// when
val latest = networkMapStorage.getLatestNetworkParameters()
@ -89,18 +90,18 @@ class DBNetworkMapStorageTest : TestBase() {
fun `getCurrentNetworkParameters returns current network map parameters`() {
// given
// Create network parameters
val networkMapParametersHash = networkMapStorage.saveNetworkParameters(createNetworkParameters(1))
val networkParametersHash = networkMapStorage.saveNetworkParameters(testNetworkParameters(emptyList()))
// Create empty network map
// Sign network map making it current network map
val networkMap = NetworkMap(emptyList(), networkMapParametersHash)
val networkMap = NetworkMap(emptyList(), networkParametersHash)
val serializedNetworkMap = networkMap.serialize()
val signatureData = intermediateCAKey.sign(serializedNetworkMap).withCert(intermediateCACert.cert)
val signatureData = intermediateCaKeyPair.sign(serializedNetworkMap).withCert(intermediateCaCert.cert)
val signedNetworkMap = SignedNetworkMap(serializedNetworkMap, signatureData)
networkMapStorage.saveNetworkMap(signedNetworkMap)
// Create new network parameters
networkMapStorage.saveNetworkParameters(createNetworkParameters(2))
networkMapStorage.saveNetworkParameters(testNetworkParameters(emptyList(), minimumPlatformVersion = 2))
// when
val result = networkMapStorage.getCurrentNetworkParameters()
@ -109,6 +110,16 @@ class DBNetworkMapStorageTest : TestBase() {
assertEquals(1, result?.minimumPlatformVersion)
}
// This test will probably won't be needed when we remove the explicit use of LocalSigner
@Test
fun `getSignedNetworkParameters uses the local signer to return a signed object`() {
val netParams = testNetworkParameters(emptyList())
val netParamsHash = networkMapStorage.saveNetworkParameters(netParams)
val signedNetParams = networkMapStorage.getSignedNetworkParameters(netParamsHash)
assertThat(signedNetParams?.verified()).isEqualTo(netParams)
assertThat(signedNetParams?.sig?.by).isEqualTo(intermediateCaKeyPair.public)
}
@Test
fun `getValidNodeInfoHashes returns only valid and signed node info hashes`() {
// given
@ -121,10 +132,10 @@ class DBNetworkMapStorageTest : TestBase() {
val nodeInfoHashB = nodeInfoStorage.putNodeInfo(signedNodeInfoB)
// Create network parameters
val networkParametersHash = networkMapStorage.saveNetworkParameters(createNetworkParameters())
val networkParametersHash = networkMapStorage.saveNetworkParameters(testNetworkParameters(emptyList()))
val networkMap = NetworkMap(listOf(nodeInfoHashA), networkParametersHash)
val serializedNetworkMap = networkMap.serialize()
val signatureData = intermediateCAKey.sign(serializedNetworkMap).withCert(intermediateCACert.cert)
val signatureData = intermediateCaKeyPair.sign(serializedNetworkMap).withCert(intermediateCaCert.cert)
val signedNetworkMap = SignedNetworkMap(serializedNetworkMap, signatureData)
// Sign network map

View File

@ -15,6 +15,7 @@ import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.network.NetworkMap
import net.corda.nodeapi.internal.network.SignedNetworkMap
import net.corda.testing.common.internal.testNetworkParameters
import org.junit.Before
import org.junit.Test
import kotlin.test.assertEquals
@ -39,12 +40,12 @@ class NetworkMapSignerTest : TestBase() {
fun `signNetworkMap builds and signs network map`() {
// given
val signedNodeInfoHashes = listOf(SecureHash.randomSHA256(), SecureHash.randomSHA256())
val networkMapParameters = createNetworkParameters()
val networkParameters = testNetworkParameters(emptyList())
val serializedNetworkMap = NetworkMap(signedNodeInfoHashes, SecureHash.randomSHA256()).serialize()
whenever(networkMapStorage.getCurrentNetworkMap())
.thenReturn(SignedNetworkMap(serializedNetworkMap, intermediateCAKey.sign(serializedNetworkMap).withCert(intermediateCACert.cert)))
whenever(networkMapStorage.getNodeInfoHashes(any())).thenReturn(signedNodeInfoHashes)
whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(networkMapParameters)
whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(networkParameters)
whenever(signer.sign(any())).then {
intermediateCAKey.sign(it.arguments.first() as ByteArray).withCert(intermediateCACert.cert)
}
@ -59,7 +60,7 @@ class NetworkMapSignerTest : TestBase() {
argumentCaptor<SignedNetworkMap>().apply {
verify(networkMapStorage).saveNetworkMap(capture())
val networkMap = firstValue.verified(rootCACert.cert)
assertEquals(networkMapParameters.serialize().hash, networkMap.networkParameterHash)
assertEquals(networkParameters.serialize().hash, networkMap.networkParameterHash)
assertEquals(signedNodeInfoHashes.size, networkMap.nodeInfoHashes.size)
assertTrue(networkMap.nodeInfoHashes.containsAll(signedNodeInfoHashes))
}
@ -68,14 +69,14 @@ class NetworkMapSignerTest : TestBase() {
@Test
fun `signNetworkMap does NOT create a new network map if there are no changes`() {
// given
val networkMapParameters = createNetworkParameters()
val networkMapParametersHash = networkMapParameters.serialize().bytes.sha256()
val networkParameters = testNetworkParameters(emptyList())
val networkMapParametersHash = networkParameters.serialize().bytes.sha256()
val networkMap = NetworkMap(emptyList(), networkMapParametersHash)
val serializedNetworkMap = networkMap.serialize()
val signedNetworkMap = SignedNetworkMap(serializedNetworkMap, intermediateCAKey.sign(serializedNetworkMap).withCert(intermediateCACert.cert))
whenever(networkMapStorage.getCurrentNetworkMap()).thenReturn(signedNetworkMap)
whenever(networkMapStorage.getNodeInfoHashes(any())).thenReturn(emptyList())
whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(networkMapParameters)
whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(networkParameters)
// when
networkMapSigner.signNetworkMap()
@ -88,10 +89,10 @@ class NetworkMapSignerTest : TestBase() {
@Test
fun `signNetworkMap creates a new network map if there is no current network map`() {
// given
val networkMapParameters = createNetworkParameters()
val networkParameters = testNetworkParameters(emptyList())
whenever(networkMapStorage.getCurrentNetworkMap()).thenReturn(null)
whenever(networkMapStorage.getNodeInfoHashes(any())).thenReturn(emptyList())
whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(networkMapParameters)
whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(networkParameters)
whenever(signer.sign(any())).then {
intermediateCAKey.sign(it.arguments.first() as ByteArray).withCert(intermediateCACert.cert)
}
@ -105,7 +106,7 @@ class NetworkMapSignerTest : TestBase() {
argumentCaptor<SignedNetworkMap>().apply {
verify(networkMapStorage).saveNetworkMap(capture())
val networkMap = firstValue.verified(rootCACert.cert)
assertEquals(networkMapParameters.serialize().hash, networkMap.networkParameterHash)
assertEquals(networkParameters.serialize().hash, networkMap.networkParameterHash)
}
}
}

View File

@ -8,11 +8,12 @@ import com.r3.corda.networkmanage.common.persistence.NodeInfoStorage
import com.r3.corda.networkmanage.common.utils.withCert
import com.r3.corda.networkmanage.doorman.webservice.NodeInfoWebService
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import net.corda.core.crypto.SecureHash.Companion.randomSHA256
import net.corda.core.crypto.SignedData
import net.corda.core.crypto.sign
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.cert
import net.corda.core.internal.openHttpConnection
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.utilities.NetworkHostAndPort
@ -21,97 +22,121 @@ import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.network.NetworkMap
import net.corda.nodeapi.internal.network.NetworkParameters
import net.corda.nodeapi.internal.network.SignedNetworkMap
import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.internal.createNodeInfoAndSigned
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.bouncycastle.asn1.x500.X500Name
import org.junit.Rule
import org.junit.Test
import java.io.FileNotFoundException
import java.io.IOException
import java.net.HttpURLConnection
import java.net.URL
import javax.ws.rs.core.MediaType
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class NodeInfoWebServiceTest {
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule(true)
private val testNetwotkMapConfig = NetworkMapConfig(10.seconds.toMillis(), 10.seconds.toMillis())
private val rootCaKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
private val rootCaCert = X509Utilities.createSelfSignedCACertificate(CordaX500Name("Corda Node Root CA", "R3 LTD", "London", "GB"), rootCaKeyPair)
private val intermediateCaKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
private val intermediateCaCert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCaCert, rootCaKeyPair, X500Name("CN=Corda Node Intermediate CA,L=London"), intermediateCaKeyPair.public)
private val testNetworkMapConfig = NetworkMapConfig(10.seconds.toMillis(), 10.seconds.toMillis())
@Test
fun `submit nodeInfo`() {
// Create node info.
val (_, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name("Test", "London", "GB"))
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(mock(), mock(), testNetwotkMapConfig)).use {
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(mock(), mock(), testNetworkMapConfig)).use {
it.start()
val registerURL = URL("http://${it.hostAndPort}/${NodeInfoWebService.NETWORK_MAP_PATH}/publish")
val nodeInfoAndSignature = signedNodeInfo.serialize().bytes
// Post node info and signature to doorman, this should pass without any exception.
doPost(registerURL, nodeInfoAndSignature)
it.doPost("publish", nodeInfoAndSignature)
}
}
@Test
fun `get network map`() {
val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(locality = "London", organisation = "R3 LTD", country = "GB", commonName = "Corda Node Root CA"), rootCAKey)
val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey, X500Name("CN=Corda Node Intermediate CA,L=London"), intermediateCAKey.public)
val networkMap = NetworkMap(listOf(SecureHash.randomSHA256(), SecureHash.randomSHA256()), SecureHash.randomSHA256())
val networkMap = NetworkMap(listOf(randomSHA256(), randomSHA256()), randomSHA256())
val serializedNetworkMap = networkMap.serialize()
val signedNetworkMap = SignedNetworkMap(serializedNetworkMap, intermediateCaKeyPair.sign(serializedNetworkMap).withCert(intermediateCaCert.cert))
val networkMapStorage: NetworkMapStorage = mock {
on { getCurrentNetworkMap() }.thenReturn(SignedNetworkMap(serializedNetworkMap, intermediateCAKey.sign(serializedNetworkMap).withCert(intermediateCACert.cert)))
on { getCurrentNetworkMap() }.thenReturn(signedNetworkMap)
}
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(mock(), networkMapStorage, testNetwotkMapConfig)).use {
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(mock(), networkMapStorage, testNetworkMapConfig)).use {
it.start()
val conn = URL("http://${it.hostAndPort}/${NodeInfoWebService.NETWORK_MAP_PATH}").openConnection() as HttpURLConnection
val signedNetworkMap = conn.inputStream.readBytes().deserialize<SignedNetworkMap>()
val signedNetworkMapResponse = it.doGet<SignedNetworkMap>("")
verify(networkMapStorage, times(1)).getCurrentNetworkMap()
assertEquals(signedNetworkMap.verified(rootCACert.cert), networkMap)
assertEquals(signedNetworkMapResponse.verified(rootCaCert.cert), networkMap)
}
}
@Test
fun `get node info`() {
val (nodeInfo, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name("Test", "London", "GB"))
val nodeInfoHash = nodeInfo.serialize().sha256()
val nodeInfoHash = nodeInfo.serialize().hash
val nodeInfoStorage: NodeInfoStorage = mock {
on { getNodeInfo(nodeInfoHash) }.thenReturn(signedNodeInfo)
}
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(nodeInfoStorage, mock(), testNetwotkMapConfig)).use {
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(nodeInfoStorage, mock(), testNetworkMapConfig)).use {
it.start()
val nodeInfoURL = URL("http://${it.hostAndPort}/${NodeInfoWebService.NETWORK_MAP_PATH}/node-info/$nodeInfoHash")
val conn = nodeInfoURL.openConnection()
val nodeInfoResponse = conn.inputStream.readBytes().deserialize<SignedNodeInfo>()
val nodeInfoResponse = it.doGet<SignedNodeInfo>("node-info/$nodeInfoHash")
verify(nodeInfoStorage, times(1)).getNodeInfo(nodeInfoHash)
assertEquals(nodeInfo, nodeInfoResponse.verified())
assertFailsWith(FileNotFoundException::class) {
URL("http://${it.hostAndPort}/${NodeInfoWebService.NETWORK_MAP_PATH}/${SecureHash.randomSHA256()}").openConnection().getInputStream()
assertThatExceptionOfType(FileNotFoundException::class.java).isThrownBy {
it.doGet<SignedNodeInfo>("node-info/${randomSHA256()}")
}
}
}
private fun doPost(url: URL, payload: ByteArray) {
val conn = url.openConnection() as HttpURLConnection
conn.doOutput = true
conn.requestMethod = "POST"
conn.setRequestProperty("Content-Type", MediaType.APPLICATION_OCTET_STREAM)
conn.outputStream.write(payload)
@Test
fun `get network parameters`() {
val netParams = testNetworkParameters(emptyList())
val serializedNetParams = netParams.serialize()
val signedNetParams = SignedData(serializedNetParams, intermediateCaKeyPair.sign(serializedNetParams))
val netParamsHash = serializedNetParams.hash
return try {
conn.inputStream.bufferedReader().use { it.readLine() }
} catch (e: IOException) {
throw IOException(conn.errorStream.bufferedReader().readLine(), e)
val networkMapStorage: NetworkMapStorage = mock {
on { getSignedNetworkParameters(netParamsHash) }.thenReturn(signedNetParams)
}
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(mock(), networkMapStorage, testNetworkMapConfig)).use {
it.start()
val netParamsResponse = it.doGet<SignedData<NetworkParameters>>("network-parameter/$netParamsHash")
verify(networkMapStorage, times(1)).getSignedNetworkParameters(netParamsHash)
assertThat(netParamsResponse.verified()).isEqualTo(netParams)
assertThat(netParamsResponse.sig.by).isEqualTo(intermediateCaKeyPair.public)
assertThatExceptionOfType(FileNotFoundException::class.java).isThrownBy {
it.doGet<SignedData<NetworkParameters>>("network-parameter/${randomSHA256()}")
}
}
}
private fun NetworkManagementWebServer.doPost(path: String, payload: ByteArray) {
val url = URL("http://$hostAndPort/network-map/$path")
url.openHttpConnection().apply {
doOutput = true
requestMethod = "POST"
setRequestProperty("Content-Type", MediaType.APPLICATION_OCTET_STREAM)
outputStream.write(payload)
inputStream.close() // This will give us a nice IOException if the response isn't HTTP 200
}
}
private inline fun <reified T : Any> NetworkManagementWebServer.doGet(path: String): T {
val url = URL("http://$hostAndPort/network-map/$path")
return url.openHttpConnection().inputStream.use { it.readBytes().deserialize() }
}
}