diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/NetworkMapStorage.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/NetworkMapStorage.kt index b57e37888f..0dea37f7b8 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/NetworkMapStorage.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/NetworkMapStorage.kt @@ -1,6 +1,7 @@ package com.r3.corda.networkmanage.common.persistence 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.SignedNetworkMap @@ -30,10 +31,10 @@ interface NetworkMapStorage { fun saveNetworkMap(signedNetworkMap: SignedNetworkMap) /** - * Retrieve network parameters by their hash. - * @return network parameters corresponding to the given hash or null if it does not exist + * Return the signed network parameters object which matches the given hash. The hash is that of the underlying + * [NetworkParameters] object and not the `SignedData` object that's returned. */ - fun getNetworkParameters(parameterHash: SecureHash): NetworkParameters? + fun getSignedNetworkParameters(hash: SecureHash): SignedData? /** * Retrieve network map parameters. diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNetworkMapStorage.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNetworkMapStorage.kt index 5ddf0a395d..331170a8b1 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNetworkMapStorage.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNetworkMapStorage.kt @@ -1,7 +1,10 @@ package com.r3.corda.networkmanage.common.persistence 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.SignedData import net.corda.core.crypto.sha256 import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.deserialize @@ -14,19 +17,22 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence /** * Database implementation of the [NetworkMapStorage] interface */ -class PersistentNetworkMapStorage(private val database: CordaPersistence) : NetworkMapStorage { - override fun getCurrentNetworkMap(): SignedNetworkMap? = database.transaction { - val networkMapEntity = getCurrentNetworkMapEntity() - networkMapEntity?.let { - val signatureAndCertPath = it.signatureAndCertificate() - SignedNetworkMap(SerializedBytes(it.networkMap), signatureAndCertPath) +class PersistentNetworkMapStorage(private val database: CordaPersistence, private val localSigner: LocalSigner?) : NetworkMapStorage { + override fun getCurrentNetworkMap(): SignedNetworkMap? { + return database.transaction { + getCurrentNetworkMapEntity()?.let { + val signatureAndCertPath = it.signatureAndCertificate() + SignedNetworkMap(SerializedBytes(it.networkMap), signatureAndCertPath) + } } } - override fun getCurrentNetworkParameters(): NetworkParameters? = database.transaction { - getCurrentNetworkMapEntity()?.let { - val parameterHash = it.networkMap.deserialize().networkParameterHash - getNetworkParameters(parameterHash) + override fun getCurrentNetworkParameters(): NetworkParameters? { + return database.transaction { + getCurrentNetworkMapEntity()?.let { + val netParamsHash = it.networkMap.deserialize().networkParameterHash + getNetworkParametersEntity(netParamsHash.toString())?.networkParameters() + } } } @@ -41,61 +47,75 @@ class PersistentNetworkMapStorage(private val database: CordaPersistence) : Netw } } - override fun getNetworkParameters(parameterHash: SecureHash): NetworkParameters? { - return getNetworkParametersEntity(parameterHash.toString())?.networkParameters() + // TODO The signing cannot occur here as it won't work with an HSM. The signed network parameters needs to be persisted + // into the database. + override fun getSignedNetworkParameters(hash: SecureHash): SignedData? { + 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 = database.transaction { - val builder = session.criteriaBuilder - val query = builder.createQuery(String::class.java).run { - from(NodeInfoEntity::class.java).run { - select(get(NodeInfoEntity::nodeInfoHash.name)) - .where(builder.equal(get(NodeInfoEntity::certificateSigningRequest.name) - .get(CertificateSigningRequestEntity::certificateData.name) - .get(CertificateDataEntity::certificateStatus.name), certificateStatus)) + override fun getNodeInfoHashes(certificateStatus: CertificateStatus): List { + return database.transaction { + val builder = session.criteriaBuilder + val query = builder.createQuery(String::class.java).run { + from(NodeInfoEntity::class.java).run { + select(get(NodeInfoEntity::nodeInfoHash.name)) + .where(builder.equal(get(NodeInfoEntity::certificateSigningRequest.name) + .get(CertificateSigningRequestEntity::certificateData.name) + .get(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 { - val bytes = networkParameters.serialize().bytes - val hash = bytes.sha256() - session.save(NetworkParametersEntity( - parametersBytes = bytes, - parametersHash = hash.toString() - )) - hash + override fun saveNetworkParameters(networkParameters: NetworkParameters): SecureHash { + return database.transaction { + val bytes = networkParameters.serialize().bytes + val hash = bytes.sha256() + session.save(NetworkParametersEntity( + parametersBytes = bytes, + parametersHash = hash.toString() + )) + hash + } } override fun getLatestNetworkParameters(): NetworkParameters = getLatestNetworkParametersEntity().networkParameters() - private fun getLatestNetworkParametersEntity(): NetworkParametersEntity = database.transaction { - val builder = session.criteriaBuilder - val query = builder.createQuery(NetworkParametersEntity::class.java).run { - from(NetworkParametersEntity::class.java).run { - orderBy(builder.desc(get(NetworkParametersEntity::version.name))) + private fun getLatestNetworkParametersEntity(): NetworkParametersEntity { + return database.transaction { + val builder = session.criteriaBuilder + val query = builder.createQuery(NetworkParametersEntity::class.java).run { + from(NetworkParametersEntity::class.java).run { + orderBy(builder.desc(get(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 { - val builder = session.criteriaBuilder - val query = builder.createQuery(NetworkMapEntity::class.java).run { - from(NetworkMapEntity::class.java).run { - where(builder.isNotNull(get(NetworkMapEntity::signature.name))) - orderBy(builder.desc(get(NetworkMapEntity::version.name))) + private fun getCurrentNetworkMapEntity(): NetworkMapEntity? { + return database.transaction { + val builder = session.criteriaBuilder + val query = builder.createQuery(NetworkMapEntity::class.java).run { + from(NetworkMapEntity::class.java).run { + where(builder.isNotNull(get(NetworkMapEntity::signature.name))) + orderBy(builder.desc(get(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 { - singleRequestWhere(NetworkParametersEntity::class.java) { builder, path -> - builder.equal(path.get(NetworkParametersEntity::parametersHash.name), parameterHash) + private fun getNetworkParametersEntity(parameterHash: String): NetworkParametersEntity? { + return database.transaction { + singleRequestWhere(NetworkParametersEntity::class.java) { builder, path -> + builder.equal(path.get(NetworkParametersEntity::parametersHash.name), parameterHash) + } } } } diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNodeInfoStorage.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNodeInfoStorage.kt index 1d4af91246..bea2f44bce 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNodeInfoStorage.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNodeInfoStorage.kt @@ -18,10 +18,9 @@ import java.security.cert.CertPath */ class PersistentNodeInfoStorage(private val database: CordaPersistence) : NodeInfoStorage { 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) { - val nodeInfo = signedNodeInfo.verified() - val nodeCaCert = nodeInfo.legalIdentitiesAndCerts[0].certPath.certificates.find { CertRole.extract(it) == CertRole.NODE_CA } - val request = nodeCaCert?.let { singleRequestWhere(CertificateDataEntity::class.java) { builder, path -> val certPublicKeyHashEq = builder.equal(path.get(CertificateDataEntity::publicKeyHash.name), it.publicKey.encoded.sha256().toString()) diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt index 13f5bac03b..2a4db95100 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt @@ -49,7 +49,7 @@ class NetworkManagementServer : Closeable { } 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) updateNetworkParameters?.let { diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/webservice/NodeInfoWebService.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/webservice/NodeInfoWebService.kt index 3f6f339ed6..0f4f41aacb 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/webservice/NodeInfoWebService.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/webservice/NodeInfoWebService.kt @@ -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.webservice.NodeInfoWebService.Companion.NETWORK_MAP_PATH 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.serialize import net.corda.nodeapi.internal.SignedNodeInfo @@ -37,9 +35,7 @@ class NodeInfoWebService(private val nodeInfoStorage: NodeInfoStorage, private val networkMapCache: LoadingCache = CacheBuilder.newBuilder() .expireAfterWrite(config.cacheTimeout, TimeUnit.MILLISECONDS) - .build(CacheLoader.from { _ -> - networkMapStorage.getCurrentNetworkMap() - }) + .build(CacheLoader.from { _ -> networkMapStorage.getCurrentNetworkMap() }) @POST @Path("publish") @@ -54,31 +50,27 @@ class NodeInfoWebService(private val nodeInfoStorage: NodeInfoStorage, // Catch exceptions thrown by signature verification. when (e) { 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 } }.build() } @GET - fun getNetworkMap(): Response { - 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() - } + fun getNetworkMap(): Response = createResponse(networkMapCache.get(true), addCacheTimeout = true) @GET @Path("node-info/{nodeInfoHash}") fun getNodeInfo(@PathParam("nodeInfoHash") nodeInfoHash: String): Response { - val nodeInfo = nodeInfoStorage.getNodeInfo(SecureHash.parse(nodeInfoHash)) - return if (nodeInfo != null) { - ok(nodeInfo.serialize().bytes) - } else { - status(Response.Status.NOT_FOUND) - }.build() + val signedNodeInfo = nodeInfoStorage.getNodeInfo(SecureHash.parse(nodeInfoHash)) + return createResponse(signedNodeInfo) + } + + @GET + @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 @@ -87,4 +79,16 @@ class NodeInfoWebService(private val nodeInfoStorage: NodeInfoStorage, // TODO: Verify this returns IP correctly. 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() + } } diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/Main.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/Main.kt index 4e25900f68..5b8668cfbf 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/Main.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/Main.kt @@ -46,7 +46,8 @@ fun run(parameters: Parameters) { checkNotNull(dataSourceProperties) val database = configureDatabase(dataSourceProperties, databaseConfig) 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( networkMapStorage, networkMapCertificateName, diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/TestBase.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/TestBase.kt index 98e2ada703..a7e37bc829 100644 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/TestBase.kt +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/TestBase.kt @@ -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 = 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 - ) - } } \ No newline at end of file diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/DBNetworkMapStorageTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNetworkMapStorageTest.kt similarity index 66% rename from network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/DBNetworkMapStorageTest.kt rename to network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNetworkMapStorageTest.kt index b7d332a5fe..832c124d79 100644 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/DBNetworkMapStorageTest.kt +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNetworkMapStorageTest.kt @@ -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 diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/signer/NetworkMapSignerTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/signer/NetworkMapSignerTest.kt index 9a65b68449..92b17f107f 100644 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/signer/NetworkMapSignerTest.kt +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/signer/NetworkMapSignerTest.kt @@ -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().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().apply { verify(networkMapStorage).saveNetworkMap(capture()) val networkMap = firstValue.verified(rootCACert.cert) - assertEquals(networkMapParameters.serialize().hash, networkMap.networkParameterHash) + assertEquals(networkParameters.serialize().hash, networkMap.networkParameterHash) } } } \ No newline at end of file diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/NodeInfoWebServiceTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/NodeInfoWebServiceTest.kt index 26452d45a9..939c901c65 100644 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/NodeInfoWebServiceTest.kt +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/NodeInfoWebServiceTest.kt @@ -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() + val signedNetworkMapResponse = it.doGet("") 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() + val nodeInfoResponse = it.doGet("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("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>("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>("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 NetworkManagementWebServer.doGet(path: String): T { + val url = URL("http://$hostAndPort/network-map/$path") + return url.openHttpConnection().inputStream.use { it.readBytes().deserialize() } + } } \ No newline at end of file