diff --git a/network-management/build.gradle b/network-management/build.gradle index cc3fa459cf..e997881302 100644 --- a/network-management/build.gradle +++ b/network-management/build.gradle @@ -1,7 +1,7 @@ ext { // We use Corda release artifact dependencies instead of project dependencies to make sure each doorman releases are // aligned with the corresponding Corda release. - corda_dependency_version = '2.0-20171108.000038-27' + corda_dependency_version = '3.0-20171115.000100-7' } version "$corda_dependency_version" @@ -125,7 +125,6 @@ dependencies { testCompile "com.nhaarman:mockito-kotlin:0.6.1" testRuntime "net.corda:corda-rpc:$corda_dependency_version" testCompile "com.spotify:docker-client:8.9.1" - integrationTestCompile "net.corda:corda-test-common:$corda_dependency_version" integrationTestRuntime "net.corda:corda-rpc:$corda_dependency_version" compile('com.atlassian.jira:jira-rest-java-client-core:4.0.0') { diff --git a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/HsmSimulator.kt b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/HsmSimulator.kt index 39d68a5a2f..232377ec09 100644 --- a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/HsmSimulator.kt +++ b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/HsmSimulator.kt @@ -1,5 +1,8 @@ package com.r3.corda.networkmanage +import CryptoServerAPI.CryptoServerException +import com.r3.corda.networkmanage.hsm.authentication.CryptoServerProviderConfig +import com.r3.corda.networkmanage.hsm.authentication.createProvider import com.spotify.docker.client.DefaultDockerClient import com.spotify.docker.client.DockerClient import com.spotify.docker.client.messages.ContainerConfig @@ -29,7 +32,9 @@ data class CryptoUserCredentials(val username: String, val password: String) */ class HsmSimulator(private val serverAddress: String = DEFAULT_SERVER_ADDRESS, private val imageRepoTag: String = DEFAULT_IMAGE_REPO_TAG, - private val imageVersion: String = DEFAULT_IMAGE_VERSION) : ExternalResource() { + private val imageVersion: String = DEFAULT_IMAGE_VERSION, + private val registryUser: String? = REGISTRY_USERNAME, + private val registryPass: String? = REGISTRY_PASSWORD) : ExternalResource() { private companion object { val DEFAULT_SERVER_ADDRESS = "corda.azurecr.io" @@ -46,6 +51,9 @@ class HsmSimulator(private val serverAddress: String = DEFAULT_SERVER_ADDRESS, val REGISTRY_PASSWORD = System.getenv("AZURE_CR_PASS") val log = loggerFor() + + private val HSM_STARTUP_SLEEP_INTERVAL_MS = 500L + private val HSM_STARTUP_POLL_MAX_COUNT = 10; } private val localHostAndPortBinding = freeLocalHostAndPort() @@ -53,8 +61,8 @@ class HsmSimulator(private val serverAddress: String = DEFAULT_SERVER_ADDRESS, private var containerId: String? = null override fun before() { - assumeFalse("Docker registry username is not set!. Skipping the test.", REGISTRY_USERNAME.isNullOrBlank()) - assumeFalse("Docker registry password is not set!. Skipping the test.", REGISTRY_PASSWORD.isNullOrBlank()) + assumeFalse("Docker registry username is not set!. Skipping the test.", registryUser.isNullOrBlank()) + assumeFalse("Docker registry password is not set!. Skipping the test.", registryPass.isNullOrBlank()) docker = DefaultDockerClient.fromEnv().build().pullHsmSimulatorImageFromRepository() containerId = docker.createContainer() docker.startHsmSimulatorContainer() @@ -96,17 +104,41 @@ class HsmSimulator(private val serverAddress: String = DEFAULT_SERVER_ADDRESS, if (containerId != null) { log.debug("Starting container $containerId...") this.startContainer(containerId) + pollAndWaitForHsmSimulator() } } + private fun pollAndWaitForHsmSimulator() { + val config = CryptoServerProviderConfig( + Device = "${localHostAndPortBinding.port}@${localHostAndPortBinding.host}", + KeyGroup = "*", + KeySpecifier = -1 + ) + var pollCount = HSM_STARTUP_POLL_MAX_COUNT + while (pollCount > 0) { + val provider = createProvider(config) + try { + provider.loginPassword(CRYPTO_USER, CRYPTO_PASSWORD) + provider.cryptoServer.authState + return + } catch (e: CryptoServerException) { + pollCount-- + Thread.sleep(HSM_STARTUP_SLEEP_INTERVAL_MS) + } finally { + provider.logoff() + } + } + throw IllegalStateException("Unable to obtain connection to initialised HSM Simulator") + } + private fun getImageFullName() = "$imageRepoTag:$imageVersion" private fun DockerClient.pullHsmSimulatorImageFromRepository(): DockerClient { this.pull(imageRepoTag, RegistryAuth.builder() .serverAddress(serverAddress) - .username(REGISTRY_USERNAME) - .password(REGISTRY_PASSWORD) + .username(registryUser) + .password(registryPass) .build()) return this } diff --git a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/DoormanIntegrationTest.kt b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/DoormanIntegrationTest.kt index 0ab970ff15..103da7669e 100644 --- a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/DoormanIntegrationTest.kt +++ b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/DoormanIntegrationTest.kt @@ -2,63 +2,73 @@ package com.r3.corda.networkmanage.doorman import com.nhaarman.mockito_kotlin.whenever import com.r3.corda.networkmanage.common.persistence.SchemaService +import com.r3.corda.networkmanage.common.utils.buildCertPath import com.r3.corda.networkmanage.common.utils.toX509Certificate import com.r3.corda.networkmanage.doorman.signer.LocalSigner import net.corda.core.crypto.Crypto import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.SignedData +import net.corda.core.crypto.sign import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.PartyAndCertificate import net.corda.core.internal.cert +import net.corda.core.node.NodeInfo +import net.corda.core.serialization.serialize import net.corda.core.utilities.NetworkHostAndPort +import net.corda.node.services.network.NetworkMapClient import net.corda.node.utilities.* import net.corda.node.utilities.registration.HTTPNetworkRegistrationService import net.corda.node.utilities.registration.NetworkRegistrationHelper import net.corda.testing.ALICE +import net.corda.testing.SerializationEnvironmentRule +import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.testNodeConfiguration +import org.bouncycastle.cert.X509CertificateHolder +import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder import java.net.URL import java.util.* import kotlin.test.assertEquals -import net.corda.testing.common.internal.testNetworkParameters +import kotlin.test.assertNotNull class DoormanIntegrationTest { @Rule @JvmField val tempFolder = TemporaryFolder() + @Rule + @JvmField + val testSerialization = SerializationEnvironmentRule(true) + @Test fun `initial registration`() { - val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Integration Test Corda Node Root CA", organisation = "R3 Ltd", locality = "London", country = "GB"), rootCAKey) - val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey, - CordaX500Name(commonName = "Integration Test Corda Node Intermediate CA", locality = "London", country = "GB", organisation = "R3 Ltd"), intermediateCAKey.public) - - val database = configureDatabase(makeTestDataSourceProperties(), null, { - // Identity service not needed doorman, corda persistence is not very generic. - throw UnsupportedOperationException() - }, SchemaService()) - val signer = LocalSigner(intermediateCAKey, arrayOf(intermediateCACert.toX509Certificate(), rootCACert.toX509Certificate())) + val rootCertAndKey = createDoormanRootCertificateAndKeyPair() + val intermediateCertAndKey = createDoormanIntermediateCertificateAndKeyPair(rootCertAndKey) //Start doorman server - val doorman = startDoorman(NetworkHostAndPort("localhost", 0), database, true, testNetworkParameters(emptyList()), signer, 2, 10,null) + val doorman = startDoorman(intermediateCertAndKey, rootCertAndKey.certificate) // Start Corda network registration. val config = testNodeConfiguration( baseDirectory = tempFolder.root.toPath(), myLegalName = ALICE.name).also { - whenever(it.certificateSigningService).thenReturn(URL("http://localhost:${doorman.hostAndPort.port}")) + val doormanHostAndPort = doorman.hostAndPort + whenever(it.compatibilityZoneURL).thenReturn(URL("http://${doormanHostAndPort.host}:${doormanHostAndPort.port}")) whenever(it.emailAddress).thenReturn("iTest@R3.com") } - NetworkRegistrationHelper(config, HTTPNetworkRegistrationService(config.certificateSigningService)).buildKeystore() + NetworkRegistrationHelper(config, HTTPNetworkRegistrationService(config.compatibilityZoneURL!!)).buildKeystore() // Checks the keystore are created with the right certificates and keys. assert(config.nodeKeystore.toFile().exists()) assert(config.sslKeystore.toFile().exists()) assert(config.trustStoreFile.toFile().exists()) + val intermediateCACert = intermediateCertAndKey.certificate + val rootCACert = rootCertAndKey.certificate + loadKeyStore(config.nodeKeystore, config.keyStorePassword).apply { assert(containsAlias(X509Utilities.CORDA_CLIENT_CA)) assertEquals(ALICE.name.copy(commonName = X509Utilities.CORDA_CLIENT_CA_CN).x500Principal, getX509Certificate(X509Utilities.CORDA_CLIENT_CA).subjectX500Principal) @@ -75,15 +85,91 @@ class DoormanIntegrationTest { assert(containsAlias(X509Utilities.CORDA_ROOT_CA)) assertEquals(rootCACert.cert.subjectX500Principal, getX509Certificate(X509Utilities.CORDA_ROOT_CA).subjectX500Principal) } + doorman.close() } - private fun makeTestDataSourceProperties(nodeName: String = SecureHash.randomSHA256().toString()): Properties { - val props = Properties() - props.setProperty("dataSourceClassName", "org.h2.jdbcx.JdbcDataSource") - props.setProperty("dataSource.url", "jdbc:h2:mem:${nodeName}_persistence;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE") - props.setProperty("dataSource.user", "sa") - props.setProperty("dataSource.password", "") - return props + //TODO remove @Ignore once PR https://github.com/corda/corda/pull/2054 is merged + @Test + @Ignore + fun `nodeInfo is published to the network map`() { + // Given + val rootCertAndKey = createDoormanRootCertificateAndKeyPair() + val intermediateCertAndKey = createDoormanIntermediateCertificateAndKeyPair(rootCertAndKey) + + //Start doorman server + val doorman = startDoorman(intermediateCertAndKey, rootCertAndKey.certificate) + val doormanHostAndPort = doorman.hostAndPort + + // Start Corda network registration. + val config = testNodeConfiguration( + baseDirectory = tempFolder.root.toPath(), + myLegalName = ALICE.name).also { + whenever(it.compatibilityZoneURL).thenReturn(URL("http://${doormanHostAndPort.host}:${doormanHostAndPort.port}")) + whenever(it.emailAddress).thenReturn("iTest@R3.com") + } + + NetworkRegistrationHelper(config, HTTPNetworkRegistrationService(config.compatibilityZoneURL!!)).buildKeystore() + + // Publish NodeInfo + val networkMapClient = NetworkMapClient(config.compatibilityZoneURL!!) + val certs = loadKeyStore(config.nodeKeystore, config.keyStorePassword).getCertificateChain(X509Utilities.CORDA_CLIENT_CA) + val keyPair = loadKeyStore(config.nodeKeystore, config.keyStorePassword).getKeyPair(X509Utilities.CORDA_CLIENT_CA, config.keyStorePassword) + val nodeInfo = NodeInfo(listOf(NetworkHostAndPort("my.company.com", 1234)), listOf(PartyAndCertificate(buildCertPath(*certs))), 1, serial = 1L) + val nodeInfoBytes = nodeInfo.serialize() + + // When + networkMapClient.publish(SignedData(nodeInfoBytes, keyPair.sign(nodeInfoBytes))) + + // Then + val networkMapNodeInfo = networkMapClient.getNodeInfo(nodeInfoBytes.hash) + assertNotNull(networkMapNodeInfo) + assertEquals(nodeInfo, networkMapNodeInfo) + + doorman.close() } +} + +fun createDoormanIntermediateCertificateAndKeyPair(rootCertificateAndKeyPair: CertificateAndKeyPair): CertificateAndKeyPair { + val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCertificateAndKeyPair.certificate, rootCertificateAndKeyPair.keyPair, + CordaX500Name(commonName = "Integration Test Corda Node Intermediate CA", + locality = "London", + country = "GB", + organisation = "R3 Ltd"), intermediateCAKey.public) + return CertificateAndKeyPair(intermediateCACert, intermediateCAKey) +} + +fun createDoormanRootCertificateAndKeyPair(): CertificateAndKeyPair { + val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val rootCACert = X509Utilities.createSelfSignedCACertificate( + CordaX500Name(commonName = "Integration Test Corda Node Root CA", + organisation = "R3 Ltd", locality = "London", + country = "GB"), rootCAKey) + return CertificateAndKeyPair(rootCACert, rootCAKey) +} + +fun makeTestDataSourceProperties(nodeName: String = SecureHash.randomSHA256().toString()): Properties { + val props = Properties() + props.setProperty("dataSourceClassName", "org.h2.jdbcx.JdbcDataSource") + props.setProperty("dataSource.url", "jdbc:h2:mem:${nodeName}_persistence;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE") + props.setProperty("dataSource.user", "sa") + props.setProperty("dataSource.password", "") + return props +} + +fun startDoorman(intermediateCACertAndKey: CertificateAndKeyPair, rootCACert: X509CertificateHolder): DoormanServer { + val signer = LocalSigner(intermediateCACertAndKey.keyPair, + arrayOf(intermediateCACertAndKey.certificate.toX509Certificate(), rootCACert.toX509Certificate())) + //Start doorman server + return startDoorman(signer) +} + +fun startDoorman(localSigner: LocalSigner? = null): DoormanServer { + val database = configureDatabase(makeTestDataSourceProperties(), null, { + // Identity service not needed doorman, corda persistence is not very generic. + throw UnsupportedOperationException() + }, SchemaService()) + //Start doorman server + return startDoorman(NetworkHostAndPort("localhost", 0), database, true, testNetworkParameters(emptyList()), localSigner, 2, 30,null) } \ No newline at end of file diff --git a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmTest.kt b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmTest.kt index 55be8b4a2d..1a280ae818 100644 --- a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmTest.kt +++ b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmTest.kt @@ -11,6 +11,7 @@ import com.r3.corda.networkmanage.hsm.configuration.Parameters import org.junit.Before import org.junit.Rule import org.junit.Test +import org.junit.rules.TemporaryFolder import kotlin.test.assertTrue class HsmTest { @@ -18,26 +19,30 @@ class HsmTest { @Rule @JvmField val hsmSimulator: HsmSimulator = HsmSimulator() + val testParameters = Parameters( + dataSourceProperties = mock(), + device = "${hsmSimulator.port}@${hsmSimulator.host}", + keySpecifier = 1, + keyGroup = "*" + ) + + @Rule + @JvmField + val tempFolder = TemporaryFolder() private lateinit var inputReader: InputReader @Before fun setUp() { inputReader = mock() + whenever(inputReader.readLine()).thenReturn(hsmSimulator.cryptoUserCredentials().username) + whenever(inputReader.readPassword(any())).thenReturn(hsmSimulator.cryptoUserCredentials().password) } @Test fun `Authenticator executes the block once user is successfully authenticated`() { // given - val parameters = Parameters( - dataSourceProperties = mock(), - device = "${hsmSimulator.port}@${hsmSimulator.host}", - keySpecifier = 1, - keyGroup = "*" - ) - whenever(inputReader.readLine()).thenReturn(hsmSimulator.cryptoUserCredentials().username) - whenever(inputReader.readPassword(any())).thenReturn(hsmSimulator.cryptoUserCredentials().password) - val authenticator = Authenticator(parameters.createProvider(), inputReader = inputReader) + val authenticator = Authenticator(testParameters.createProvider(), inputReader = inputReader) var executed = false // when @@ -48,6 +53,4 @@ class HsmTest { // then assertTrue(executed) } - - } \ No newline at end of file diff --git a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/SigningServiceIntegrationTest.kt b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/SigningServiceIntegrationTest.kt index ef694f6752..15df345323 100644 --- a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/SigningServiceIntegrationTest.kt +++ b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/SigningServiceIntegrationTest.kt @@ -86,19 +86,20 @@ class SigningServiceIntegrationTest { } @Test - fun `Signing service communicates with Doorman`() { + fun `Signing service signs approved CSRs`() { //Start doorman server val database = configureDatabase(makeTestDataSourceProperties(), null, { // Identity service not needed doorman, corda persistence is not very generic. throw UnsupportedOperationException() }, SchemaService()) - val doorman = startDoorman(NetworkHostAndPort(HOST, 0), database, approveAll = true, approveInterval = 2, signInterval = 10, initialNetworkMapParameters = testNetworkParameters(emptyList())) + val doorman = startDoorman(NetworkHostAndPort(HOST, 0), database, approveAll = true, approveInterval = 2, signInterval = 30, initialNetworkMapParameters = testNetworkParameters(emptyList())) // Start Corda network registration. val config = testNodeConfiguration( baseDirectory = tempFolder.root.toPath(), myLegalName = ALICE.name).also { - whenever(it.certificateSigningService).thenReturn(URL("http://$HOST:${doorman.hostAndPort.port}")) + val doormanHostAndPort = doorman.hostAndPort + whenever(it.compatibilityZoneURL).thenReturn(URL("http://${doormanHostAndPort.host}:${doormanHostAndPort.port}")) } val signingServiceStorage = DBSignedCertificateRequestStorage(configureDatabase(makeTestDataSourceProperties(), makeNotInitialisingTestDatabaseProperties(), { @@ -126,7 +127,7 @@ class SigningServiceIntegrationTest { // [org.hibernate.tool.schema.spi.SchemaManagementException] being thrown as the schema is missing. } } - NetworkRegistrationHelper(config, HTTPNetworkRegistrationService(config.certificateSigningService)).buildKeystore() + NetworkRegistrationHelper(config, HTTPNetworkRegistrationService(config.compatibilityZoneURL!!)).buildKeystore() verify(hsmSigner).sign(any()) doorman.close() } @@ -166,9 +167,9 @@ class SigningServiceIntegrationTest { 3 -> CHARLIE.name else -> throw IllegalArgumentException("Unrecognised option") }).also { - whenever(it.certificateSigningService).thenReturn(URL("http://$HOST:${doorman.hostAndPort.port}")) + whenever(it.compatibilityZoneURL).thenReturn(URL("http://$HOST:${doorman.hostAndPort.port}")) } - NetworkRegistrationHelper(config, HTTPNetworkRegistrationService(config.certificateSigningService)).buildKeystore() + NetworkRegistrationHelper(config, HTTPNetworkRegistrationService(config.compatibilityZoneURL!!)).buildKeystore() } }.map { it.join() } doorman.close() 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 967cea7f47..a60a51c5c0 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 @@ -33,10 +33,10 @@ interface NetworkMapStorage { fun saveNetworkMap(signedNetworkMap: SignedNetworkMap) /** - * Retrieve all node info hashes for all signed node info with valid certificates, + * Retrieve all node info hashes for all node info with valid certificates, * that are not associated with any network map yet. */ - fun getDetachedSignedAndValidNodeInfoHashes(): List + fun getDetachedAndValidNodeInfoHashes(): List /** * Retrieve network parameters by their hash. diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/NodeInfoStorage.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/NodeInfoStorage.kt index d768f4c780..42b2cb10fb 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/NodeInfoStorage.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/NodeInfoStorage.kt @@ -16,41 +16,16 @@ interface NodeInfoStorage { */ fun getCertificatePath(publicKeyHash: SecureHash): CertPath? - /** - * Obtain list of registered node info hashes that haven't been signed yet and have valid certificates. - */ - fun getUnsignedNodeInfoHashes(): List - - /** - * Similar to [getUnsignedNodeInfoHashes] but instead of hashes, map of node info bytes is returned. - * @return map of node info hashes to their corresponding node info bytes - */ - fun getUnsignedNodeInfoBytes(): Map - - /** - * Retrieve node info using nodeInfo's hash - * @return [NodeInfo] or null if the node info is not registered. - */ - fun getNodeInfo(nodeInfoHash: SecureHash): NodeInfo? - /** * Retrieve node info together with its signature using nodeInfo's hash * @return [NodeInfo] or null if the node info is not registered. */ - fun getSignedNodeInfo(nodeInfoHash: SecureHash): SignedData? + fun getNodeInfo(nodeInfoHash: SecureHash): SignedData? /** * The [nodeInfo] is keyed by the public key, old node info with the same public key will be replaced by the new node info. - * @param nodeInfo node info to be stored - * @param signature (optional) signature associated with the node info + * @param signedNodeInfo signed node info data to be stored * @return hash for the newly created node info entry */ - fun putNodeInfo(nodeInfo: NodeInfo, signature: DigitalSignature? = null): SecureHash - - /** - * Stores the signature for the given node info hash. - * @param nodeInfoHash node info hash which signature corresponds to - * @param signature signature for the node info - */ - fun signNodeInfo(nodeInfoHash: SecureHash, signature: DigitalSignature.WithKey) + fun putNodeInfo(signedNodeInfo: SignedData): SecureHash } \ No newline at end of file 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 73b41433bf..bca4e9accb 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 @@ -98,7 +98,7 @@ class PersistentNetworkMapStorage(private val database: CordaPersistence) : Netw session.createQuery(query).resultList.first() } - override fun getDetachedSignedAndValidNodeInfoHashes(): List = database.transaction { + override fun getDetachedAndValidNodeInfoHashes(): List = database.transaction { val builder = session.criteriaBuilder // Get signed NodeInfoEntities val query = builder.createQuery(NodeInfoEntity::class.java).run { 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 1714dd4796..33a268c324 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 @@ -5,16 +5,11 @@ import com.r3.corda.networkmanage.common.persistence.entity.CertificateSigningRe import com.r3.corda.networkmanage.common.persistence.entity.NodeInfoEntity import com.r3.corda.networkmanage.common.utils.buildCertPath import com.r3.corda.networkmanage.common.utils.hashString -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.node.NodeInfo import net.corda.core.serialization.SerializedBytes -import net.corda.core.serialization.serialize import net.corda.node.utilities.CordaPersistence -import org.hibernate.Session -import org.hibernate.jpa.QueryHints import java.security.cert.CertPath import java.sql.Connection @@ -22,7 +17,8 @@ import java.sql.Connection * Database implementation of the [NetworkMapStorage] interface */ class PersistentNodeInfoStorage(private val database: CordaPersistence) : NodeInfoStorage { - override fun putNodeInfo(nodeInfo: NodeInfo, signature: DigitalSignature?): SecureHash = database.transaction(Connection.TRANSACTION_SERIALIZABLE) { + override fun putNodeInfo(signedNodeInfo: SignedData): SecureHash = database.transaction(Connection.TRANSACTION_SERIALIZABLE) { + val nodeInfo = signedNodeInfo.verified() val publicKeyHash = nodeInfo.legalIdentities.first().owningKey.hashString() val request = singleRequestWhere(CertificateDataEntity::class.java) { builder, path -> val certPublicKeyHashEq = builder.equal(path.get(CertificateDataEntity::publicKeyHash.name), publicKeyHash) @@ -31,7 +27,7 @@ class PersistentNodeInfoStorage(private val database: CordaPersistence) : NodeIn } request ?: throw IllegalArgumentException("CSR data missing for provided node info: $nodeInfo") /* - * Delete any previous [HashedNodeInfo] instance for this CSR + * Delete any previous [NodeInfoEntity] instance for this CSR * Possibly it should be moved at the network signing process at the network signing process * as for a while the network map will have invalid entries (i.e. hashes for node info which have been * removed). Either way, there will be a period of time when the network map data will be invalid @@ -40,18 +36,19 @@ class PersistentNodeInfoStorage(private val database: CordaPersistence) : NodeIn deleteRequest(NodeInfoEntity::class.java) { builder, path -> builder.equal(path.get(NodeInfoEntity::certificateSigningRequest.name), request.certificateSigningRequest) } - val serializedNodeInfo = nodeInfo.serialize().bytes - val hash = serializedNodeInfo.sha256() + val hash = signedNodeInfo.raw.hash val hashedNodeInfo = NodeInfoEntity( nodeInfoHash = hash.toString(), certificateSigningRequest = request.certificateSigningRequest, - nodeInfoBytes = serializedNodeInfo, - signatureBytes = signature?.bytes) + nodeInfoBytes = signedNodeInfo.raw.bytes, + signatureBytes = signedNodeInfo.sig.bytes, + signaturePublicKeyBytes = signedNodeInfo.sig.by.encoded, + signaturePublicKeyAlgorithm = signedNodeInfo.sig.by.algorithm) session.save(hashedNodeInfo) hash } - override fun getSignedNodeInfo(nodeInfoHash: SecureHash): SignedData? = database.transaction { + override fun getNodeInfo(nodeInfoHash: SecureHash): SignedData? = database.transaction { val nodeInfoEntity = session.find(NodeInfoEntity::class.java, nodeInfoHash.toString()) if (nodeInfoEntity?.signatureBytes == null) { null @@ -60,18 +57,6 @@ class PersistentNodeInfoStorage(private val database: CordaPersistence) : NodeIn } } - override fun getNodeInfo(nodeInfoHash: SecureHash): NodeInfo? = database.transaction { - session.find(NodeInfoEntity::class.java, nodeInfoHash.toString())?.nodeInfo() - } - - override fun getUnsignedNodeInfoBytes(): Map { - return getUnsignedNodeInfoEntities().associate { SecureHash.parse(it.nodeInfoHash) to it.nodeInfoBytes } - } - - override fun getUnsignedNodeInfoHashes(): List { - return getUnsignedNodeInfoEntities().map { SecureHash.parse(it.nodeInfoHash) } - } - override fun getCertificatePath(publicKeyHash: SecureHash): CertPath? { return database.transaction { val builder = session.criteriaBuilder @@ -86,44 +71,4 @@ class PersistentNodeInfoStorage(private val database: CordaPersistence) : NodeIn session.createQuery(query).uniqueResultOptional().orElseGet { null }?.let { buildCertPath(it) } } } - - override fun signNodeInfo(nodeInfoHash: SecureHash, signature: DigitalSignature.WithKey) { - database.transaction { - val nodeInfoEntity = session.find(NodeInfoEntity::class.java, nodeInfoHash.toString()) - if (nodeInfoEntity != null) { - session.merge(nodeInfoEntity.copy( - signatureBytes = signature.bytes, - signaturePublicKeyAlgorithm = signature.by.algorithm, - signaturePublicKeyBytes = signature.by.encoded - )) - } - } - } - - private fun getUnsignedNodeInfoEntities(): List = database.transaction { - val builder = session.criteriaBuilder - // Retrieve all unsigned NodeInfoHash - val query = builder.createQuery(NodeInfoEntity::class.java).run { - from(NodeInfoEntity::class.java).run { - where(builder.and(builder.isNull(get(NodeInfoEntity::signatureBytes.name)))) - } - } - // Retrieve them together with their CSR - val (hintKey, hintValue) = getNodeInfoWithCsrHint(session) - val unsigned = session.createQuery(query).setHint(hintKey, hintValue).resultList - // Get only those that are valid - unsigned.filter({ - val certificateStatus = it.certificateSigningRequest?.certificateData?.certificateStatus - certificateStatus == CertificateStatus.VALID - }) - } - - /** - * Creates Hibernate query hint for pulling [CertificateSigningRequestEntity] when querying for [NodeInfoEntity] - */ - private fun getNodeInfoWithCsrHint(session: Session): Pair { - val graph = session.createEntityGraph(NodeInfoEntity::class.java) - graph.addAttributeNodes(NodeInfoEntity::certificateSigningRequest.name) - return QueryHints.HINT_LOADGRAPH to graph - } } \ No newline at end of file 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 30475230bf..d6b810b7dd 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 @@ -31,7 +31,7 @@ class NetworkMapSigner(private val networkMapStorage: NetworkMapStorage, fun signNetworkMap() { val currentSignedNetworkMap = networkMapStorage.getCurrentNetworkMap() val currentNetworkMapValidNodeInfo = networkMapStorage.getCurrentNetworkMapNodeInfoHashes(listOf(CertificateStatus.VALID)) - val detachedValidNodeInfo = networkMapStorage.getDetachedSignedAndValidNodeInfoHashes() + val detachedValidNodeInfo = networkMapStorage.getDetachedAndValidNodeInfoHashes() val nodeInfoHashes = currentNetworkMapValidNodeInfo + detachedValidNodeInfo val networkParameters = networkMapStorage.getLatestNetworkParameters() val networkMap = NetworkMap(nodeInfoHashes.map { it.toString() }, networkParameters.serialize().hash.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 ba684b0d59..b179ac3151 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 @@ -79,7 +79,7 @@ class DoormanServer(hostAndPort: NetworkHostAndPort, private vararg val webServi webServices.forEach { register(it) } } val jerseyServlet = ServletHolder(ServletContainer(resourceConfig)).apply { initOrder = 0 }// Initialise at server start - addServlet(jerseyServlet, "/api/*") + addServlet(jerseyServlet, "/*") } } } @@ -184,7 +184,7 @@ fun startDoorman(hostAndPort: NetworkHostAndPort, val networkMapStorage = PersistentNetworkMapStorage(database) val nodeInfoStorage = PersistentNodeInfoStorage(database) - val doorman = DoormanServer(hostAndPort, RegistrationWebService(requestProcessor, DoormanServer.serverStatus), NodeInfoWebService(nodeInfoStorage, networkMapStorage, signer)) + val doorman = DoormanServer(hostAndPort, RegistrationWebService(requestProcessor, DoormanServer.serverStatus), NodeInfoWebService(nodeInfoStorage, networkMapStorage)) doorman.start() val networkMapSigner = if (signer != null) NetworkMapSigner(networkMapStorage, signer) else null 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 a7db16cd56..c41ddf3179 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 @@ -3,12 +3,10 @@ package com.r3.corda.networkmanage.doorman.webservice import com.r3.corda.networkmanage.common.persistence.NetworkMapStorage import com.r3.corda.networkmanage.common.persistence.NodeInfoStorage import com.r3.corda.networkmanage.common.utils.hashString -import com.r3.corda.networkmanage.doorman.signer.LocalSigner -import com.r3.corda.networkmanage.doorman.webservice.NodeInfoWebService.Companion.networkMapPath +import com.r3.corda.networkmanage.doorman.webservice.NodeInfoWebService.Companion.NETWORK_MAP_PATH import net.corda.core.crypto.Crypto import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SignedData -import net.corda.core.node.NetworkParameters import net.corda.core.node.NodeInfo import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize @@ -23,42 +21,39 @@ import javax.ws.rs.core.Response import javax.ws.rs.core.Response.ok import javax.ws.rs.core.Response.status -@Path(networkMapPath) +@Path(NETWORK_MAP_PATH) class NodeInfoWebService(private val nodeInfoStorage: NodeInfoStorage, - private val networkMapStorage: NetworkMapStorage, - private val signer: LocalSigner? = null) { + private val networkMapStorage: NetworkMapStorage) { companion object { - const val networkMapPath = "network-map" + const val NETWORK_MAP_PATH = "network-map" } @POST - @Path("register") + @Path("publish") @Consumes(MediaType.APPLICATION_OCTET_STREAM) fun registerNode(input: InputStream): Response { - // TODO: Use JSON instead. val registrationData = input.readBytes().deserialize>() val nodeInfo = registrationData.verified() - val digitalSignature = registrationData.sig - val certPath = nodeInfoStorage.getCertificatePath(SecureHash.parse(digitalSignature.by.hashString())) + val certPath = nodeInfoStorage.getCertificatePath(SecureHash.parse(nodeInfo.legalIdentitiesAndCerts.first().certPath.certificates.first().publicKey.hashString())) return if (certPath != null) { try { - val serializedNodeInfo = nodeInfo.serialize().bytes val nodeCAPubKey = certPath.certificates.first().publicKey // Validate node public key nodeInfo.legalIdentitiesAndCerts.forEach { require(it.certPath.certificates.any { it.publicKey == nodeCAPubKey }) } - require(Crypto.doVerify(nodeCAPubKey, digitalSignature.bytes, serializedNodeInfo)) - // Store the NodeInfo and notify registration listener - nodeInfoStorage.putNodeInfo(nodeInfo, signer?.sign(serializedNodeInfo)?.signature) + val digitalSignature = registrationData.sig + require(Crypto.doVerify(nodeCAPubKey, digitalSignature.bytes, registrationData.raw.bytes)) + // Store the NodeInfo + nodeInfoStorage.putNodeInfo(registrationData) ok() } catch (e: Exception) { // 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 } } @@ -76,9 +71,12 @@ class NodeInfoWebService(private val nodeInfoStorage: NodeInfoStorage, @GET @Path("{nodeInfoHash}") fun getNodeInfo(@PathParam("nodeInfoHash") nodeInfoHash: String): Response { - return nodeInfoStorage.getSignedNodeInfo(SecureHash.parse(nodeInfoHash))?.let { - ok(it.serialize().bytes).build() - } ?: status(Response.Status.NOT_FOUND).build() + val nodeInfo = nodeInfoStorage.getNodeInfo(SecureHash.parse(nodeInfoHash)) + return if (nodeInfo != null) { + ok(nodeInfo.serialize().bytes).build() + } else { + status(Response.Status.NOT_FOUND).build() + } } @GET 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 af4005fe3d..a4516c1450 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 @@ -1,7 +1,6 @@ package com.r3.corda.networkmanage.hsm import com.r3.corda.networkmanage.common.persistence.PersistentNetworkMapStorage -import com.r3.corda.networkmanage.common.persistence.PersistentNodeInfoStorage import com.r3.corda.networkmanage.common.persistence.SchemaService import com.r3.corda.networkmanage.hsm.authentication.AuthMode import com.r3.corda.networkmanage.hsm.authentication.Authenticator @@ -32,10 +31,8 @@ fun run(parameters: Parameters) { }, SchemaService()) val csrStorage = DBSignedCertificateRequestStorage(database) val networkMapStorage = PersistentNetworkMapStorage(database) - val nodeInfoStorage = PersistentNodeInfoStorage(database) val hsmNetworkMapSigningThread = HsmNetworkMapSigner( networkMapStorage, - nodeInfoStorage, networkMapCertificateName, networkMapPrivateKeyPass, keyStorePass, diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/authentication/Authenticator.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/authentication/Authenticator.kt index c41e7e1359..02348e0561 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/authentication/Authenticator.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/authentication/Authenticator.kt @@ -93,7 +93,7 @@ class Authenticator(private val provider: CryptoServerProvider, /* * Configuration class for [CryptoServerProvider] */ -internal data class CryptoServerProviderConfig( +data class CryptoServerProviderConfig( val Device: String = "3001@127.0.0.1", val ConnectionTimeout: Int = 30000, val Timeout: Int = 60000, @@ -113,6 +113,10 @@ fun Parameters.createProvider(): CryptoServerProvider { KeyGroup = keyGroup, KeySpecifier = keySpecifier ) + return createProvider(config) +} + +fun createProvider(config: CryptoServerProviderConfig): CryptoServerProvider { val cfgBuffer = ByteArrayOutputStream() val writer = cfgBuffer.writer(Charsets.UTF_8) for (property in CryptoServerProviderConfig::class.memberProperties) { diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/generator/KeyCertificateGenerator.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/generator/KeyCertificateGenerator.kt index 25dac32009..3458c4f183 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/generator/KeyCertificateGenerator.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/generator/KeyCertificateGenerator.kt @@ -31,7 +31,7 @@ class KeyCertificateGenerator(private val authenticator: Authenticator, * @param parentPrivateKeyPassword password for the parent private key * @param validDays days of certificate validity */ - fun generateAllCertificates(keyStorePassword: String?, + fun generateAllCertificates(keyStorePassword: String? = null, intermediateCertificatesCredentials: List, parentCertificateName: String, parentPrivateKeyPassword: String, diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/signer/HsmNetworkMapSigner.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/signer/HsmNetworkMapSigner.kt index d6dd84922a..e755e57f47 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/signer/HsmNetworkMapSigner.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/signer/HsmNetworkMapSigner.kt @@ -1,7 +1,7 @@ package com.r3.corda.networkmanage.hsm.signer +import com.google.common.util.concurrent.MoreExecutors import com.r3.corda.networkmanage.common.persistence.NetworkMapStorage -import com.r3.corda.networkmanage.common.persistence.NodeInfoStorage import com.r3.corda.networkmanage.common.signer.NetworkMapSigner import com.r3.corda.networkmanage.common.signer.SignatureAndCertPath import com.r3.corda.networkmanage.common.signer.Signer @@ -15,15 +15,15 @@ import net.corda.core.utilities.minutes import java.security.KeyPair import java.security.PrivateKey import java.time.Duration -import java.util.* -import kotlin.concurrent.fixedRateTimer +import java.util.concurrent.Executors +import java.util.concurrent.ScheduledExecutorService +import java.util.concurrent.TimeUnit /** * Encapsulates logic for periodic network map signing execution. * It uses HSM as the signing entity with keys and certificates specified at the construction time. */ class HsmNetworkMapSigner(networkMapStorage: NetworkMapStorage, - private val nodeInfoStorage: NodeInfoStorage, private val caCertificateKeyName: String, private val caPrivateKeyPass: String, private val keyStorePassword: String?, @@ -33,45 +33,28 @@ class HsmNetworkMapSigner(networkMapStorage: NetworkMapStorage, companion object { val log = loggerFor() val DEFAULT_SIGNING_PERIOD_MS = 10.minutes + + private val TERMINATION_TIMEOUT_SEC = 2L } private val networkMapSigner = NetworkMapSigner(networkMapStorage, this) - private var fixedRateTimer: Timer? = null + private lateinit var scheduledExecutor: ScheduledExecutorService fun start(): HsmNetworkMapSigner { - stop() - fixedRateTimer = fixedRateTimer( - name = "Network Map Signing Thread", - period = signingPeriod.toMillis(), - action = { - try { - signNodeInfo() - networkMapSigner.signNetworkMap() - } catch (exception: Exception) { - log.warn("Exception thrown while signing network map", exception) - } - }) + val signingPeriodMillis = signingPeriod.toMillis() + scheduledExecutor = Executors.newSingleThreadScheduledExecutor() + scheduledExecutor.scheduleAtFixedRate({ + try { + networkMapSigner.signNetworkMap() + } catch (exception: Exception) { + log.warn("Exception thrown while signing network map", exception) + } + }, signingPeriodMillis, signingPeriodMillis, TimeUnit.MILLISECONDS) return this } fun stop() { - fixedRateTimer?.cancel() - } - - private fun signNodeInfo() { - // Retrieve data - val nodeInfoBytes = nodeInfoStorage.getUnsignedNodeInfoBytes() - // Authenticate and sign - authenticator.connectAndAuthenticate { provider, _ -> - val keyStore = X509Utilities.getAndInitializeKeyStore(provider, keyStorePassword) - val caCertificateChain = keyStore.getCertificateChain(caCertificateKeyName) - val caKey = keyStore.getKey(caCertificateKeyName, caPrivateKeyPass.toCharArray()) as PrivateKey - for ((nodeInfoHash, bytes) in nodeInfoBytes) { - val signature = signData(bytes, KeyPair(caCertificateChain.first().publicKey, caKey), provider) - verify(bytes, signature, caCertificateChain.first().publicKey) - nodeInfoStorage.signNodeInfo(nodeInfoHash, signature) - } - } + MoreExecutors.shutdownAndAwaitTermination(scheduledExecutor, TERMINATION_TIMEOUT_SEC, TimeUnit.SECONDS) } /** 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 c0feaed862..a04bdf59a3 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 @@ -23,28 +23,6 @@ abstract class TestBase { @JvmField val testSerialization = SerializationEnvironmentRule() - protected fun certificateSigningRequestEntity( - requestId: String = SecureHash.randomSHA256().toString(), - status: RequestStatus = RequestStatus.NEW, - legalName: String = "TestLegalName", - modifiedBy: List = emptyList(), - modifiedAt: Instant = Instant.now(), - remark: String = "Test remark", - certificateData: CertificateDataEntity? = null, - requestBytes: ByteArray = ByteArray(0) - ): CertificateSigningRequestEntity { - return CertificateSigningRequestEntity( - requestId = requestId, - status = status, - legalName = legalName, - modifiedBy = modifiedBy, - modifiedAt = modifiedAt, - remark = remark, - certificateData = certificateData, - requestBytes = requestBytes - ) - } - protected fun certificateSigningRequest( requestId: String = SecureHash.randomSHA256().toString(), status: RequestStatus = RequestStatus.NEW, 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/DBNetworkMapStorageTest.kt index 8dd8942186..cc5d252a02 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/DBNetworkMapStorageTest.kt @@ -7,11 +7,11 @@ import com.r3.corda.networkmanage.common.signer.SignedNetworkMap import com.r3.corda.networkmanage.common.utils.buildCertPath import com.r3.corda.networkmanage.common.utils.toX509Certificate import net.corda.core.crypto.Crypto +import net.corda.core.crypto.SignedData import net.corda.core.crypto.sign import net.corda.core.identity.CordaX500Name import net.corda.core.identity.PartyAndCertificate import net.corda.core.node.NodeInfo -import net.corda.core.node.NotaryInfo import net.corda.core.serialization.serialize import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.utilities.CertificateType @@ -62,15 +62,15 @@ class DBNetworkMapStorageTest : TestBase() { val certPath = buildCertPath(clientCert.toX509Certificate(), intermediateCACert.toX509Certificate(), rootCACert.toX509Certificate()) requestStorage.putCertificatePath(requestId, certPath, emptyList()) val nodeInfo = NodeInfo(listOf(NetworkHostAndPort("my.company.com", 1234)), listOf(PartyAndCertificate(certPath)), 1, serial = 1L) - val nodeInfoHash = nodeInfoStorage.putNodeInfo(nodeInfo) - // Some random bytes - val signature = keyPair.sign(nodeInfo.serialize()) - nodeInfoStorage.signNodeInfo(nodeInfoHash, signature) + // Put signed node info data + val nodeInfoBytes = nodeInfo.serialize() + val nodeInfoHash = nodeInfoStorage.putNodeInfo(SignedData(nodeInfoBytes, keyPair.sign(nodeInfoBytes))) // Create network parameters val networkParametersHash = networkMapStorage.putNetworkParameters(testNetworkParameters(emptyList())) - val signatureData = SignatureAndCertPath(signature, certPath) + val networkMap = NetworkMap(listOf(nodeInfoHash.toString()), networkParametersHash.toString()) + val signatureData = SignatureAndCertPath(keyPair.sign(networkMap.serialize()), certPath) val signedNetworkMap = SignedNetworkMap(NetworkMap(listOf(nodeInfoHash.toString()), networkParametersHash.toString()), signatureData) // when @@ -121,7 +121,7 @@ class DBNetworkMapStorageTest : TestBase() { } @Test - fun `getDetachedSignedAndValidNodeInfoHashes returns only valid and signed node info hashes`() { + fun `getDetachedAndValidNodeInfoHashes returns only valid and signed node info hashes`() { // given // Create node info. val organisationA = "TestA" @@ -139,11 +139,11 @@ class DBNetworkMapStorageTest : TestBase() { requestStorage.putCertificatePath(requestIdB, certPathB, emptyList()) val nodeInfoA = NodeInfo(listOf(NetworkHostAndPort("my.companyA.com", 1234)), listOf(PartyAndCertificate(certPathA)), 1, serial = 1L) val nodeInfoB = NodeInfo(listOf(NetworkHostAndPort("my.companyB.com", 1234)), listOf(PartyAndCertificate(certPathB)), 1, serial = 1L) - val nodeInfoHashA = nodeInfoStorage.putNodeInfo(nodeInfoA) - val nodeInfoHashB = nodeInfoStorage.putNodeInfo(nodeInfoB) - // Sign node info - nodeInfoStorage.signNodeInfo(nodeInfoHashA, keyPair.sign(nodeInfoA.serialize())) - nodeInfoStorage.signNodeInfo(nodeInfoHashB, keyPair.sign(nodeInfoB.serialize())) + // Put signed node info data + val nodeInfoABytes = nodeInfoA.serialize() + val nodeInfoBBytes = nodeInfoB.serialize() + val nodeInfoHashA = nodeInfoStorage.putNodeInfo(SignedData(nodeInfoABytes, keyPair.sign(nodeInfoABytes))) + val nodeInfoHashB = nodeInfoStorage.putNodeInfo(SignedData(nodeInfoBBytes, keyPair.sign(nodeInfoBBytes))) // Create network parameters val networkParametersHash = networkMapStorage.putNetworkParameters(createNetworkParameters()) @@ -155,7 +155,7 @@ class DBNetworkMapStorageTest : TestBase() { networkMapStorage.saveNetworkMap(signedNetworkMap) // when - val detachedHashes = networkMapStorage.getDetachedSignedAndValidNodeInfoHashes() + val detachedHashes = networkMapStorage.getDetachedAndValidNodeInfoHashes() // then assertEquals(1, detachedHashes.size) diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersistenceNodeInfoStorageTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersistenceNodeInfoStorageTest.kt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersitenceNodeInfoStorageTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersitenceNodeInfoStorageTest.kt index 7061e2c6fe..1bcc4e922d 100644 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersitenceNodeInfoStorageTest.kt +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersitenceNodeInfoStorageTest.kt @@ -6,12 +6,11 @@ import com.r3.corda.networkmanage.common.utils.hashString import com.r3.corda.networkmanage.common.utils.toX509Certificate import net.corda.core.crypto.Crypto import net.corda.core.crypto.SecureHash -import net.corda.core.crypto.sha256 +import net.corda.core.crypto.SignedData import net.corda.core.crypto.sign import net.corda.core.identity.CordaX500Name import net.corda.core.identity.PartyAndCertificate import net.corda.core.node.NodeInfo -import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.utilities.CertificateType @@ -25,7 +24,6 @@ import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertNull -import kotlin.test.assertTrue class PersitenceNodeInfoStorageTest : TestBase() { private lateinit var requestStorage: CertificationRequestStorage @@ -72,8 +70,8 @@ class PersitenceNodeInfoStorageTest : TestBase() { } @Test - fun `test getNodeInfoHashes`() { - // Create node info. + fun `test getNodeInfoHash returns correct data`() { + // given val organisationA = "TestA" val requestIdA = requestStorage.saveRequest(createRequest(organisationA).first) requestStorage.approveRequest(requestIdA, "TestUser") @@ -88,22 +86,23 @@ class PersitenceNodeInfoStorageTest : TestBase() { val certPathB = buildCertPath(clientCertB.toX509Certificate(), intermediateCACert.toX509Certificate(), rootCACert.toX509Certificate()) requestStorage.putCertificatePath(requestIdB, certPathB, emptyList()) val nodeInfoA = NodeInfo(listOf(NetworkHostAndPort("my.company.com", 1234)), listOf(PartyAndCertificate(certPathA)), 1, serial = 1L) - val nodeInfoSame = NodeInfo(listOf(NetworkHostAndPort("my.company.com", 1234)), listOf(PartyAndCertificate(certPathA)), 1, serial = 1L) val nodeInfoB = NodeInfo(listOf(NetworkHostAndPort("my.company.com", 1234)), listOf(PartyAndCertificate(certPathB)), 1, serial = 1L) - nodeInfoStorage.putNodeInfo(nodeInfoA) - nodeInfoStorage.putNodeInfo(nodeInfoSame) + // Put signed node info data + val nodeInfoABytes = nodeInfoA.serialize() + val nodeInfoBBytes = nodeInfoB.serialize() + nodeInfoStorage.putNodeInfo(SignedData(nodeInfoABytes, keyPair.sign(nodeInfoABytes))) + nodeInfoStorage.putNodeInfo(SignedData(nodeInfoBBytes, keyPair.sign(nodeInfoBBytes))) - // getNodeInfoHashes should contain 1 hash. - assertEquals(listOf(nodeInfoA.serialize().sha256()), nodeInfoStorage.getUnsignedNodeInfoHashes()) + // when + val persistedNodeInfoA = nodeInfoStorage.getNodeInfo(nodeInfoABytes.hash) + val persistedNodeInfoB = nodeInfoStorage.getNodeInfo(nodeInfoBBytes.hash) - nodeInfoStorage.putNodeInfo(nodeInfoB) - // getNodeInfoHashes should contain 2 hash. - assertEquals(listOf(nodeInfoB.serialize().sha256(), nodeInfoA.serialize().sha256()).sorted(), nodeInfoStorage.getUnsignedNodeInfoHashes().sorted()) - - // Test retrieve NodeInfo. - assertEquals(nodeInfoA, nodeInfoStorage.getNodeInfo(nodeInfoA.serialize().sha256())) - assertEquals(nodeInfoB, nodeInfoStorage.getNodeInfo(nodeInfoB.serialize().sha256())) + // then + assertNotNull(persistedNodeInfoA) + assertNotNull(persistedNodeInfoB) + assertEquals(persistedNodeInfoA!!.verified(), nodeInfoA) + assertEquals(persistedNodeInfoB!!.verified(), nodeInfoB) } @Test @@ -119,19 +118,21 @@ class PersitenceNodeInfoStorageTest : TestBase() { val nodeInfo = NodeInfo(listOf(NetworkHostAndPort("my.company.com", 1234)), listOf(PartyAndCertificate(certPath)), 1, serial = 1L) val nodeInfoSamePubKey = NodeInfo(listOf(NetworkHostAndPort("my.company2.com", 1234)), listOf(PartyAndCertificate(certPath)), 1, serial = 1L) + val nodeInfoBytes = nodeInfo.serialize() + val nodeInfoHash = nodeInfoStorage.putNodeInfo(SignedData(nodeInfoBytes, keyPair.sign(nodeInfoBytes))) + assertEquals(nodeInfo, nodeInfoStorage.getNodeInfo(nodeInfoHash)?.verified()) - nodeInfoStorage.putNodeInfo(nodeInfo) - assertEquals(nodeInfo, nodeInfoStorage.getNodeInfo(nodeInfo.serialize().sha256())) - + val nodeInfoSamePubKeyBytes = nodeInfoSamePubKey.serialize() // This should replace the node info. - nodeInfoStorage.putNodeInfo(nodeInfoSamePubKey) + nodeInfoStorage.putNodeInfo(SignedData(nodeInfoSamePubKeyBytes, keyPair.sign(nodeInfoSamePubKeyBytes))) + // Old node info should be removed. - assertNull(nodeInfoStorage.getNodeInfo(nodeInfo.serialize().sha256())) - assertEquals(nodeInfoSamePubKey, nodeInfoStorage.getNodeInfo(nodeInfoSamePubKey.serialize().sha256())) + assertNull(nodeInfoStorage.getNodeInfo(nodeInfoHash)) + assertEquals(nodeInfoSamePubKey, nodeInfoStorage.getNodeInfo(nodeInfoSamePubKeyBytes.hash)?.verified()) } @Test - fun `signNodeInfo associates signature to with node info`() { + fun `putNodeInfo persists node info data with its signature`() { // given // Create node info. val organisation = "Test" @@ -143,38 +144,16 @@ class PersitenceNodeInfoStorageTest : TestBase() { requestStorage.putCertificatePath(requestId, certPath, emptyList()) val nodeInfo = NodeInfo(listOf(NetworkHostAndPort("my.company.com", 1234)), listOf(PartyAndCertificate(certPath)), 1, serial = 1L) - val nodeInfoHash = nodeInfoStorage.putNodeInfo(nodeInfo) - // Some random bytes - val signature = keyPair.sign(nodeInfo.serialize()) + val nodeInfoBytes = nodeInfo.serialize() + val signature = keyPair.sign(nodeInfoBytes) // when - nodeInfoStorage.signNodeInfo(nodeInfoHash, signature) + val nodeInfoHash = nodeInfoStorage.putNodeInfo(SignedData(nodeInfoBytes, signature)) // then - val signedNodeInfo = nodeInfoStorage.getSignedNodeInfo(nodeInfoHash) - assertEquals(signature, signedNodeInfo?.sig) - } - - @Test - fun `getUnsignedNodeInfoBytes return node info bytes`() { - // given - // Create node info. - val organisation = "Test" - val requestId = requestStorage.saveRequest(createRequest(organisation).first) - requestStorage.approveRequest(requestId, "TestUser") - val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val clientCert = X509Utilities.createCertificate(CertificateType.CLIENT_CA, intermediateCACert, intermediateCAKey, CordaX500Name(organisation = organisation, locality = "London", country = "GB"), keyPair.public) - val certPath = buildCertPath(clientCert.toX509Certificate(), intermediateCACert.toX509Certificate(), rootCACert.toX509Certificate()) - requestStorage.putCertificatePath(requestId, certPath, emptyList()) - - val nodeInfo = NodeInfo(listOf(NetworkHostAndPort("my.company.com", 1234)), listOf(PartyAndCertificate(certPath)), 1, serial = 1L) - val nodeInfoHash = nodeInfoStorage.putNodeInfo(nodeInfo) - - // when - val nodeInfoBytes = nodeInfoStorage.getUnsignedNodeInfoBytes() - - // then - assertTrue(nodeInfoBytes.containsKey(nodeInfoHash)) - assertEquals(nodeInfo, nodeInfoBytes[nodeInfoHash]?.deserialize()!!) + val persistedNodeInfo = nodeInfoStorage.getNodeInfo(nodeInfoHash) + assertNotNull(persistedNodeInfo) + assertEquals(nodeInfo, persistedNodeInfo!!.verified()) + assertEquals(signature, persistedNodeInfo.sig) } } \ 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 e07269280a..5b9260696a 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 @@ -32,7 +32,7 @@ class NetworkMapSignerTest : TestBase() { whenever(networkMapStorage.getCurrentNetworkMap()) .thenReturn(SignedNetworkMap(NetworkMap(signedNodeInfoHashes.map { it.toString() }, "Dummy"), mock())) whenever(networkMapStorage.getCurrentNetworkMapNodeInfoHashes(any())).thenReturn(signedNodeInfoHashes) - whenever(networkMapStorage.getDetachedSignedAndValidNodeInfoHashes()).thenReturn(detachedNodeInfoHashes) + whenever(networkMapStorage.getDetachedAndValidNodeInfoHashes()).thenReturn(detachedNodeInfoHashes) whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(networkMapParameters) whenever(signer.sign(any())).thenReturn(mock()) @@ -42,7 +42,7 @@ class NetworkMapSignerTest : TestBase() { // then // Verify networkMapStorage calls verify(networkMapStorage).getCurrentNetworkMapNodeInfoHashes(any()) - verify(networkMapStorage).getDetachedSignedAndValidNodeInfoHashes() + verify(networkMapStorage).getDetachedAndValidNodeInfoHashes() verify(networkMapStorage).getLatestNetworkParameters() argumentCaptor().apply { verify(networkMapStorage).saveNetworkMap(capture()) @@ -63,7 +63,7 @@ class NetworkMapSignerTest : TestBase() { val signedNetworkMap = SignedNetworkMap(networkMap, mock()) whenever(networkMapStorage.getCurrentNetworkMap()).thenReturn(signedNetworkMap) whenever(networkMapStorage.getCurrentNetworkMapNodeInfoHashes(any())).thenReturn(emptyList()) - whenever(networkMapStorage.getDetachedSignedAndValidNodeInfoHashes()).thenReturn(emptyList()) + whenever(networkMapStorage.getDetachedAndValidNodeInfoHashes()).thenReturn(emptyList()) whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(networkMapParameters) // when 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 4344706323..ab7e2030a1 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 @@ -4,7 +4,6 @@ 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.TestBase import com.r3.corda.networkmanage.common.persistence.NetworkMapStorage import com.r3.corda.networkmanage.common.persistence.NodeInfoStorage import com.r3.corda.networkmanage.common.signer.NetworkMap @@ -21,10 +20,9 @@ import net.corda.core.serialization.serialize import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.utilities.CertificateType import net.corda.node.utilities.X509Utilities -import net.corda.nodeapi.internal.serialization.* -import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme -import net.corda.testing.common.internal.testNetworkParameters +import net.corda.testing.SerializationEnvironmentRule import org.bouncycastle.asn1.x500.X500Name +import org.junit.Rule import org.junit.Test import java.io.FileNotFoundException import java.io.IOException @@ -34,7 +32,12 @@ import javax.ws.rs.core.MediaType import kotlin.test.assertEquals import kotlin.test.assertFailsWith -class NodeInfoWebServiceTest : TestBase() { +class NodeInfoWebServiceTest { + + @Rule + @JvmField + val testSerialization = SerializationEnvironmentRule(true) + private val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) private val rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(locality = "London", organisation = "R3 LTD", country = "GB", commonName = "Corda Node Root CA"), rootCAKey) private val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) @@ -57,7 +60,7 @@ class NodeInfoWebServiceTest : TestBase() { DoormanServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(nodeInfoStorage, mock())).use { it.start() - val registerURL = URL("http://${it.hostAndPort}/api/${NodeInfoWebService.networkMapPath}/register") + val registerURL = URL("http://${it.hostAndPort}/${NodeInfoWebService.NETWORK_MAP_PATH}/publish") val nodeInfoAndSignature = SignedData(nodeInfo.serialize(), digitalSignature).serialize().bytes // Post node info and signature to doorman doPost(registerURL, nodeInfoAndSignature) @@ -83,7 +86,7 @@ class NodeInfoWebServiceTest : TestBase() { DoormanServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(nodeInfoStorage, mock())).use { it.start() - val registerURL = URL("http://${it.hostAndPort}/api/${NodeInfoWebService.networkMapPath}/register") + val registerURL = URL("http://${it.hostAndPort}/${NodeInfoWebService.NETWORK_MAP_PATH}/publish") val nodeInfoAndSignature = SignedData(nodeInfo.serialize(), digitalSignature).serialize().bytes // Post node info and signature to doorman assertFailsWith(IOException::class) { @@ -101,7 +104,7 @@ class NodeInfoWebServiceTest : TestBase() { } DoormanServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(mock(), networkMapStorage)).use { it.start() - val conn = URL("http://${it.hostAndPort}/api/${NodeInfoWebService.networkMapPath}").openConnection() as HttpURLConnection + val conn = URL("http://${it.hostAndPort}/${NodeInfoWebService.NETWORK_MAP_PATH}").openConnection() as HttpURLConnection val signedHashedNetworkMap = conn.inputStream.readBytes().deserialize() verify(networkMapStorage, times(1)).getCurrentNetworkMap() assertEquals(signedHashedNetworkMap.networkMap, hashedNetworkMap) @@ -119,19 +122,19 @@ class NodeInfoWebServiceTest : TestBase() { val nodeInfoStorage: NodeInfoStorage = mock { val serializedNodeInfo = nodeInfo.serialize() - on { getSignedNodeInfo(nodeInfoHash) }.thenReturn(SignedData(serializedNodeInfo, keyPair.sign(serializedNodeInfo))) + on { getNodeInfo(nodeInfoHash) }.thenReturn(SignedData(serializedNodeInfo, keyPair.sign(serializedNodeInfo))) } DoormanServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(nodeInfoStorage, mock())).use { it.start() - val nodeInfoURL = URL("http://${it.hostAndPort}/api/${NodeInfoWebService.networkMapPath}/$nodeInfoHash") + val nodeInfoURL = URL("http://${it.hostAndPort}/${NodeInfoWebService.NETWORK_MAP_PATH}/$nodeInfoHash") val conn = nodeInfoURL.openConnection() val nodeInfoResponse = conn.inputStream.readBytes().deserialize>() - verify(nodeInfoStorage, times(1)).getSignedNodeInfo(nodeInfoHash) + verify(nodeInfoStorage, times(1)).getNodeInfo(nodeInfoHash) assertEquals(nodeInfo, nodeInfoResponse.verified()) assertFailsWith(FileNotFoundException::class) { - URL("http://${it.hostAndPort}/api/${NodeInfoWebService.networkMapPath}/${SecureHash.randomSHA256()}").openConnection().getInputStream() + URL("http://${it.hostAndPort}/${NodeInfoWebService.NETWORK_MAP_PATH}/${SecureHash.randomSHA256()}").openConnection().getInputStream() } } } diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/RegistrationWebServiceTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/RegistrationWebServiceTest.kt index a6bcec02e7..521d248f58 100644 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/RegistrationWebServiceTest.kt +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/RegistrationWebServiceTest.kt @@ -169,7 +169,7 @@ class RegistrationWebServiceTest : TestBase() { } private fun submitRequest(request: PKCS10CertificationRequest): String { - val conn = URL("http://${doormanServer.hostAndPort}/api/certificate").openConnection() as HttpURLConnection + val conn = URL("http://${doormanServer.hostAndPort}/certificate").openConnection() as HttpURLConnection conn.doOutput = true conn.requestMethod = "POST" conn.setRequestProperty("Content-Type", MediaType.APPLICATION_OCTET_STREAM) @@ -178,7 +178,7 @@ class RegistrationWebServiceTest : TestBase() { } private fun pollForResponse(id: String): PollResponse { - val url = URL("http://${doormanServer.hostAndPort}/api/certificate/$id") + val url = URL("http://${doormanServer.hostAndPort}/certificate/$id") val conn = url.openConnection() as HttpURLConnection conn.requestMethod = "GET"