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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 203 additions and 161 deletions

View File

@ -1,6 +1,7 @@
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.nodeapi.internal.network.NetworkParameters import net.corda.nodeapi.internal.network.NetworkParameters
import net.corda.nodeapi.internal.network.SignedNetworkMap import net.corda.nodeapi.internal.network.SignedNetworkMap
@ -30,10 +31,10 @@ interface NetworkMapStorage {
fun saveNetworkMap(signedNetworkMap: SignedNetworkMap) fun saveNetworkMap(signedNetworkMap: SignedNetworkMap)
/** /**
* Retrieve network parameters by their hash. * Return the signed network parameters object which matches the given hash. The hash is that of the underlying
* @return network parameters corresponding to the given hash or null if it does not exist * [NetworkParameters] object and not the `SignedData<NetworkParameters>` object that's returned.
*/ */
fun getNetworkParameters(parameterHash: SecureHash): NetworkParameters? fun getSignedNetworkParameters(hash: SecureHash): SignedData<NetworkParameters>?
/** /**
* Retrieve network map parameters. * Retrieve network map parameters.

View File

@ -1,7 +1,10 @@
package com.r3.corda.networkmanage.common.persistence package com.r3.corda.networkmanage.common.persistence
import com.r3.corda.networkmanage.common.persistence.entity.* import com.r3.corda.networkmanage.common.persistence.entity.*
import com.r3.corda.networkmanage.doorman.signer.LocalSigner
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.SignedData
import net.corda.core.crypto.sha256 import net.corda.core.crypto.sha256
import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
@ -14,19 +17,22 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence
/** /**
* Database implementation of the [NetworkMapStorage] interface * Database implementation of the [NetworkMapStorage] interface
*/ */
class PersistentNetworkMapStorage(private val database: CordaPersistence) : NetworkMapStorage { class PersistentNetworkMapStorage(private val database: CordaPersistence, private val localSigner: LocalSigner?) : NetworkMapStorage {
override fun getCurrentNetworkMap(): SignedNetworkMap? = database.transaction { override fun getCurrentNetworkMap(): SignedNetworkMap? {
val networkMapEntity = getCurrentNetworkMapEntity() return database.transaction {
networkMapEntity?.let { getCurrentNetworkMapEntity()?.let {
val signatureAndCertPath = it.signatureAndCertificate() val signatureAndCertPath = it.signatureAndCertificate()
SignedNetworkMap(SerializedBytes(it.networkMap), signatureAndCertPath) SignedNetworkMap(SerializedBytes(it.networkMap), signatureAndCertPath)
}
} }
} }
override fun getCurrentNetworkParameters(): NetworkParameters? = database.transaction { override fun getCurrentNetworkParameters(): NetworkParameters? {
getCurrentNetworkMapEntity()?.let { return database.transaction {
val parameterHash = it.networkMap.deserialize<NetworkMap>().networkParameterHash getCurrentNetworkMapEntity()?.let {
getNetworkParameters(parameterHash) val netParamsHash = it.networkMap.deserialize<NetworkMap>().networkParameterHash
getNetworkParametersEntity(netParamsHash.toString())?.networkParameters()
}
} }
} }
@ -41,61 +47,75 @@ class PersistentNetworkMapStorage(private val database: CordaPersistence) : Netw
} }
} }
override fun getNetworkParameters(parameterHash: SecureHash): NetworkParameters? { // TODO The signing cannot occur here as it won't work with an HSM. The signed network parameters needs to be persisted
return getNetworkParametersEntity(parameterHash.toString())?.networkParameters() // into the database.
override fun getSignedNetworkParameters(hash: SecureHash): SignedData<NetworkParameters>? {
val netParamsBytes = getNetworkParametersEntity(hash.toString())?.parametersBytes ?: return null
val sigWithCert = localSigner!!.sign(netParamsBytes)
return SignedData(SerializedBytes(netParamsBytes), DigitalSignature.WithKey(sigWithCert.by.publicKey, sigWithCert.signatureBytes))
} }
override fun getNodeInfoHashes(certificateStatus: CertificateStatus): List<SecureHash> = database.transaction { override fun getNodeInfoHashes(certificateStatus: CertificateStatus): List<SecureHash> {
val builder = session.criteriaBuilder return database.transaction {
val query = builder.createQuery(String::class.java).run { val builder = session.criteriaBuilder
from(NodeInfoEntity::class.java).run { val query = builder.createQuery(String::class.java).run {
select(get<String>(NodeInfoEntity::nodeInfoHash.name)) from(NodeInfoEntity::class.java).run {
.where(builder.equal(get<CertificateSigningRequestEntity>(NodeInfoEntity::certificateSigningRequest.name) select(get<String>(NodeInfoEntity::nodeInfoHash.name))
.get<CertificateDataEntity>(CertificateSigningRequestEntity::certificateData.name) .where(builder.equal(get<CertificateSigningRequestEntity>(NodeInfoEntity::certificateSigningRequest.name)
.get<CertificateStatus>(CertificateDataEntity::certificateStatus.name), certificateStatus)) .get<CertificateDataEntity>(CertificateSigningRequestEntity::certificateData.name)
.get<CertificateStatus>(CertificateDataEntity::certificateStatus.name), certificateStatus))
}
} }
session.createQuery(query).resultList.map { SecureHash.parse(it) }
} }
session.createQuery(query).resultList.map { SecureHash.parse(it) }
} }
override fun saveNetworkParameters(networkParameters: NetworkParameters): SecureHash = database.transaction { override fun saveNetworkParameters(networkParameters: NetworkParameters): SecureHash {
val bytes = networkParameters.serialize().bytes return database.transaction {
val hash = bytes.sha256() val bytes = networkParameters.serialize().bytes
session.save(NetworkParametersEntity( val hash = bytes.sha256()
parametersBytes = bytes, session.save(NetworkParametersEntity(
parametersHash = hash.toString() parametersBytes = bytes,
)) parametersHash = hash.toString()
hash ))
hash
}
} }
override fun getLatestNetworkParameters(): NetworkParameters = getLatestNetworkParametersEntity().networkParameters() override fun getLatestNetworkParameters(): NetworkParameters = getLatestNetworkParametersEntity().networkParameters()
private fun getLatestNetworkParametersEntity(): NetworkParametersEntity = database.transaction { private fun getLatestNetworkParametersEntity(): NetworkParametersEntity {
val builder = session.criteriaBuilder return database.transaction {
val query = builder.createQuery(NetworkParametersEntity::class.java).run { val builder = session.criteriaBuilder
from(NetworkParametersEntity::class.java).run { val query = builder.createQuery(NetworkParametersEntity::class.java).run {
orderBy(builder.desc(get<String>(NetworkParametersEntity::version.name))) from(NetworkParametersEntity::class.java).run {
orderBy(builder.desc(get<String>(NetworkParametersEntity::version.name)))
}
} }
// We just want the last signed entry
session.createQuery(query).resultList.first()
} }
// We just want the last signed entry
session.createQuery(query).resultList.first()
} }
private fun getCurrentNetworkMapEntity(): NetworkMapEntity? = database.transaction { private fun getCurrentNetworkMapEntity(): NetworkMapEntity? {
val builder = session.criteriaBuilder return database.transaction {
val query = builder.createQuery(NetworkMapEntity::class.java).run { val builder = session.criteriaBuilder
from(NetworkMapEntity::class.java).run { val query = builder.createQuery(NetworkMapEntity::class.java).run {
where(builder.isNotNull(get<ByteArray?>(NetworkMapEntity::signature.name))) from(NetworkMapEntity::class.java).run {
orderBy(builder.desc(get<String>(NetworkMapEntity::version.name))) where(builder.isNotNull(get<ByteArray?>(NetworkMapEntity::signature.name)))
orderBy(builder.desc(get<String>(NetworkMapEntity::version.name)))
}
} }
// We just want the last signed entry
session.createQuery(query).resultList.firstOrNull()
} }
// We just want the last signed entry
session.createQuery(query).resultList.firstOrNull()
} }
private fun getNetworkParametersEntity(parameterHash: String): NetworkParametersEntity? = database.transaction { private fun getNetworkParametersEntity(parameterHash: String): NetworkParametersEntity? {
singleRequestWhere(NetworkParametersEntity::class.java) { builder, path -> return database.transaction {
builder.equal(path.get<String>(NetworkParametersEntity::parametersHash.name), parameterHash) singleRequestWhere(NetworkParametersEntity::class.java) { builder, path ->
builder.equal(path.get<String>(NetworkParametersEntity::parametersHash.name), parameterHash)
}
} }
} }
} }

View File

@ -18,10 +18,9 @@ import java.security.cert.CertPath
*/ */
class PersistentNodeInfoStorage(private val database: CordaPersistence) : NodeInfoStorage { class PersistentNodeInfoStorage(private val database: CordaPersistence) : NodeInfoStorage {
override fun putNodeInfo(signedNodeInfo: SignedNodeInfo): SecureHash { override fun putNodeInfo(signedNodeInfo: SignedNodeInfo): SecureHash {
val nodeInfo = signedNodeInfo.verified()
val nodeCaCert = nodeInfo.legalIdentitiesAndCerts[0].certPath.certificates.find { CertRole.extract(it) == CertRole.NODE_CA }
return database.transaction(TransactionIsolationLevel.SERIALIZABLE) { return database.transaction(TransactionIsolationLevel.SERIALIZABLE) {
val nodeInfo = signedNodeInfo.verified()
val nodeCaCert = nodeInfo.legalIdentitiesAndCerts[0].certPath.certificates.find { CertRole.extract(it) == CertRole.NODE_CA }
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

@ -49,7 +49,7 @@ class NetworkManagementServer : Closeable {
} }
private fun getNetworkMapService(config: NetworkMapConfig, database: CordaPersistence, signer: LocalSigner?, updateNetworkParameters: NetworkParameters?): NodeInfoWebService { private fun getNetworkMapService(config: NetworkMapConfig, database: CordaPersistence, signer: LocalSigner?, updateNetworkParameters: NetworkParameters?): NodeInfoWebService {
val networkMapStorage = PersistentNetworkMapStorage(database) val networkMapStorage = PersistentNetworkMapStorage(database, signer)
val nodeInfoStorage = PersistentNodeInfoStorage(database) val nodeInfoStorage = PersistentNodeInfoStorage(database)
updateNetworkParameters?.let { updateNetworkParameters?.let {

View File

@ -8,8 +8,6 @@ import com.r3.corda.networkmanage.common.persistence.NodeInfoStorage
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.crypto.SignedData
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.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.SignedNodeInfo
@ -37,9 +35,7 @@ class NodeInfoWebService(private val nodeInfoStorage: NodeInfoStorage,
private val networkMapCache: LoadingCache<Boolean, SignedNetworkMap?> = CacheBuilder.newBuilder() private val networkMapCache: LoadingCache<Boolean, SignedNetworkMap?> = CacheBuilder.newBuilder()
.expireAfterWrite(config.cacheTimeout, TimeUnit.MILLISECONDS) .expireAfterWrite(config.cacheTimeout, TimeUnit.MILLISECONDS)
.build(CacheLoader.from { _ -> .build(CacheLoader.from { _ -> networkMapStorage.getCurrentNetworkMap() })
networkMapStorage.getCurrentNetworkMap()
})
@POST @POST
@Path("publish") @Path("publish")
@ -54,31 +50,27 @@ class NodeInfoWebService(private val nodeInfoStorage: NodeInfoStorage,
// Catch exceptions thrown by signature verification. // Catch exceptions thrown by signature verification.
when (e) { when (e) {
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 { fun getNetworkMap(): Response = createResponse(networkMapCache.get(true), addCacheTimeout = true)
val currentNetworkMap = networkMapCache.get(true)
return if (currentNetworkMap != null) {
Response.ok(currentNetworkMap.serialize().bytes).header("Cache-Control", "max-age=${Duration.ofMillis(config.cacheTimeout).seconds}")
} else {
status(Response.Status.NOT_FOUND)
}.build()
}
@GET @GET
@Path("node-info/{nodeInfoHash}") @Path("node-info/{nodeInfoHash}")
fun getNodeInfo(@PathParam("nodeInfoHash") nodeInfoHash: String): Response { fun getNodeInfo(@PathParam("nodeInfoHash") nodeInfoHash: String): Response {
val nodeInfo = nodeInfoStorage.getNodeInfo(SecureHash.parse(nodeInfoHash)) val signedNodeInfo = nodeInfoStorage.getNodeInfo(SecureHash.parse(nodeInfoHash))
return if (nodeInfo != null) { return createResponse(signedNodeInfo)
ok(nodeInfo.serialize().bytes) }
} else {
status(Response.Status.NOT_FOUND) @GET
}.build() @Path("network-parameter/{netParamsHash}") // TODO Fix path to be /network-parameters
fun getNetworkParameters(@PathParam("netParamsHash") netParamsHash: String): Response {
val signedNetParams = networkMapStorage.getSignedNetworkParameters(SecureHash.parse(netParamsHash))
return createResponse(signedNetParams)
} }
@GET @GET
@ -87,4 +79,16 @@ class NodeInfoWebService(private val nodeInfoStorage: NodeInfoStorage,
// TODO: Verify this returns IP correctly. // TODO: Verify this returns IP correctly.
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 createResponse(payload: Any?, addCacheTimeout: Boolean = false): Response {
return if (payload != null) {
val ok = Response.ok(payload.serialize().bytes)
if (addCacheTimeout) {
ok.header("Cache-Control", "max-age=${Duration.ofMillis(config.cacheTimeout).seconds}")
}
ok
} else {
status(Response.Status.NOT_FOUND)
}.build()
}
} }

View File

@ -46,7 +46,8 @@ fun run(parameters: Parameters) {
checkNotNull(dataSourceProperties) checkNotNull(dataSourceProperties)
val database = configureDatabase(dataSourceProperties, databaseConfig) val database = configureDatabase(dataSourceProperties, databaseConfig)
val csrStorage = DBSignedCertificateRequestStorage(database) val csrStorage = DBSignedCertificateRequestStorage(database)
val networkMapStorage = PersistentNetworkMapStorage(database) // TODO Remove the dependency for a local signer to make signing of network parameters work with an HSM
val networkMapStorage = PersistentNetworkMapStorage(database, localSigner = null)
val hsmNetworkMapSigningThread = HsmNetworkMapSigner( val hsmNetworkMapSigningThread = HsmNetworkMapSigner(
networkMapStorage, networkMapStorage,
networkMapCertificateName, networkMapCertificateName,

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.CertificateStatus
import com.r3.corda.networkmanage.common.persistence.RequestStatus import com.r3.corda.networkmanage.common.persistence.RequestStatus
import net.corda.core.crypto.SecureHash 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 net.corda.testing.SerializationEnvironmentRule
import org.bouncycastle.pkcs.PKCS10CertificationRequest import org.bouncycastle.pkcs.PKCS10CertificationRequest
import org.junit.Rule import org.junit.Rule
import java.security.cert.CertPath import java.security.cert.CertPath
import java.time.Instant
abstract class TestBase { abstract class TestBase {
@Rule @Rule
@ -48,21 +45,4 @@ abstract class TestBase {
certPath = certPath 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.TestBase
import com.r3.corda.networkmanage.common.utils.withCert 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.Crypto
import net.corda.core.crypto.sign import net.corda.core.crypto.sign
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
@ -23,21 +24,21 @@ import org.junit.Before
import org.junit.Test import org.junit.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
class DBNetworkMapStorageTest : TestBase() { class PersistentNetworkMapStorageTest : TestBase() {
private lateinit var networkMapStorage: NetworkMapStorage
private lateinit var requestStorage: CertificationRequestStorage
private lateinit var nodeInfoStorage: NodeInfoStorage
private lateinit var persistence: CordaPersistence 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 rootCaKeyPair = 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 rootCaCert = X509Utilities.createSelfSignedCACertificate(CordaX500Name("Corda Node Root CA", "R3 LTD", "London", "GB"), rootCaKeyPair)
private val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) private val intermediateCaKeyPair = 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 intermediateCaCert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCaCert, rootCaKeyPair, CordaX500Name("Corda Node Intermediate CA", "R3 LTD", "London", "GB"), intermediateCaKeyPair.public)
@Before @Before
fun startDb() { fun startDb() {
persistence = configureDatabase(makeTestDataSourceProperties()) persistence = configureDatabase(makeTestDataSourceProperties())
networkMapStorage = PersistentNetworkMapStorage(persistence) networkMapStorage = PersistentNetworkMapStorage(persistence, LocalSigner(intermediateCaKeyPair, arrayOf(intermediateCaCert.cert, rootCaCert.cert)))
nodeInfoStorage = PersistentNodeInfoStorage(persistence) nodeInfoStorage = PersistentNodeInfoStorage(persistence)
requestStorage = PersistentCertificateRequestStorage(persistence) requestStorage = PersistentCertificateRequestStorage(persistence)
} }
@ -59,7 +60,7 @@ class DBNetworkMapStorageTest : TestBase() {
val networkMap = NetworkMap(listOf(nodeInfoHash), networkParametersHash) val networkMap = NetworkMap(listOf(nodeInfoHash), networkParametersHash)
val serializedNetworkMap = networkMap.serialize() 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) val signedNetworkMap = SignedNetworkMap(serializedNetworkMap, signatureData)
// when // when
@ -69,14 +70,14 @@ class DBNetworkMapStorageTest : TestBase() {
val persistedSignedNetworkMap = networkMapStorage.getCurrentNetworkMap() val persistedSignedNetworkMap = networkMapStorage.getCurrentNetworkMap()
assertEquals(signedNetworkMap.signature, persistedSignedNetworkMap?.signature) assertEquals(signedNetworkMap.signature, persistedSignedNetworkMap?.signature)
assertEquals(signedNetworkMap.verified(rootCACert.cert), persistedSignedNetworkMap?.verified(rootCACert.cert)) assertEquals(signedNetworkMap.verified(rootCaCert.cert), persistedSignedNetworkMap?.verified(rootCaCert.cert))
} }
@Test @Test
fun `getLatestNetworkParameters returns last inserted`() { fun `getLatestNetworkParameters returns last inserted`() {
// given // given
networkMapStorage.saveNetworkParameters(createNetworkParameters(minimumPlatformVersion = 1)) networkMapStorage.saveNetworkParameters(testNetworkParameters(emptyList(), minimumPlatformVersion = 1))
networkMapStorage.saveNetworkParameters(createNetworkParameters(minimumPlatformVersion = 2)) networkMapStorage.saveNetworkParameters(testNetworkParameters(emptyList(), minimumPlatformVersion = 2))
// when // when
val latest = networkMapStorage.getLatestNetworkParameters() val latest = networkMapStorage.getLatestNetworkParameters()
@ -89,18 +90,18 @@ class DBNetworkMapStorageTest : TestBase() {
fun `getCurrentNetworkParameters returns current network map parameters`() { fun `getCurrentNetworkParameters returns current network map parameters`() {
// given // given
// Create network parameters // Create network parameters
val networkMapParametersHash = networkMapStorage.saveNetworkParameters(createNetworkParameters(1)) val networkParametersHash = networkMapStorage.saveNetworkParameters(testNetworkParameters(emptyList()))
// Create empty network map // Create empty network map
// Sign network map making it current 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 serializedNetworkMap = networkMap.serialize()
val signatureData = intermediateCAKey.sign(serializedNetworkMap).withCert(intermediateCACert.cert) val signatureData = intermediateCaKeyPair.sign(serializedNetworkMap).withCert(intermediateCaCert.cert)
val signedNetworkMap = SignedNetworkMap(serializedNetworkMap, signatureData) val signedNetworkMap = SignedNetworkMap(serializedNetworkMap, signatureData)
networkMapStorage.saveNetworkMap(signedNetworkMap) networkMapStorage.saveNetworkMap(signedNetworkMap)
// Create new network parameters // Create new network parameters
networkMapStorage.saveNetworkParameters(createNetworkParameters(2)) networkMapStorage.saveNetworkParameters(testNetworkParameters(emptyList(), minimumPlatformVersion = 2))
// when // when
val result = networkMapStorage.getCurrentNetworkParameters() val result = networkMapStorage.getCurrentNetworkParameters()
@ -109,6 +110,16 @@ class DBNetworkMapStorageTest : TestBase() {
assertEquals(1, result?.minimumPlatformVersion) 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 @Test
fun `getValidNodeInfoHashes returns only valid and signed node info hashes`() { fun `getValidNodeInfoHashes returns only valid and signed node info hashes`() {
// given // given
@ -121,10 +132,10 @@ class DBNetworkMapStorageTest : TestBase() {
val nodeInfoHashB = nodeInfoStorage.putNodeInfo(signedNodeInfoB) val nodeInfoHashB = nodeInfoStorage.putNodeInfo(signedNodeInfoB)
// Create network parameters // Create network parameters
val networkParametersHash = networkMapStorage.saveNetworkParameters(createNetworkParameters()) val networkParametersHash = networkMapStorage.saveNetworkParameters(testNetworkParameters(emptyList()))
val networkMap = NetworkMap(listOf(nodeInfoHashA), networkParametersHash) val networkMap = NetworkMap(listOf(nodeInfoHashA), networkParametersHash)
val serializedNetworkMap = networkMap.serialize() 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) val signedNetworkMap = SignedNetworkMap(serializedNetworkMap, signatureData)
// Sign network map // 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.crypto.X509Utilities
import net.corda.nodeapi.internal.network.NetworkMap import net.corda.nodeapi.internal.network.NetworkMap
import net.corda.nodeapi.internal.network.SignedNetworkMap import net.corda.nodeapi.internal.network.SignedNetworkMap
import net.corda.testing.common.internal.testNetworkParameters
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -39,12 +40,12 @@ class NetworkMapSignerTest : TestBase() {
fun `signNetworkMap builds and signs network map`() { fun `signNetworkMap builds and signs network map`() {
// given // given
val signedNodeInfoHashes = listOf(SecureHash.randomSHA256(), SecureHash.randomSHA256()) val signedNodeInfoHashes = listOf(SecureHash.randomSHA256(), SecureHash.randomSHA256())
val networkMapParameters = createNetworkParameters() val networkParameters = testNetworkParameters(emptyList())
val serializedNetworkMap = NetworkMap(signedNodeInfoHashes, SecureHash.randomSHA256()).serialize() val serializedNetworkMap = NetworkMap(signedNodeInfoHashes, SecureHash.randomSHA256()).serialize()
whenever(networkMapStorage.getCurrentNetworkMap()) whenever(networkMapStorage.getCurrentNetworkMap())
.thenReturn(SignedNetworkMap(serializedNetworkMap, intermediateCAKey.sign(serializedNetworkMap).withCert(intermediateCACert.cert))) .thenReturn(SignedNetworkMap(serializedNetworkMap, intermediateCAKey.sign(serializedNetworkMap).withCert(intermediateCACert.cert)))
whenever(networkMapStorage.getNodeInfoHashes(any())).thenReturn(signedNodeInfoHashes) whenever(networkMapStorage.getNodeInfoHashes(any())).thenReturn(signedNodeInfoHashes)
whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(networkMapParameters) whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(networkParameters)
whenever(signer.sign(any())).then { whenever(signer.sign(any())).then {
intermediateCAKey.sign(it.arguments.first() as ByteArray).withCert(intermediateCACert.cert) intermediateCAKey.sign(it.arguments.first() as ByteArray).withCert(intermediateCACert.cert)
} }
@ -59,7 +60,7 @@ class NetworkMapSignerTest : TestBase() {
argumentCaptor<SignedNetworkMap>().apply { argumentCaptor<SignedNetworkMap>().apply {
verify(networkMapStorage).saveNetworkMap(capture()) verify(networkMapStorage).saveNetworkMap(capture())
val networkMap = firstValue.verified(rootCACert.cert) val networkMap = firstValue.verified(rootCACert.cert)
assertEquals(networkMapParameters.serialize().hash, networkMap.networkParameterHash) assertEquals(networkParameters.serialize().hash, networkMap.networkParameterHash)
assertEquals(signedNodeInfoHashes.size, networkMap.nodeInfoHashes.size) assertEquals(signedNodeInfoHashes.size, networkMap.nodeInfoHashes.size)
assertTrue(networkMap.nodeInfoHashes.containsAll(signedNodeInfoHashes)) assertTrue(networkMap.nodeInfoHashes.containsAll(signedNodeInfoHashes))
} }
@ -68,14 +69,14 @@ class NetworkMapSignerTest : TestBase() {
@Test @Test
fun `signNetworkMap does NOT create a new network map if there are no changes`() { fun `signNetworkMap does NOT create a new network map if there are no changes`() {
// given // given
val networkMapParameters = createNetworkParameters() val networkParameters = testNetworkParameters(emptyList())
val networkMapParametersHash = networkMapParameters.serialize().bytes.sha256() val networkMapParametersHash = networkParameters.serialize().bytes.sha256()
val networkMap = NetworkMap(emptyList(), networkMapParametersHash) val networkMap = NetworkMap(emptyList(), networkMapParametersHash)
val serializedNetworkMap = networkMap.serialize() val serializedNetworkMap = networkMap.serialize()
val signedNetworkMap = SignedNetworkMap(serializedNetworkMap, intermediateCAKey.sign(serializedNetworkMap).withCert(intermediateCACert.cert)) val signedNetworkMap = SignedNetworkMap(serializedNetworkMap, intermediateCAKey.sign(serializedNetworkMap).withCert(intermediateCACert.cert))
whenever(networkMapStorage.getCurrentNetworkMap()).thenReturn(signedNetworkMap) whenever(networkMapStorage.getCurrentNetworkMap()).thenReturn(signedNetworkMap)
whenever(networkMapStorage.getNodeInfoHashes(any())).thenReturn(emptyList()) whenever(networkMapStorage.getNodeInfoHashes(any())).thenReturn(emptyList())
whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(networkMapParameters) whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(networkParameters)
// when // when
networkMapSigner.signNetworkMap() networkMapSigner.signNetworkMap()
@ -88,10 +89,10 @@ class NetworkMapSignerTest : TestBase() {
@Test @Test
fun `signNetworkMap creates a new network map if there is no current network map`() { fun `signNetworkMap creates a new network map if there is no current network map`() {
// given // given
val networkMapParameters = createNetworkParameters() val networkParameters = testNetworkParameters(emptyList())
whenever(networkMapStorage.getCurrentNetworkMap()).thenReturn(null) whenever(networkMapStorage.getCurrentNetworkMap()).thenReturn(null)
whenever(networkMapStorage.getNodeInfoHashes(any())).thenReturn(emptyList()) whenever(networkMapStorage.getNodeInfoHashes(any())).thenReturn(emptyList())
whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(networkMapParameters) whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(networkParameters)
whenever(signer.sign(any())).then { whenever(signer.sign(any())).then {
intermediateCAKey.sign(it.arguments.first() as ByteArray).withCert(intermediateCACert.cert) intermediateCAKey.sign(it.arguments.first() as ByteArray).withCert(intermediateCACert.cert)
} }
@ -105,7 +106,7 @@ class NetworkMapSignerTest : TestBase() {
argumentCaptor<SignedNetworkMap>().apply { argumentCaptor<SignedNetworkMap>().apply {
verify(networkMapStorage).saveNetworkMap(capture()) verify(networkMapStorage).saveNetworkMap(capture())
val networkMap = firstValue.verified(rootCACert.cert) 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.common.utils.withCert
import com.r3.corda.networkmanage.doorman.webservice.NodeInfoWebService import com.r3.corda.networkmanage.doorman.webservice.NodeInfoWebService
import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash.Companion.randomSHA256
import net.corda.core.crypto.sha256 import net.corda.core.crypto.SignedData
import net.corda.core.crypto.sign import net.corda.core.crypto.sign
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.cert import net.corda.core.internal.cert
import net.corda.core.internal.openHttpConnection
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.NetworkHostAndPort 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.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.network.NetworkMap import net.corda.nodeapi.internal.network.NetworkMap
import net.corda.nodeapi.internal.network.NetworkParameters
import net.corda.nodeapi.internal.network.SignedNetworkMap import net.corda.nodeapi.internal.network.SignedNetworkMap
import net.corda.testing.SerializationEnvironmentRule import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.internal.createNodeInfoAndSigned 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.bouncycastle.asn1.x500.X500Name
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.HttpURLConnection
import java.net.URL import java.net.URL
import javax.ws.rs.core.MediaType import javax.ws.rs.core.MediaType
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class NodeInfoWebServiceTest { class NodeInfoWebServiceTest {
@Rule @Rule
@JvmField @JvmField
val testSerialization = SerializationEnvironmentRule(true) 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 @Test
fun `submit nodeInfo`() { fun `submit nodeInfo`() {
// 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(), testNetwotkMapConfig)).use { NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(mock(), mock(), testNetworkMapConfig)).use {
it.start() it.start()
val registerURL = URL("http://${it.hostAndPort}/${NodeInfoWebService.NETWORK_MAP_PATH}/publish")
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.
doPost(registerURL, nodeInfoAndSignature) it.doPost("publish", nodeInfoAndSignature)
} }
} }
@Test @Test
fun `get network map`() { fun `get network map`() {
val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val networkMap = NetworkMap(listOf(randomSHA256(), randomSHA256()), randomSHA256())
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 serializedNetworkMap = networkMap.serialize() val serializedNetworkMap = networkMap.serialize()
val signedNetworkMap = SignedNetworkMap(serializedNetworkMap, intermediateCaKeyPair.sign(serializedNetworkMap).withCert(intermediateCaCert.cert))
val networkMapStorage: NetworkMapStorage = mock { 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() it.start()
val conn = URL("http://${it.hostAndPort}/${NodeInfoWebService.NETWORK_MAP_PATH}").openConnection() as HttpURLConnection val signedNetworkMapResponse = it.doGet<SignedNetworkMap>("")
val signedNetworkMap = conn.inputStream.readBytes().deserialize<SignedNetworkMap>()
verify(networkMapStorage, times(1)).getCurrentNetworkMap() verify(networkMapStorage, times(1)).getCurrentNetworkMap()
assertEquals(signedNetworkMap.verified(rootCACert.cert), networkMap) assertEquals(signedNetworkMapResponse.verified(rootCaCert.cert), networkMap)
} }
} }
@Test @Test
fun `get node info`() { fun `get node info`() {
val (nodeInfo, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name("Test", "London", "GB")) val (nodeInfo, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name("Test", "London", "GB"))
val nodeInfoHash = nodeInfo.serialize().hash
val nodeInfoHash = nodeInfo.serialize().sha256()
val nodeInfoStorage: NodeInfoStorage = mock { val nodeInfoStorage: NodeInfoStorage = mock {
on { getNodeInfo(nodeInfoHash) }.thenReturn(signedNodeInfo) 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() it.start()
val nodeInfoURL = URL("http://${it.hostAndPort}/${NodeInfoWebService.NETWORK_MAP_PATH}/node-info/$nodeInfoHash") val nodeInfoResponse = it.doGet<SignedNodeInfo>("node-info/$nodeInfoHash")
val conn = nodeInfoURL.openConnection()
val nodeInfoResponse = conn.inputStream.readBytes().deserialize<SignedNodeInfo>()
verify(nodeInfoStorage, times(1)).getNodeInfo(nodeInfoHash) verify(nodeInfoStorage, times(1)).getNodeInfo(nodeInfoHash)
assertEquals(nodeInfo, nodeInfoResponse.verified()) assertEquals(nodeInfo, nodeInfoResponse.verified())
assertFailsWith(FileNotFoundException::class) { assertThatExceptionOfType(FileNotFoundException::class.java).isThrownBy {
URL("http://${it.hostAndPort}/${NodeInfoWebService.NETWORK_MAP_PATH}/${SecureHash.randomSHA256()}").openConnection().getInputStream() it.doGet<SignedNodeInfo>("node-info/${randomSHA256()}")
} }
} }
} }
private fun doPost(url: URL, payload: ByteArray) { @Test
val conn = url.openConnection() as HttpURLConnection fun `get network parameters`() {
conn.doOutput = true val netParams = testNetworkParameters(emptyList())
conn.requestMethod = "POST" val serializedNetParams = netParams.serialize()
conn.setRequestProperty("Content-Type", MediaType.APPLICATION_OCTET_STREAM) val signedNetParams = SignedData(serializedNetParams, intermediateCaKeyPair.sign(serializedNetParams))
conn.outputStream.write(payload) val netParamsHash = serializedNetParams.hash
return try { val networkMapStorage: NetworkMapStorage = mock {
conn.inputStream.bufferedReader().use { it.readLine() } on { getSignedNetworkParameters(netParamsHash) }.thenReturn(signedNetParams)
} catch (e: IOException) { }
throw IOException(conn.errorStream.bufferedReader().readLine(), e)
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() }
}
} }