From 78b2bc75494184f03b2cb20704b1fbb306bfbd1e Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Fri, 16 Mar 2018 10:57:30 +0000 Subject: [PATCH] network_map table references the network_parameters table (ENT-1524). (#567) --- .../net/corda/core/internal/InternalUtils.kt | 12 ++- .../hsm/HsmSigningServiceTest.kt | 4 +- .../common/persistence/NetworkMapStorage.kt | 34 +++---- .../common/persistence/PersistenceUtils.kt | 9 +- ...sistentCertificateRevocationListStorage.kt | 2 +- ...tentCertificateRevocationRequestStorage.kt | 6 +- ...sistentCertificateSigningRequestStorage.kt | 4 +- .../PersistentNetworkMapStorage.kt | 93 ++++++++---------- .../persistence/PersistentNodeInfoStorage.kt | 7 +- .../persistence/entity/NetworkMapEntity.kt | 18 ++-- .../entity/NetworkParametersEntity.kt | 2 +- .../common/signer/NetworkMapSigner.kt | 65 +++++++------ .../doorman/NetworkManagementServer.kt | 4 +- .../webservice/NetworkMapWebService.kt | 18 ++-- .../network-manager.changelog-init.xml | 24 ++++- .../com/r3/corda/networkmanage/TestUtils.kt | 40 ++++++++ .../PersistentNetworkMapStorageTest.kt | 97 +++++++------------ .../common/signer/NetworkMapSignerTest.kt | 82 +++++++--------- .../webservice/NetworkMapWebServiceTest.kt | 42 ++++---- .../nodeapi/internal/crypto/X509Utilities.kt | 9 +- .../nodeapi/internal/network/NetworkMap.kt | 10 ++ 21 files changed, 304 insertions(+), 278 deletions(-) create mode 100644 network-management/src/test/kotlin/com/r3/corda/networkmanage/TestUtils.kt diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt index 57f928b2bd..aa3fe09997 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -388,10 +388,16 @@ val CordaX500Name.x500Name: X500Name val CordaX500Name.Companion.unspecifiedCountry get() = "ZZ" -fun T.signWithCert(privateKey: PrivateKey, certificate: X509Certificate): SignedDataWithCert { +inline fun T.signWithCert(signer: (SerializedBytes) -> DigitalSignatureWithCert): SignedDataWithCert { val serialised = serialize() - val signature = Crypto.doSign(privateKey, serialised.bytes) - return SignedDataWithCert(serialised, DigitalSignatureWithCert(certificate, signature)) + return SignedDataWithCert(serialised, signer(serialised)) +} + +fun T.signWithCert(privateKey: PrivateKey, certificate: X509Certificate): SignedDataWithCert { + return signWithCert { + val signature = Crypto.doSign(privateKey, it.bytes) + DigitalSignatureWithCert(certificate, signature) + } } inline fun SerializedBytes.sign(signer: (SerializedBytes) -> DigitalSignature.WithKey): SignedData { diff --git a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmSigningServiceTest.kt b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmSigningServiceTest.kt index 7e644a7751..6b242011f8 100644 --- a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmSigningServiceTest.kt +++ b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmSigningServiceTest.kt @@ -165,9 +165,7 @@ class HsmSigningServiceTest : HsmBaseTest() { networkMapSigner.signNetworkMap() // then - val signedNetworkMap = networkMapStorage.getCurrentNetworkMap() - assertNotNull(signedNetworkMap) - val persistedNetworkMap = signedNetworkMap!!.verified() + val persistedNetworkMap = networkMapStorage.getActiveNetworkMap()!!.toSignedNetworkMap().verified() assertEquals(networkMapParameters.serialize().hash, persistedNetworkMap.networkParameterHash) assertThat(persistedNetworkMap.nodeInfoHashes).isEmpty() } 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 b07d66748b..4692caf380 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 @@ -10,11 +10,13 @@ package com.r3.corda.networkmanage.common.persistence +import com.r3.corda.networkmanage.common.persistence.entity.NetworkMapEntity +import com.r3.corda.networkmanage.common.persistence.entity.NetworkParametersEntity import com.r3.corda.networkmanage.common.persistence.entity.NodeInfoEntity import net.corda.core.crypto.SecureHash import net.corda.core.internal.DigitalSignatureWithCert import net.corda.core.node.NetworkParameters -import net.corda.nodeapi.internal.network.SignedNetworkMap +import net.corda.nodeapi.internal.network.NetworkMapAndSigned import net.corda.nodeapi.internal.network.SignedNetworkParameters /** @@ -22,47 +24,37 @@ import net.corda.nodeapi.internal.network.SignedNetworkParameters */ interface NetworkMapStorage { /** - * Retrieves current network map. Current in this context means the one that has been most recently signed. - * @return current network map + * Returns the active network map, or null */ - fun getCurrentNetworkMap(): SignedNetworkMap? + fun getActiveNetworkMap(): NetworkMapEntity? + + /** + * Persist the new active network map, replacing any existing network map. + */ + fun saveNewActiveNetworkMap(networkMapAndSigned: NetworkMapAndSigned) /** * Retrieves node info hashes where [NodeInfoEntity.isCurrent] is true and the certificate status is [CertificateStatus.VALID] - * - * @return list of current and valid node info hashes. */ fun getActiveNodeInfoHashes(): List - /** - * Persists a new instance of the signed network map. - * @param signedNetworkMap encapsulates all the information needed for persisting current network map state. - */ - fun saveNetworkMap(signedNetworkMap: SignedNetworkMap) - /** * Retrieve the signed with certificate network parameters by their hash. The hash is that of the underlying - * [NetworkParameters] object and not the `SignedWithCert` object that's returned. + * [NetworkParameters] object and not the [SignedNetworkParameters] object that's returned. * @return signed network parameters corresponding to the given hash or null if it does not exist (parameters don't exist or they haven't been signed yet) */ fun getSignedNetworkParameters(hash: SecureHash): SignedNetworkParameters? - /** - * Retrieve the network parameters of the current network map, or null if there's no network map. - */ - // TODO: Remove this method. We should get the "current" network parameter by using the the hash in the network map and use the [getSignedNetworkParameters] method. - fun getNetworkParametersOfNetworkMap(): SignedNetworkParameters? - /** * Persists given network parameters with signature if provided. * @return hash corresponding to newly created network parameters entry */ - fun saveNetworkParameters(networkParameters: NetworkParameters, sig: DigitalSignatureWithCert?): SecureHash + fun saveNetworkParameters(networkParameters: NetworkParameters, signature: DigitalSignatureWithCert?): SecureHash /** * Retrieves the latest (i.e. most recently inserted) network parameters * Note that they may not have been signed up yet. * @return latest network parameters */ - fun getLatestNetworkParameters(): NetworkParameters? + fun getLatestNetworkParameters(): NetworkParametersEntity? } diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistenceUtils.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistenceUtils.kt index cde83fea56..5e5698b3a5 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistenceUtils.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistenceUtils.kt @@ -18,23 +18,24 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.DatabaseTransaction import net.corda.nodeapi.internal.persistence.SchemaMigration +import org.hibernate.query.Query import java.util.* import javax.persistence.LockModeType import javax.persistence.criteria.CriteriaBuilder import javax.persistence.criteria.Path import javax.persistence.criteria.Predicate -inline fun DatabaseTransaction.singleEntityWhere(predicate: (CriteriaBuilder, Path) -> Predicate): T? { - return getEntitiesWhere(predicate).firstOrNull() +inline fun DatabaseTransaction.uniqueEntityWhere(predicate: (CriteriaBuilder, Path) -> Predicate): T? { + return entitiesWhere(predicate).setMaxResults(1).uniqueResult() } -inline fun DatabaseTransaction.getEntitiesWhere(predicate: (CriteriaBuilder, Path) -> Predicate): List { +inline fun DatabaseTransaction.entitiesWhere(predicate: (CriteriaBuilder, Path) -> Predicate): Query { val builder = session.criteriaBuilder val criteriaQuery = builder.createQuery(T::class.java) val query = criteriaQuery.from(T::class.java).run { criteriaQuery.where(predicate(builder, this)) } - return session.createQuery(query).setLockMode(LockModeType.PESSIMISTIC_WRITE).resultList + return session.createQuery(query).setLockMode(LockModeType.PESSIMISTIC_WRITE) } inline fun DatabaseTransaction.deleteEntity(predicate: (CriteriaBuilder, Path) -> Predicate): Int { diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateRevocationListStorage.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateRevocationListStorage.kt index 6ec0fb4f79..1fb909dbff 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateRevocationListStorage.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateRevocationListStorage.kt @@ -40,7 +40,7 @@ class PersistentCertificateRevocationListStorage(private val database: CordaPers } private fun revokeCertificate(certificateSerialNumber: BigInteger, time: Instant, transaction: DatabaseTransaction) { - val revocation = transaction.singleEntityWhere { builder, path -> + val revocation = transaction.uniqueEntityWhere { builder, path -> builder.equal(path.get(CertificateRevocationRequestEntity::certificateSerialNumber.name), certificateSerialNumber) } revocation ?: throw IllegalStateException("The certificate revocation request for $certificateSerialNumber does not exist") diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateRevocationRequestStorage.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateRevocationRequestStorage.kt index ab1a2690be..2acc3ff272 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateRevocationRequestStorage.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateRevocationRequestStorage.kt @@ -13,7 +13,7 @@ class PersistentCertificateRevocationRequestStorage(private val database: CordaP override fun saveRevocationRequest(certificateSerialNumber: BigInteger, reason: CRLReason, reporter: String): String { return database.transaction(TransactionIsolationLevel.SERIALIZABLE) { // Check if there is an entry for the given certificate serial number - val revocation = singleEntityWhere { builder, path -> + val revocation = uniqueEntityWhere { builder, path -> val serialNumberEqual = builder.equal(path.get(CertificateRevocationRequestEntity::certificateSerialNumber.name), certificateSerialNumber) val statusNotEqualRejected = builder.notEqual(path.get(CertificateRevocationRequestEntity::status.name), RequestStatus.REJECTED) builder.and(serialNumberEqual, statusNotEqualRejected) @@ -21,7 +21,7 @@ class PersistentCertificateRevocationRequestStorage(private val database: CordaP if (revocation != null) { revocation.requestId } else { - val certificateData = singleEntityWhere { builder, path -> + val certificateData = uniqueEntityWhere { builder, path -> val serialNumberEqual = builder.equal(path.get(CertificateDataEntity::certificateSerialNumber.name), certificateSerialNumber) val statusEqualValid = builder.equal(path.get(CertificateDataEntity::certificateStatus.name), CertificateStatus.VALID) builder.and(serialNumberEqual, statusEqualValid) @@ -90,7 +90,7 @@ class PersistentCertificateRevocationRequestStorage(private val database: CordaP } private fun getRevocationRequestEntity(requestId: String): CertificateRevocationRequestEntity? = database.transaction { - singleEntityWhere { builder, path -> + uniqueEntityWhere { builder, path -> builder.equal(path.get(CertificateRevocationRequestEntity::requestId.name), requestId) } } diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateSigningRequestStorage.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateSigningRequestStorage.kt index ea87ad16d3..46ea5f9a3d 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateSigningRequestStorage.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateSigningRequestStorage.kt @@ -38,7 +38,7 @@ class PersistentCertificateSigningRequestStorage(private val database: CordaPers override fun putCertificatePath(requestId: String, certPath: CertPath, signedBy: String) { return database.transaction(TransactionIsolationLevel.SERIALIZABLE) { - val request = singleEntityWhere { builder, path -> + val request = uniqueEntityWhere { builder, path -> val requestIdEq = builder.equal(path.get(CertificateSigningRequestEntity::requestId.name), requestId) val statusEq = builder.equal(path.get(CertificateSigningRequestEntity::status.name), RequestStatus.APPROVED) builder.and(requestIdEq, statusEq) @@ -89,7 +89,7 @@ class PersistentCertificateSigningRequestStorage(private val database: CordaPers private fun DatabaseTransaction.findRequest(requestId: String, requestStatus: RequestStatus? = null): CertificateSigningRequestEntity? { - return singleEntityWhere { builder, path -> + return uniqueEntityWhere { builder, path -> val idClause = builder.equal(path.get(CertificateSigningRequestEntity::requestId.name), requestId) if (requestStatus == null) { idClause 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 809da2d0d1..b25fc6ed70 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 @@ -12,48 +12,54 @@ package com.r3.corda.networkmanage.common.persistence import com.r3.corda.networkmanage.common.persistence.entity.* import net.corda.core.crypto.SecureHash -import net.corda.core.crypto.sha256 import net.corda.core.internal.DigitalSignatureWithCert import net.corda.core.node.NetworkParameters import net.corda.core.serialization.serialize -import net.corda.nodeapi.internal.network.SignedNetworkMap +import net.corda.nodeapi.internal.network.NetworkMapAndSigned import net.corda.nodeapi.internal.network.SignedNetworkParameters import net.corda.nodeapi.internal.persistence.CordaPersistence +import net.corda.nodeapi.internal.persistence.DatabaseTransaction /** * Database implementation of the [NetworkMapStorage] interface */ class PersistentNetworkMapStorage(private val database: CordaPersistence) : NetworkMapStorage { - override fun getCurrentNetworkMap(): SignedNetworkMap? { + override fun getActiveNetworkMap(): NetworkMapEntity? { return database.transaction { - getCurrentNetworkMapEntity()?.toSignedNetworkMap() - } - } - - override fun getNetworkParametersOfNetworkMap(): SignedNetworkParameters? { - return database.transaction { - getCurrentNetworkMapEntity()?.let { - val netParamsHash = it.toNetworkMap().networkParameterHash - getSignedNetworkParameters(netParamsHash) ?: - throw IllegalStateException("Current network map is pointing to network parameters that do not exist: $netParamsHash") + val builder = session.criteriaBuilder + val query = builder.createQuery(NetworkMapEntity::class.java).run { + from(NetworkMapEntity::class.java).run { + orderBy(builder.desc(get(NetworkMapEntity::version.name))) + } } + // We want the latest signed entry + session.createQuery(query).setMaxResults(1).uniqueResult() } } - override fun saveNetworkMap(signedNetworkMap: SignedNetworkMap) { + override fun saveNewActiveNetworkMap(networkMapAndSigned: NetworkMapAndSigned) { + val (networkMap, signedNetworkMap) = networkMapAndSigned database.transaction { - val networkMapEntity = NetworkMapEntity( - networkMap = signedNetworkMap.raw.bytes, + val networkParametersEntity = checkNotNull(getNetworkParametersEntity(networkMap.networkParameterHash)) { + "Network parameters ${networkMap.networkParameterHash} must be first persisted" + } + check(networkParametersEntity.isSigned) { + "Network parameters ${networkMap.networkParameterHash} are not signed" + } + session.save(NetworkMapEntity( + networkMapBytes = signedNetworkMap.raw.bytes, signature = signedNetworkMap.sig.bytes, - certificate = signedNetworkMap.sig.by.encoded - ) - session.save(networkMapEntity) + certificate = signedNetworkMap.sig.by.encoded, + networkParameters = networkParametersEntity + )) } } override fun getSignedNetworkParameters(hash: SecureHash): SignedNetworkParameters? { - return getNetworkParametersEntity(hash.toString())?.let { - if (it.isSigned) it.toSignedNetworkParameters() else null + return database.transaction { + getNetworkParametersEntity(hash)?.let { + if (it.isSigned) it.toSignedNetworkParameters() else null + } } } @@ -65,6 +71,8 @@ class PersistentNetworkMapStorage(private val database: CordaPersistence) : Netw val certStatusExpression = get(NodeInfoEntity::certificateSigningRequest.name) .get(CertificateSigningRequestEntity::certificateData.name) .get(CertificateDataEntity::certificateStatus.name) + // TODO When revoking certs, all node-infos that point to it must be made non-current. Then this check + // isn't needed. val certStatusEq = builder.equal(certStatusExpression, CertificateStatus.VALID) val isCurrentNodeInfo = builder.isTrue(get(NodeInfoEntity::isCurrent.name)) select(get(NodeInfoEntity::nodeInfoHash.name)).where(builder.and(certStatusEq, isCurrentNodeInfo)) @@ -74,21 +82,21 @@ class PersistentNetworkMapStorage(private val database: CordaPersistence) : Netw } } - override fun saveNetworkParameters(networkParameters: NetworkParameters, sig: DigitalSignatureWithCert?): SecureHash { - return database.transaction { - val bytes = networkParameters.serialize().bytes - val hash = bytes.sha256() + override fun saveNetworkParameters(networkParameters: NetworkParameters, signature: DigitalSignatureWithCert?): SecureHash { + val serialised = networkParameters.serialize() + val hash = serialised.hash + database.transaction { session.saveOrUpdate(NetworkParametersEntity( - parametersBytes = bytes, + parametersBytes = serialised.bytes, parametersHash = hash.toString(), - signature = sig?.bytes, - certificate = sig?.by?.encoded + signature = signature?.bytes, + certificate = signature?.by?.encoded )) - hash } + return hash } - override fun getLatestNetworkParameters(): NetworkParameters? { + override fun getLatestNetworkParameters(): NetworkParametersEntity? { return database.transaction { val query = session.criteriaBuilder.run { createQuery(NetworkParametersEntity::class.java).run { @@ -98,30 +106,13 @@ class PersistentNetworkMapStorage(private val database: CordaPersistence) : Netw } } // We just want the last entry - session.createQuery(query).setMaxResults(1).uniqueResult()?.toNetworkParameters() + session.createQuery(query).setMaxResults(1).uniqueResult() } } - private fun getCurrentNetworkMapEntity(): NetworkMapEntity? { - return database.transaction { - val builder = session.criteriaBuilder - val query = builder.createQuery(NetworkMapEntity::class.java).run { - from(NetworkMapEntity::class.java).run { - // TODO a limit of 1 since we only need the first result - 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() - } - } - - private fun getNetworkParametersEntity(parameterHash: String): NetworkParametersEntity? { - return database.transaction { - singleEntityWhere { builder, path -> - builder.equal(path.get(NetworkParametersEntity::parametersHash.name), parameterHash) - } + private fun DatabaseTransaction.getNetworkParametersEntity(hash: SecureHash): NetworkParametersEntity? { + return uniqueEntityWhere { builder, path -> + builder.equal(path.get(NetworkParametersEntity::parametersHash.name), hash.toString()) } } } 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 f323e5ea49..0724399cbe 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 @@ -43,12 +43,13 @@ class PersistentNodeInfoStorage(private val database: CordaPersistence) : NodeIn } // Update any [NodeInfoEntity] instance for this CSR as not current. - val existingNodeInfo = getEntitiesWhere { builder, path -> + entitiesWhere { builder, path -> val requestEq = builder.equal(path.get(NodeInfoEntity::certificateSigningRequest.name), request) val isCurrent = builder.isTrue(path.get(NodeInfoEntity::isCurrent.name)) builder.and(requestEq, isCurrent) + }.resultStream.use { existingNodeInfos -> + existingNodeInfos.forEach { session.merge(it.copy(isCurrent = false)) } } - existingNodeInfo.forEach { session.merge(it.copy(isCurrent = false)) } val hash = signedNodeInfo.raw.hash val hashedNodeInfo = NodeInfoEntity( @@ -75,7 +76,7 @@ class PersistentNodeInfoStorage(private val database: CordaPersistence) : NodeIn } private fun DatabaseTransaction.getSignedRequestByPublicHash(publicKeyHash: SecureHash): CertificateSigningRequestEntity? { - return singleEntityWhere { builder, path -> + return uniqueEntityWhere { builder, path -> val publicKeyEq = builder.equal(path.get(CertificateSigningRequestEntity::publicKeyHash.name), publicKeyHash.toString()) val statusEq = builder.equal(path.get(CertificateSigningRequestEntity::status.name), RequestStatus.DONE) builder.and(publicKeyEq, statusEq) diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NetworkMapEntity.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NetworkMapEntity.kt index 22634f5dba..343102efc4 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NetworkMapEntity.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NetworkMapEntity.kt @@ -26,22 +26,26 @@ class NetworkMapEntity( val version: Long? = null, @Lob - @Column(name = "serialized_network_map") - val networkMap: ByteArray, + @Column(name = "serialized_network_map", nullable = false) + val networkMapBytes: ByteArray, @Lob - @Column(name = "signature") + @Column(name = "signature", nullable = false) val signature: ByteArray, @Lob - @Column(name = "certificate") - val certificate: ByteArray + @Column(name = "certificate", nullable = false) + val certificate: ByteArray, + + @ManyToOne(optional = false, fetch = FetchType.EAGER) + @JoinColumn(name = "network_parameters") + val networkParameters: NetworkParametersEntity ) { - fun toNetworkMap(): NetworkMap = networkMap.deserialize() + fun toNetworkMap(): NetworkMap = networkMapBytes.deserialize() fun toSignedNetworkMap(): SignedNetworkMap { return SignedNetworkMap( - SerializedBytes(networkMap), + SerializedBytes(networkMapBytes), DigitalSignatureWithCert(X509CertificateFactory().generateCertificate(certificate.inputStream()), signature) ) } diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NetworkParametersEntity.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NetworkParametersEntity.kt index 826fd30051..00164bc70a 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NetworkParametersEntity.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/NetworkParametersEntity.kt @@ -31,7 +31,7 @@ class NetworkParametersEntity( val created: Instant = Instant.now(), @Lob - @Column(name = "parameters_bytes") + @Column(name = "parameters_bytes", nullable = false) val parametersBytes: ByteArray, // Both of the fields below are nullable, because of the way we sign network map data. NetworkParameters can be diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/signer/NetworkMapSigner.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/signer/NetworkMapSigner.kt index bc8a3f25ae..8c8e496f9e 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/signer/NetworkMapSigner.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/signer/NetworkMapSigner.kt @@ -11,11 +11,12 @@ package com.r3.corda.networkmanage.common.signer import com.r3.corda.networkmanage.common.persistence.NetworkMapStorage +import net.corda.core.crypto.SecureHash import net.corda.core.node.NetworkParameters -import net.corda.core.serialization.serialize import net.corda.core.utilities.contextLogger +import net.corda.core.utilities.debug import net.corda.nodeapi.internal.network.NetworkMap -import net.corda.nodeapi.internal.network.SignedNetworkMap +import net.corda.nodeapi.internal.network.NetworkMapAndSigned class NetworkMapSigner(private val networkMapStorage: NetworkMapStorage, private val signer: Signer) { private companion object { @@ -28,41 +29,45 @@ class NetworkMapSigner(private val networkMapStorage: NetworkMapStorage, private fun signNetworkMap() { // TODO There is no network parameters update process in place yet. We assume that latest parameters are to be used // in current network map. - val latestNetworkParameters = networkMapStorage.getLatestNetworkParameters() - if (latestNetworkParameters == null) { - logger.debug("No network parameters present") + val latestNetParamsEntity = networkMapStorage.getLatestNetworkParameters() + if (latestNetParamsEntity == null) { + logger.warn("No network parameters present") return } - logger.debug("Fetching current network map parameters...") - val currentNetworkParameters = networkMapStorage.getNetworkParametersOfNetworkMap() - logger.debug("Retrieved network map parameters: $currentNetworkParameters") - if (currentNetworkParameters?.verified() != latestNetworkParameters) { - persistSignedNetworkParameters(latestNetworkParameters) + + val latestNetParams = latestNetParamsEntity.toNetworkParameters() + logger.debug { "Latest network parameters: $latestNetParams" } + + if (!latestNetParamsEntity.isSigned) { + signAndPersistNetworkParameters(latestNetParams) } else { - logger.debug("Network map parameters up-to-date. Skipping signing.") + logger.debug("Network parameters are already signed") } - logger.debug("Fetching current network map...") - val currentSignedNetworkMap = networkMapStorage.getCurrentNetworkMap() - logger.debug("Fetching node info hashes with VALID certificates...") + val nodeInfoHashes = networkMapStorage.getActiveNodeInfoHashes() - logger.debug("Retrieved node info hashes: $nodeInfoHashes") - val newNetworkMap = NetworkMap(nodeInfoHashes, latestNetworkParameters.serialize().hash, null) - val serialisedNetworkMap = newNetworkMap.serialize() - if (serialisedNetworkMap != currentSignedNetworkMap?.raw) { - logger.info("Signing a new network map: $newNetworkMap") - logger.debug("Creating a new signed network map: ${serialisedNetworkMap.hash}") - val newSignedNetworkMap = SignedNetworkMap(serialisedNetworkMap, signer.signBytes(serialisedNetworkMap.bytes)) - networkMapStorage.saveNetworkMap(newSignedNetworkMap) - logger.debug("Signed network map saved") + logger.debug { "Active node-info hashes:\n${nodeInfoHashes.joinToString("\n")}" } + + val currentNetworkMap = networkMapStorage.getActiveNetworkMap()?.toNetworkMap() + if (currentNetworkMap != null) { + logger.debug { "Current network map: $currentNetworkMap" } } else { - logger.debug("Current network map is up-to-date. Skipping signing.") + logger.info("There is currently no network map") + } + + val newNetworkMap = NetworkMap(nodeInfoHashes, SecureHash.parse(latestNetParamsEntity.parametersHash), null) + logger.debug { "Potential new network map: $newNetworkMap" } + + if (currentNetworkMap != newNetworkMap) { + val netNetworkMapAndSigned = NetworkMapAndSigned(newNetworkMap) { signer.signBytes(it.bytes) } + networkMapStorage.saveNewActiveNetworkMap(netNetworkMapAndSigned) + logger.info("Signed new network map: $newNetworkMap") + } else { + logger.debug("Current network map is up-to-date") } } - fun persistSignedNetworkParameters(networkParameters: NetworkParameters) { - logger.info("Signing and persisting network parameters: $networkParameters") - val digitalSignature = signer.signObject(networkParameters).sig - networkMapStorage.saveNetworkParameters(networkParameters, digitalSignature) - logger.info("Signed network map parameters saved.") + fun signAndPersistNetworkParameters(networkParameters: NetworkParameters) { + networkMapStorage.saveNetworkParameters(networkParameters, signer.signObject(networkParameters).sig) + logger.info("Signed network parameters: $networkParameters") } -} \ No newline at end of file +} diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkManagementServer.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkManagementServer.kt index 0bd7a44efa..673697018f 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkManagementServer.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkManagementServer.kt @@ -59,9 +59,9 @@ class NetworkManagementServer : Closeable { val localNetworkMapSigner = signer?.let { NetworkMapSigner(networkMapStorage, it) } newNetworkParameters?.let { - val netParamsOfNetworkMap = networkMapStorage.getNetworkParametersOfNetworkMap() + val netParamsOfNetworkMap = networkMapStorage.getActiveNetworkMap()?.networkParameters if (netParamsOfNetworkMap == null) { - localNetworkMapSigner?.persistSignedNetworkParameters(it) ?: networkMapStorage.saveNetworkParameters(it, null) + localNetworkMapSigner?.signAndPersistNetworkParameters(it) ?: networkMapStorage.saveNetworkParameters(it, null) } else { throw UnsupportedOperationException("Network parameters already exist. Updating them is not supported yet.") } diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/webservice/NetworkMapWebService.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/webservice/NetworkMapWebService.kt index 4f31b85374..691aa53609 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/webservice/NetworkMapWebService.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/webservice/NetworkMapWebService.kt @@ -52,11 +52,12 @@ class NetworkMapWebService(private val nodeInfoStorage: NodeInfoStorage, private val networkMapCache: LoadingCache = Caffeine.newBuilder() .expireAfterWrite(config.cacheTimeout, TimeUnit.MILLISECONDS) - .build { _ -> - networkMapStorage.getCurrentNetworkMap()?.let { - val networkMap = it.verified() - val networkParameters = networkMapStorage.getSignedNetworkParameters(networkMap.networkParameterHash)?.verified() - CachedData(it, networkMap.nodeInfoHashes.toSet(), networkParameters) + .build { + networkMapStorage.getActiveNetworkMap()?.let { + logger.info("Re-publishing network map") + val networkMap = it.toNetworkMap() + val signedNetworkMap = it.toSignedNetworkMap() + CachedData(signedNetworkMap, networkMap.nodeInfoHashes.toSet(), it.networkParameters.toNetworkParameters()) } } @@ -67,7 +68,7 @@ class NetworkMapWebService(private val nodeInfoStorage: NodeInfoStorage, private val currentSignedNetworkMap: SignedNetworkMap? get() = networkMapCache.get(true)?.signedNetworkMap private val currentNodeInfoHashes: Set get() = networkMapCache.get(true)?.nodeInfoHashes ?: emptySet() - private val currentNetworkParameters: NetworkParameters? get() = networkMapCache.get(true)?.currentNetworkParameter + private val currentNetworkParameters: NetworkParameters? get() = networkMapCache.get(true)?.networkParameters @POST @Path("publish") @@ -153,6 +154,7 @@ class NetworkMapWebService(private val nodeInfoStorage: NodeInfoStorage, class NetworkMapNotInitialisedException(message: String?) : Exception(message) class InvalidPlatformVersionException(message: String?) : Exception(message) - private data class CachedData(val signedNetworkMap: SignedNetworkMap, val nodeInfoHashes: Set, val currentNetworkParameter: NetworkParameters?) - + private data class CachedData(val signedNetworkMap: SignedNetworkMap, + val nodeInfoHashes: Set, + val networkParameters: NetworkParameters) } diff --git a/network-management/src/main/resources/migration/network-manager.changelog-init.xml b/network-management/src/main/resources/migration/network-manager.changelog-init.xml index cf0bd97339..6eadb6102a 100644 --- a/network-management/src/main/resources/migration/network-manager.changelog-init.xml +++ b/network-management/src/main/resources/migration/network-manager.changelog-init.xml @@ -80,9 +80,18 @@ - - - + + + + + + + + + + + + @@ -92,7 +101,9 @@ - + + + @@ -299,4 +310,9 @@ constraintName="FK_CSR_PN" referencedColumnNames="id" referencedTableName="private_network"/> + + + diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/TestUtils.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/TestUtils.kt new file mode 100644 index 0000000000..e4fa8b5b1a --- /dev/null +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/TestUtils.kt @@ -0,0 +1,40 @@ +package com.r3.corda.networkmanage + +import com.r3.corda.networkmanage.common.persistence.entity.NetworkMapEntity +import com.r3.corda.networkmanage.common.persistence.entity.NetworkParametersEntity +import net.corda.core.crypto.SecureHash +import net.corda.core.node.NetworkParameters +import net.corda.nodeapi.internal.createDevNetworkMapCa +import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair +import net.corda.nodeapi.internal.network.NetworkMap +import net.corda.testing.common.internal.testNetworkParameters + +fun createNetworkParametersEntity(signingCertAndKeyPair: CertificateAndKeyPair = createDevNetworkMapCa(), + networkParameters: NetworkParameters = testNetworkParameters()): NetworkParametersEntity { + val signedNetParams = signingCertAndKeyPair.sign(networkParameters) + return NetworkParametersEntity( + parametersHash = signedNetParams.raw.hash.toString(), + parametersBytes = signedNetParams.raw.bytes, + signature = signedNetParams.sig.bytes, + certificate = signedNetParams.sig.by.encoded + ) +} + +fun createNetworkMapEntity(signingCertAndKeyPair: CertificateAndKeyPair = createDevNetworkMapCa(), + netParamsEntity: NetworkParametersEntity, + nodeInfoHashes: List = emptyList()): NetworkMapEntity { + val signedNetMap = signingCertAndKeyPair.sign(NetworkMap(nodeInfoHashes, SecureHash.parse(netParamsEntity.parametersHash), null)) + return NetworkMapEntity( + networkMapBytes = signedNetMap.raw.bytes, + signature = signedNetMap.sig.bytes, + certificate = signedNetMap.sig.by.encoded, + networkParameters = netParamsEntity + ) +} + +fun createNetworkMapEntity(signingCertAndKeyPair: CertificateAndKeyPair = createDevNetworkMapCa(), + networkParameters: NetworkParameters = testNetworkParameters(), + nodeInfoHashes: List = emptyList()): NetworkMapEntity { + val netParamsEntity = createNetworkParametersEntity(signingCertAndKeyPair, networkParameters) + return createNetworkMapEntity(signingCertAndKeyPair, netParamsEntity, nodeInfoHashes) +} diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNetworkMapStorageTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNetworkMapStorageTest.kt index e06e334250..f2b6ec3ba3 100644 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNetworkMapStorageTest.kt +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNetworkMapStorageTest.kt @@ -11,10 +11,12 @@ package com.r3.corda.networkmanage.common.persistence import com.r3.corda.networkmanage.TestBase -import net.corda.core.internal.signWithCert +import com.r3.corda.networkmanage.common.persistence.entity.NodeInfoEntity +import net.corda.core.crypto.SecureHash import net.corda.nodeapi.internal.createDevNetworkMapCa import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair import net.corda.nodeapi.internal.network.NetworkMap +import net.corda.nodeapi.internal.network.NetworkMapAndSigned import net.corda.nodeapi.internal.network.verifiedNetworkMapCert import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig @@ -26,7 +28,6 @@ import org.junit.After import org.junit.Before import org.junit.Test import java.security.cert.X509Certificate -import kotlin.test.assertEquals class PersistentNetworkMapStorageTest : TestBase() { private lateinit var persistence: CordaPersistence @@ -35,13 +36,13 @@ class PersistentNetworkMapStorageTest : TestBase() { private lateinit var requestStorage: PersistentCertificateSigningRequestStorage private lateinit var rootCaCert: X509Certificate - private lateinit var networkMapCa: CertificateAndKeyPair + private lateinit var networkMapCertAndKeyPair: CertificateAndKeyPair @Before fun startDb() { val (rootCa) = createDevIntermediateCaCertPath() rootCaCert = rootCa.certificate - networkMapCa = createDevNetworkMapCa(rootCa) + networkMapCertAndKeyPair = createDevNetworkMapCa(rootCa) persistence = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true)) networkMapStorage = PersistentNetworkMapStorage(persistence) nodeInfoStorage = PersistentNodeInfoStorage(persistence) @@ -54,74 +55,52 @@ class PersistentNetworkMapStorageTest : TestBase() { } @Test - fun `saveNetworkMap and saveNetworkParameters create current network map and parameters`() { + fun `saveNetworkParameters and then saveNewActiveNetworkMap creates the active network map`() { // given // Create node info. val (signedNodeInfo) = createValidSignedNodeInfo("Test", requestStorage) val nodeInfoHash = nodeInfoStorage.putNodeInfo(signedNodeInfo) - val networkParameters = testNetworkParameters(emptyList()) - val parametersSignature = networkParameters.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate).sig // Create network parameters - val networkParametersHash = networkMapStorage.saveNetworkParameters(networkParameters, parametersSignature) + val networkParameters = testNetworkParameters(maxTransactionSize = 1234567) + val networkParametersSig = networkMapCertAndKeyPair.sign(networkParameters).sig + val networkParametersHash = networkMapStorage.saveNetworkParameters(networkParameters, networkParametersSig) val networkMap = NetworkMap(listOf(nodeInfoHash), networkParametersHash, null) - val signedNetworkMap = networkMap.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate) + val networkMapAndSigned = NetworkMapAndSigned(networkMap) { networkMapCertAndKeyPair.sign(networkMap).sig } // when - networkMapStorage.saveNetworkMap(signedNetworkMap) + networkMapStorage.saveNewActiveNetworkMap(networkMapAndSigned) // then - val persistedSignedNetworkMap = networkMapStorage.getCurrentNetworkMap() - val persistedSignedParameters = networkMapStorage.getNetworkParametersOfNetworkMap() + val activeNetworkMapEntity = networkMapStorage.getActiveNetworkMap()!! + val activeSignedNetworkMap = activeNetworkMapEntity.toSignedNetworkMap() + val activeNetworkMap = activeSignedNetworkMap.verifiedNetworkMapCert(rootCaCert) + val activeNetworkParametersEntity = activeNetworkMapEntity.networkParameters + val activeSignedNetworkParameters = activeNetworkParametersEntity.toSignedNetworkParameters() + val activeNetworkParameters = activeSignedNetworkParameters.verifiedNetworkMapCert(rootCaCert) - assertEquals(networkParameters, persistedSignedParameters?.verifiedNetworkMapCert(rootCaCert)) - assertEquals(parametersSignature, persistedSignedParameters?.sig) - assertEquals(signedNetworkMap.sig, persistedSignedNetworkMap?.sig) - assertEquals(signedNetworkMap.verifiedNetworkMapCert(rootCaCert), persistedSignedNetworkMap?.verifiedNetworkMapCert(rootCaCert)) - assertEquals(signedNetworkMap.verifiedNetworkMapCert(rootCaCert).networkParameterHash, persistedSignedParameters?.raw?.hash) + assertThat(activeNetworkMap).isEqualTo(networkMap) + assertThat(activeSignedNetworkMap.sig).isEqualTo(networkMapAndSigned.signed.sig) + assertThat(activeNetworkParameters).isEqualTo(networkParameters) + assertThat(activeSignedNetworkParameters.sig).isEqualTo(networkParametersSig) + assertThat(SecureHash.parse(activeNetworkParametersEntity.parametersHash)) + .isEqualTo(activeNetworkMap.networkParameterHash) + .isEqualTo(networkParametersHash) } @Test fun `getLatestNetworkParameters returns last inserted`() { - val params1 = testNetworkParameters(emptyList(), minimumPlatformVersion = 1) - val params2 = testNetworkParameters(emptyList(), minimumPlatformVersion = 2) - // given - networkMapStorage.saveNetworkParameters(params1, params1.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate).sig) + val params1 = testNetworkParameters(minimumPlatformVersion = 1) + val params2 = testNetworkParameters(minimumPlatformVersion = 2) + networkMapStorage.saveNetworkParameters(params1, networkMapCertAndKeyPair.sign(params1).sig) // We may have not signed them yet. networkMapStorage.saveNetworkParameters(params2, null) - // when - val latest = networkMapStorage.getLatestNetworkParameters()?.minimumPlatformVersion - // then - assertEquals(2, latest) + assertThat(networkMapStorage.getLatestNetworkParameters()?.toNetworkParameters()).isEqualTo(params2) } @Test - fun `getNetworkParametersOfNetworkMap returns current network map parameters`() { - // given - // Create network parameters - val testParameters1 = testNetworkParameters(emptyList()) - val networkParametersHash = networkMapStorage.saveNetworkParameters(testParameters1, testParameters1.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate).sig) - // Create empty network map - - // Sign network map making it current network map - val networkMap = NetworkMap(emptyList(), networkParametersHash, null) - val signedNetworkMap = networkMap.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate) - networkMapStorage.saveNetworkMap(signedNetworkMap) - - // Create new network parameters - val testParameters2 = testNetworkParameters(emptyList(), minimumPlatformVersion = 2) - networkMapStorage.saveNetworkParameters(testParameters2, testParameters2.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate).sig) - - // when - val result = networkMapStorage.getNetworkParametersOfNetworkMap()?.verifiedNetworkMapCert(rootCaCert) - - // then - assertEquals(1, result?.minimumPlatformVersion) - } - - @Test - fun `getValidNodeInfoHashes returns only valid and signed node info hashes`() { + fun `getValidNodeInfoHashes returns only for current node-infos`() { // given // Create node infos. val (signedNodeInfoA) = createValidSignedNodeInfo("TestA", requestStorage) @@ -131,19 +110,15 @@ class PersistentNetworkMapStorageTest : TestBase() { val nodeInfoHashA = nodeInfoStorage.putNodeInfo(signedNodeInfoA) val nodeInfoHashB = nodeInfoStorage.putNodeInfo(signedNodeInfoB) - // Create network parameters - val testParameters = testNetworkParameters(emptyList()) - val networkParametersHash = networkMapStorage.saveNetworkParameters(testParameters, testParameters.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate).sig) - val networkMap = NetworkMap(listOf(nodeInfoHashA), networkParametersHash, null) - val signedNetworkMap = networkMap.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate) - - // Sign network map - networkMapStorage.saveNetworkMap(signedNetworkMap) + persistence.transaction { + val entity = session.find(NodeInfoEntity::class.java, nodeInfoHashA.toString()) + session.merge(entity.copy(isCurrent = false)) + } // when - val validNodeInfoHash = networkMapStorage.getActiveNodeInfoHashes() + val validNodeInfoHashes = networkMapStorage.getActiveNodeInfoHashes() // then - assertThat(validNodeInfoHash).containsOnly(nodeInfoHashA, nodeInfoHashB) + assertThat(validNodeInfoHashes).containsOnly(nodeInfoHashB) } -} \ No newline at end of file +} 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 ae37435475..f11d2b3b4f 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 @@ -13,18 +13,16 @@ package com.r3.corda.networkmanage.common.signer import com.nhaarman.mockito_kotlin.* import com.r3.corda.networkmanage.TestBase import com.r3.corda.networkmanage.common.persistence.NetworkMapStorage +import com.r3.corda.networkmanage.createNetworkMapEntity +import com.r3.corda.networkmanage.createNetworkParametersEntity import net.corda.core.crypto.Crypto import net.corda.core.crypto.SecureHash -import net.corda.core.crypto.sha256 import net.corda.core.internal.DigitalSignatureWithCert -import net.corda.core.internal.signWithCert import net.corda.core.serialization.serialize import net.corda.nodeapi.internal.createDevNetworkMapCa import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair -import net.corda.nodeapi.internal.network.NetworkMap -import net.corda.nodeapi.internal.network.SignedNetworkMap +import net.corda.nodeapi.internal.network.NetworkMapAndSigned import net.corda.nodeapi.internal.network.SignedNetworkParameters -import net.corda.nodeapi.internal.network.verifiedNetworkMapCert import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.internal.createDevIntermediateCaCertPath import org.assertj.core.api.Assertions.assertThat @@ -39,13 +37,13 @@ class NetworkMapSignerTest : TestBase() { private lateinit var networkMapSigner: NetworkMapSigner private lateinit var rootCaCert: X509Certificate - private lateinit var networkMapCa: CertificateAndKeyPair + private lateinit var signingCertAndKeyPair: CertificateAndKeyPair @Before fun setUp() { val (rootCa) = createDevIntermediateCaCertPath() rootCaCert = rootCa.certificate - networkMapCa = createDevNetworkMapCa(rootCa) + signingCertAndKeyPair = createDevNetworkMapCa(rootCa) signer = mock() networkMapStorage = mock() networkMapSigner = NetworkMapSigner(networkMapStorage, signer) @@ -54,20 +52,20 @@ class NetworkMapSignerTest : TestBase() { @Test fun `signNetworkMap builds and signs network map and network parameters`() { // given - val signedNodeInfoHashes = listOf(SecureHash.randomSHA256(), SecureHash.randomSHA256()) - val currentParameters = testNetworkParameters(emptyList(), minimumPlatformVersion = 1) - val latestNetworkParameters = testNetworkParameters(emptyList(), minimumPlatformVersion = 2) - val networkMap = NetworkMap(signedNodeInfoHashes, currentParameters.serialize().hash, null) - val signedNetworkMap = networkMap.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate) - whenever(networkMapStorage.getCurrentNetworkMap()).thenReturn(signedNetworkMap) - whenever(networkMapStorage.getActiveNodeInfoHashes()).thenReturn(signedNodeInfoHashes) - whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(latestNetworkParameters) - whenever(networkMapStorage.getNetworkParametersOfNetworkMap()).thenReturn(currentParameters.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate)) + val nodeInfoHashes = listOf(SecureHash.randomSHA256(), SecureHash.randomSHA256()) + val activeNetParams = testNetworkParameters(minimumPlatformVersion = 1) + val latestNetParams = testNetworkParameters(minimumPlatformVersion = 2) + val activeNetParamsEntity = createNetworkParametersEntity(signingCertAndKeyPair, activeNetParams) + val latestNetParamsEntity = createNetworkParametersEntity(signingCertAndKeyPair, latestNetParams) + val netMapEntity = createNetworkMapEntity(signingCertAndKeyPair, activeNetParamsEntity, nodeInfoHashes) + whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(latestNetParamsEntity) + whenever(networkMapStorage.getActiveNodeInfoHashes()).thenReturn(nodeInfoHashes) + whenever(networkMapStorage.getActiveNetworkMap()).thenReturn(netMapEntity) whenever(signer.signBytes(any())).then { - DigitalSignatureWithCert(networkMapCa.certificate, Crypto.doSign(networkMapCa.keyPair.private, it.arguments[0] as ByteArray)) + DigitalSignatureWithCert(signingCertAndKeyPair.certificate, Crypto.doSign(signingCertAndKeyPair.keyPair.private, it.arguments[0] as ByteArray)) } - whenever(signer.signObject(latestNetworkParameters)).then { - val serialised = latestNetworkParameters.serialize() + whenever(signer.signObject(latestNetParams)).then { + val serialised = latestNetParams.serialize() SignedNetworkParameters(serialised, signer.signBytes(serialised.bytes)) } @@ -78,48 +76,43 @@ class NetworkMapSignerTest : TestBase() { // Verify networkMapStorage calls verify(networkMapStorage).getActiveNodeInfoHashes() verify(networkMapStorage).getLatestNetworkParameters() - verify(networkMapStorage).getNetworkParametersOfNetworkMap() - argumentCaptor().apply { - verify(networkMapStorage).saveNetworkMap(capture()) - val capturedNetworkMap = firstValue.verifiedNetworkMapCert(rootCaCert) - assertEquals(latestNetworkParameters.serialize().hash, capturedNetworkMap.networkParameterHash) - assertEquals(signedNodeInfoHashes.size, capturedNetworkMap.nodeInfoHashes.size) - assertThat(capturedNetworkMap.nodeInfoHashes).containsAll(signedNodeInfoHashes) + argumentCaptor().apply { + verify(networkMapStorage).saveNewActiveNetworkMap(capture()) + val capturedNetworkMap = firstValue.networkMap + assertEquals(latestNetParams.serialize().hash, capturedNetworkMap.networkParameterHash) + assertThat(capturedNetworkMap.nodeInfoHashes).isEqualTo(nodeInfoHashes) } } @Test fun `signNetworkMap does NOT create a new network map if there are no changes`() { // given - val networkParameters = testNetworkParameters(emptyList()) - val networkMapParametersHash = networkParameters.serialize().bytes.sha256() - val networkMap = NetworkMap(emptyList(), networkMapParametersHash, null) - val signedNetworkMap = networkMap.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate) - whenever(networkMapStorage.getCurrentNetworkMap()).thenReturn(signedNetworkMap) + val netParamsEntity = createNetworkParametersEntity(signingCertAndKeyPair) + val netMapEntity = createNetworkMapEntity(signingCertAndKeyPair, netParamsEntity, emptyList()) + whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(netParamsEntity) whenever(networkMapStorage.getActiveNodeInfoHashes()).thenReturn(emptyList()) - whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(networkParameters) - whenever(networkMapStorage.getNetworkParametersOfNetworkMap()).thenReturn(networkParameters.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate)) + whenever(networkMapStorage.getActiveNetworkMap()).thenReturn(netMapEntity) // when networkMapSigner.signNetworkMap() // then // Verify networkMapStorage is not called - verify(networkMapStorage, never()).saveNetworkMap(any()) + verify(networkMapStorage, never()).saveNewActiveNetworkMap(any()) } @Test fun `signNetworkMap creates a new network map if there is no current network map`() { // given - val networkParameters = testNetworkParameters(emptyList()) - whenever(networkMapStorage.getCurrentNetworkMap()).thenReturn(null) + val netParams = testNetworkParameters() + whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(createNetworkParametersEntity(signingCertAndKeyPair, netParams)) whenever(networkMapStorage.getActiveNodeInfoHashes()).thenReturn(emptyList()) - whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(networkParameters) + whenever(networkMapStorage.getActiveNetworkMap()).thenReturn(null) whenever(signer.signBytes(any())).then { - DigitalSignatureWithCert(networkMapCa.certificate, Crypto.doSign(networkMapCa.keyPair.private, it.arguments[0] as ByteArray)) + DigitalSignatureWithCert(signingCertAndKeyPair.certificate, Crypto.doSign(signingCertAndKeyPair.keyPair.private, it.arguments[0] as ByteArray)) } - whenever(signer.signObject(networkParameters)).then { - val serialised = networkParameters.serialize() + whenever(signer.signObject(netParams)).then { + val serialised = netParams.serialize() SignedNetworkParameters(serialised, signer.signBytes(serialised.bytes)) } // when @@ -129,10 +122,9 @@ class NetworkMapSignerTest : TestBase() { // Verify networkMapStorage calls verify(networkMapStorage).getActiveNodeInfoHashes() verify(networkMapStorage).getLatestNetworkParameters() - argumentCaptor().apply { - verify(networkMapStorage).saveNetworkMap(capture()) - val networkMap = firstValue.verifiedNetworkMapCert(rootCaCert) - assertEquals(networkParameters.serialize().hash, networkMap.networkParameterHash) + argumentCaptor().apply { + verify(networkMapStorage).saveNewActiveNetworkMap(capture()) + assertEquals(netParams.serialize().hash, firstValue.networkMap.networkParameterHash) } } -} \ No newline at end of file +} diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/webservice/NetworkMapWebServiceTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/webservice/NetworkMapWebServiceTest.kt index 86ec8a0d3a..4d595e1161 100644 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/webservice/NetworkMapWebServiceTest.kt +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/webservice/NetworkMapWebServiceTest.kt @@ -10,12 +10,12 @@ package com.r3.corda.networkmanage.doorman.webservice -import com.nhaarman.mockito_kotlin.any import com.nhaarman.mockito_kotlin.mock import com.nhaarman.mockito_kotlin.times import com.nhaarman.mockito_kotlin.verify import com.r3.corda.networkmanage.common.persistence.NetworkMapStorage import com.r3.corda.networkmanage.common.persistence.NodeInfoStorage +import com.r3.corda.networkmanage.createNetworkMapEntity import com.r3.corda.networkmanage.doorman.NetworkManagementWebServer import com.r3.corda.networkmanage.doorman.NetworkMapConfig import net.corda.core.crypto.SecureHash.Companion.randomSHA256 @@ -23,14 +23,12 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.internal.checkOkResponse import net.corda.core.internal.openHttpConnection import net.corda.core.internal.responseAs -import net.corda.core.internal.signWithCert import net.corda.core.serialization.serialize import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.seconds import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.createDevNetworkMapCa import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair -import net.corda.nodeapi.internal.network.NetworkMap import net.corda.nodeapi.internal.network.SignedNetworkMap import net.corda.nodeapi.internal.network.SignedNetworkParameters import net.corda.nodeapi.internal.network.verifiedNetworkMapCert @@ -54,7 +52,7 @@ class NetworkMapWebServiceTest { val testSerialization = SerializationEnvironmentRule(true) private lateinit var rootCaCert: X509Certificate - private lateinit var networkMapCa: CertificateAndKeyPair + private lateinit var signingCertAndKeyPair: CertificateAndKeyPair private val testNetworkMapConfig = NetworkMapConfig(10.seconds.toMillis(), 10.seconds.toMillis()) @@ -62,16 +60,15 @@ class NetworkMapWebServiceTest { fun init() { val (rootCa) = createDevIntermediateCaCertPath() rootCaCert = rootCa.certificate - networkMapCa = createDevNetworkMapCa(rootCa) + signingCertAndKeyPair = createDevNetworkMapCa(rootCa) } @Test fun `submit nodeInfo`() { val networkMapStorage: NetworkMapStorage = mock { - val networkParameter = testNetworkParameters(emptyList()).signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate) - on { getSignedNetworkParameters(any()) }.thenReturn(networkParameter) - on { getCurrentNetworkMap() }.thenReturn(NetworkMap(emptyList(), networkParameter.raw.hash, null).signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate)) + on { getActiveNetworkMap() }.thenReturn(createNetworkMapEntity()) } + // Create node info. val (_, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name("Test", "London", "GB")) @@ -86,9 +83,7 @@ class NetworkMapWebServiceTest { @Test fun `submit old nodeInfo`() { val networkMapStorage: NetworkMapStorage = mock { - val networkParameter = testNetworkParameters(emptyList(), minimumPlatformVersion = 2).signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate) - on { getSignedNetworkParameters(any()) }.thenReturn(networkParameter) - on { getCurrentNetworkMap() }.thenReturn(NetworkMap(emptyList(), networkParameter.raw.hash, null).signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate)) + on { getActiveNetworkMap() }.thenReturn(createNetworkMapEntity(networkParameters = testNetworkParameters(minimumPlatformVersion = 2))) } // Create node info. val (_, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name("Test", "London", "GB"), platformVersion = 1) @@ -102,9 +97,9 @@ class NetworkMapWebServiceTest { } @Test - fun `submit nodeInfo when no network parameters`() { + fun `submit nodeInfo when no network map`() { val networkMapStorage: NetworkMapStorage = mock { - on { getNetworkParametersOfNetworkMap() }.thenReturn(null) + on { getActiveNetworkMap() }.thenReturn(null) } // Create node info. val (_, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name("Test", "London", "GB"), platformVersion = 1) @@ -119,18 +114,19 @@ class NetworkMapWebServiceTest { @Test fun `get network map`() { - val networkMap = NetworkMap(listOf(randomSHA256(), randomSHA256()), randomSHA256(), null) - val signedNetworkMap = networkMap.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate) + val networkMapEntity = createNetworkMapEntity( + signingCertAndKeyPair = signingCertAndKeyPair, + nodeInfoHashes = listOf(randomSHA256(), randomSHA256())) val networkMapStorage: NetworkMapStorage = mock { - on { getCurrentNetworkMap() }.thenReturn(signedNetworkMap) + on { getActiveNetworkMap() }.thenReturn(networkMapEntity) } NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NetworkMapWebService(mock(), networkMapStorage, testNetworkMapConfig)).use { it.start() val signedNetworkMapResponse = it.doGet("") - verify(networkMapStorage, times(1)).getCurrentNetworkMap() - assertEquals(signedNetworkMapResponse.verifiedNetworkMapCert(rootCaCert), networkMap) + verify(networkMapStorage, times(1)).getActiveNetworkMap() + assertEquals(signedNetworkMapResponse.verifiedNetworkMapCert(rootCaCert), networkMapEntity.toNetworkMap()) } } @@ -144,10 +140,8 @@ class NetworkMapWebServiceTest { } // Mock network map storage - val networkMap = NetworkMap(listOf(nodeInfoHash), randomSHA256(), null) - val signedNetworkMap = networkMap.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate) val networkMapStorage: NetworkMapStorage = mock { - on { getCurrentNetworkMap() }.thenReturn(signedNetworkMap) + on { getActiveNetworkMap() }.thenReturn(createNetworkMapEntity(nodeInfoHashes = listOf(nodeInfoHash))) } NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NetworkMapWebService(nodeInfoStorage, networkMapStorage, testNetworkMapConfig)).use { @@ -165,7 +159,7 @@ class NetworkMapWebServiceTest { @Test fun `get network parameters`() { val networkParameters = testNetworkParameters(emptyList()) - val signedNetworkParameters = networkParameters.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate) + val signedNetworkParameters = signingCertAndKeyPair.sign(networkParameters) val networkParametersHash = signedNetworkParameters.raw.hash val networkMapStorage: NetworkMapStorage = mock { @@ -177,7 +171,7 @@ class NetworkMapWebServiceTest { val netParamsResponse = it.doGet("network-parameters/$networkParametersHash") verify(networkMapStorage, times(1)).getSignedNetworkParameters(networkParametersHash) assertThat(netParamsResponse.verified()).isEqualTo(networkParameters) - assertThat(netParamsResponse.sig.by).isEqualTo(networkMapCa.certificate) + assertThat(netParamsResponse.sig.by).isEqualTo(signingCertAndKeyPair.certificate) assertThatExceptionOfType(IOException::class.java) .isThrownBy { it.doGet("network-parameters/${randomSHA256()}") } .withMessageContaining("404") @@ -198,4 +192,4 @@ class NetworkMapWebServiceTest { private inline fun NetworkManagementWebServer.doGet(path: String): T { return URL("http://$hostAndPort/network-map/$path").openHttpConnection().responseAs() } -} \ No newline at end of file +} diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt index f2cba25503..0abe86076e 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt @@ -14,10 +14,7 @@ import net.corda.core.CordaOID import net.corda.core.crypto.Crypto import net.corda.core.crypto.SignatureScheme import net.corda.core.crypto.random63BitValue -import net.corda.core.internal.CertRole -import net.corda.core.internal.reader -import net.corda.core.internal.uncheckedCast -import net.corda.core.internal.writer +import net.corda.core.internal.* import net.corda.core.utilities.days import net.corda.core.utilities.millis import org.bouncycastle.asn1.* @@ -425,4 +422,6 @@ enum class CertificateType(val keyUsage: KeyUsage, vararg val purposes: KeyPurpo ) } -data class CertificateAndKeyPair(val certificate: X509Certificate, val keyPair: KeyPair) +data class CertificateAndKeyPair(val certificate: X509Certificate, val keyPair: KeyPair) { + fun sign(obj: T): SignedDataWithCert = obj.signWithCert(keyPair.private, certificate) +} diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkMap.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkMap.kt index 368dbd1a4b..a32f034d5d 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkMap.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/NetworkMap.kt @@ -12,10 +12,13 @@ package net.corda.nodeapi.internal.network import net.corda.core.crypto.SecureHash import net.corda.core.internal.CertRole +import net.corda.core.internal.DigitalSignatureWithCert import net.corda.core.internal.SignedDataWithCert +import net.corda.core.internal.signWithCert import net.corda.core.node.NetworkParameters import net.corda.core.node.NodeInfo import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.SerializedBytes import net.corda.nodeapi.internal.crypto.X509Utilities import java.security.cert.X509Certificate import java.time.Instant @@ -63,3 +66,10 @@ fun SignedDataWithCert.verifiedNetworkMapCert(rootCert: X509Certifi X509Utilities.validateCertificateChain(rootCert, sig.by, rootCert) return verified() } + +class NetworkMapAndSigned private constructor(val networkMap: NetworkMap, val signed: SignedNetworkMap) { + constructor(networkMap: NetworkMap, signer: (SerializedBytes) -> DigitalSignatureWithCert) : this(networkMap, networkMap.signWithCert(signer)) + constructor(signed: SignedNetworkMap) : this(signed.verified(), signed) + operator fun component1(): NetworkMap = networkMap + operator fun component2(): SignedNetworkMap = signed +}