Removing NodeInfo signing process - this task is moved to node itself (#112)

* Removing NodeInfo signing process - this task is moved to node itself

* Addressing review comments

* Introducing HSM simulator polling

* Addressing review comments
This commit is contained in:
mkit 2017-11-20 12:52:30 +00:00 committed by GitHub
parent 7786913cd8
commit c1ae6e9647
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 290 additions and 307 deletions

View File

@ -1,7 +1,7 @@
ext { ext {
// We use Corda release artifact dependencies instead of project dependencies to make sure each doorman releases are // We use Corda release artifact dependencies instead of project dependencies to make sure each doorman releases are
// aligned with the corresponding Corda release. // 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" version "$corda_dependency_version"
@ -125,7 +125,6 @@ dependencies {
testCompile "com.nhaarman:mockito-kotlin:0.6.1" testCompile "com.nhaarman:mockito-kotlin:0.6.1"
testRuntime "net.corda:corda-rpc:$corda_dependency_version" testRuntime "net.corda:corda-rpc:$corda_dependency_version"
testCompile "com.spotify:docker-client:8.9.1" 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" integrationTestRuntime "net.corda:corda-rpc:$corda_dependency_version"
compile('com.atlassian.jira:jira-rest-java-client-core:4.0.0') { compile('com.atlassian.jira:jira-rest-java-client-core:4.0.0') {

View File

@ -1,5 +1,8 @@
package com.r3.corda.networkmanage 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.DefaultDockerClient
import com.spotify.docker.client.DockerClient import com.spotify.docker.client.DockerClient
import com.spotify.docker.client.messages.ContainerConfig 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, class HsmSimulator(private val serverAddress: String = DEFAULT_SERVER_ADDRESS,
private val imageRepoTag: String = DEFAULT_IMAGE_REPO_TAG, 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 { private companion object {
val DEFAULT_SERVER_ADDRESS = "corda.azurecr.io" 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 REGISTRY_PASSWORD = System.getenv("AZURE_CR_PASS")
val log = loggerFor<HsmSimulator>() val log = loggerFor<HsmSimulator>()
private val HSM_STARTUP_SLEEP_INTERVAL_MS = 500L
private val HSM_STARTUP_POLL_MAX_COUNT = 10;
} }
private val localHostAndPortBinding = freeLocalHostAndPort() private val localHostAndPortBinding = freeLocalHostAndPort()
@ -53,8 +61,8 @@ class HsmSimulator(private val serverAddress: String = DEFAULT_SERVER_ADDRESS,
private var containerId: String? = null private var containerId: String? = null
override fun before() { override fun before() {
assumeFalse("Docker registry username is not set!. Skipping the test.", REGISTRY_USERNAME.isNullOrBlank()) assumeFalse("Docker registry username is not set!. Skipping the test.", registryUser.isNullOrBlank())
assumeFalse("Docker registry password is not set!. Skipping the test.", REGISTRY_PASSWORD.isNullOrBlank()) assumeFalse("Docker registry password is not set!. Skipping the test.", registryPass.isNullOrBlank())
docker = DefaultDockerClient.fromEnv().build().pullHsmSimulatorImageFromRepository() docker = DefaultDockerClient.fromEnv().build().pullHsmSimulatorImageFromRepository()
containerId = docker.createContainer() containerId = docker.createContainer()
docker.startHsmSimulatorContainer() docker.startHsmSimulatorContainer()
@ -96,17 +104,41 @@ class HsmSimulator(private val serverAddress: String = DEFAULT_SERVER_ADDRESS,
if (containerId != null) { if (containerId != null) {
log.debug("Starting container $containerId...") log.debug("Starting container $containerId...")
this.startContainer(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 getImageFullName() = "$imageRepoTag:$imageVersion"
private fun DockerClient.pullHsmSimulatorImageFromRepository(): DockerClient { private fun DockerClient.pullHsmSimulatorImageFromRepository(): DockerClient {
this.pull(imageRepoTag, this.pull(imageRepoTag,
RegistryAuth.builder() RegistryAuth.builder()
.serverAddress(serverAddress) .serverAddress(serverAddress)
.username(REGISTRY_USERNAME) .username(registryUser)
.password(REGISTRY_PASSWORD) .password(registryPass)
.build()) .build())
return this return this
} }

View File

@ -2,63 +2,73 @@ package com.r3.corda.networkmanage.doorman
import com.nhaarman.mockito_kotlin.whenever import com.nhaarman.mockito_kotlin.whenever
import com.r3.corda.networkmanage.common.persistence.SchemaService 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.common.utils.toX509Certificate
import com.r3.corda.networkmanage.doorman.signer.LocalSigner import com.r3.corda.networkmanage.doorman.signer.LocalSigner
import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash 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.CordaX500Name
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.cert 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.core.utilities.NetworkHostAndPort
import net.corda.node.services.network.NetworkMapClient
import net.corda.node.utilities.* import net.corda.node.utilities.*
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
import net.corda.node.utilities.registration.NetworkRegistrationHelper import net.corda.node.utilities.registration.NetworkRegistrationHelper
import net.corda.testing.ALICE import net.corda.testing.ALICE
import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.testNodeConfiguration import net.corda.testing.testNodeConfiguration
import org.bouncycastle.cert.X509CertificateHolder
import org.junit.Ignore
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.TemporaryFolder import org.junit.rules.TemporaryFolder
import java.net.URL import java.net.URL
import java.util.* import java.util.*
import kotlin.test.assertEquals import kotlin.test.assertEquals
import net.corda.testing.common.internal.testNetworkParameters import kotlin.test.assertNotNull
class DoormanIntegrationTest { class DoormanIntegrationTest {
@Rule @Rule
@JvmField @JvmField
val tempFolder = TemporaryFolder() val tempFolder = TemporaryFolder()
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule(true)
@Test @Test
fun `initial registration`() { fun `initial registration`() {
val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val rootCertAndKey = createDoormanRootCertificateAndKeyPair()
val rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Integration Test Corda Node Root CA", organisation = "R3 Ltd", locality = "London", country = "GB"), rootCAKey) val intermediateCertAndKey = createDoormanIntermediateCertificateAndKeyPair(rootCertAndKey)
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()))
//Start doorman server //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. // Start Corda network registration.
val config = testNodeConfiguration( val config = testNodeConfiguration(
baseDirectory = tempFolder.root.toPath(), baseDirectory = tempFolder.root.toPath(),
myLegalName = ALICE.name).also { 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") 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. // Checks the keystore are created with the right certificates and keys.
assert(config.nodeKeystore.toFile().exists()) assert(config.nodeKeystore.toFile().exists())
assert(config.sslKeystore.toFile().exists()) assert(config.sslKeystore.toFile().exists())
assert(config.trustStoreFile.toFile().exists()) assert(config.trustStoreFile.toFile().exists())
val intermediateCACert = intermediateCertAndKey.certificate
val rootCACert = rootCertAndKey.certificate
loadKeyStore(config.nodeKeystore, config.keyStorePassword).apply { loadKeyStore(config.nodeKeystore, config.keyStorePassword).apply {
assert(containsAlias(X509Utilities.CORDA_CLIENT_CA)) assert(containsAlias(X509Utilities.CORDA_CLIENT_CA))
assertEquals(ALICE.name.copy(commonName = X509Utilities.CORDA_CLIENT_CA_CN).x500Principal, getX509Certificate(X509Utilities.CORDA_CLIENT_CA).subjectX500Principal) 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)) assert(containsAlias(X509Utilities.CORDA_ROOT_CA))
assertEquals(rootCACert.cert.subjectX500Principal, getX509Certificate(X509Utilities.CORDA_ROOT_CA).subjectX500Principal) assertEquals(rootCACert.cert.subjectX500Principal, getX509Certificate(X509Utilities.CORDA_ROOT_CA).subjectX500Principal)
} }
doorman.close() doorman.close()
} }
private fun makeTestDataSourceProperties(nodeName: String = SecureHash.randomSHA256().toString()): Properties { //TODO remove @Ignore once PR https://github.com/corda/corda/pull/2054 is merged
val props = Properties() @Test
props.setProperty("dataSourceClassName", "org.h2.jdbcx.JdbcDataSource") @Ignore
props.setProperty("dataSource.url", "jdbc:h2:mem:${nodeName}_persistence;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE") fun `nodeInfo is published to the network map`() {
props.setProperty("dataSource.user", "sa") // Given
props.setProperty("dataSource.password", "") val rootCertAndKey = createDoormanRootCertificateAndKeyPair()
return props 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)
} }

View File

@ -11,6 +11,7 @@ import com.r3.corda.networkmanage.hsm.configuration.Parameters
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.TemporaryFolder
import kotlin.test.assertTrue import kotlin.test.assertTrue
class HsmTest { class HsmTest {
@ -18,26 +19,30 @@ class HsmTest {
@Rule @Rule
@JvmField @JvmField
val hsmSimulator: HsmSimulator = HsmSimulator() 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 private lateinit var inputReader: InputReader
@Before @Before
fun setUp() { fun setUp() {
inputReader = mock() inputReader = mock()
whenever(inputReader.readLine()).thenReturn(hsmSimulator.cryptoUserCredentials().username)
whenever(inputReader.readPassword(any())).thenReturn(hsmSimulator.cryptoUserCredentials().password)
} }
@Test @Test
fun `Authenticator executes the block once user is successfully authenticated`() { fun `Authenticator executes the block once user is successfully authenticated`() {
// given // given
val parameters = Parameters( val authenticator = Authenticator(testParameters.createProvider(), inputReader = inputReader)
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)
var executed = false var executed = false
// when // when
@ -48,6 +53,4 @@ class HsmTest {
// then // then
assertTrue(executed) assertTrue(executed)
} }
} }

View File

@ -86,19 +86,20 @@ class SigningServiceIntegrationTest {
} }
@Test @Test
fun `Signing service communicates with Doorman`() { fun `Signing service signs approved CSRs`() {
//Start doorman server //Start doorman server
val database = configureDatabase(makeTestDataSourceProperties(), null, { val database = configureDatabase(makeTestDataSourceProperties(), null, {
// Identity service not needed doorman, corda persistence is not very generic. // Identity service not needed doorman, corda persistence is not very generic.
throw UnsupportedOperationException() throw UnsupportedOperationException()
}, SchemaService()) }, 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. // Start Corda network registration.
val config = testNodeConfiguration( val config = testNodeConfiguration(
baseDirectory = tempFolder.root.toPath(), baseDirectory = tempFolder.root.toPath(),
myLegalName = ALICE.name).also { 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(), { 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. // [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()) verify(hsmSigner).sign(any())
doorman.close() doorman.close()
} }
@ -166,9 +167,9 @@ class SigningServiceIntegrationTest {
3 -> CHARLIE.name 3 -> CHARLIE.name
else -> throw IllegalArgumentException("Unrecognised option") else -> throw IllegalArgumentException("Unrecognised option")
}).also { }).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() } }.map { it.join() }
doorman.close() doorman.close()

View File

@ -33,10 +33,10 @@ interface NetworkMapStorage {
fun saveNetworkMap(signedNetworkMap: SignedNetworkMap) 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. * that are not associated with any network map yet.
*/ */
fun getDetachedSignedAndValidNodeInfoHashes(): List<SecureHash> fun getDetachedAndValidNodeInfoHashes(): List<SecureHash>
/** /**
* Retrieve network parameters by their hash. * Retrieve network parameters by their hash.

View File

@ -16,41 +16,16 @@ interface NodeInfoStorage {
*/ */
fun getCertificatePath(publicKeyHash: SecureHash): CertPath? 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<SecureHash>
/**
* 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<SecureHash, ByteArray>
/**
* 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 * Retrieve node info together with its signature using nodeInfo's hash
* @return [NodeInfo] or null if the node info is not registered. * @return [NodeInfo] or null if the node info is not registered.
*/ */
fun getSignedNodeInfo(nodeInfoHash: SecureHash): SignedData<NodeInfo>? fun getNodeInfo(nodeInfoHash: SecureHash): SignedData<NodeInfo>?
/** /**
* The [nodeInfo] is keyed by the public key, old node info with the same public key will be replaced by the new node info. * 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 signedNodeInfo signed node info data to be stored
* @param signature (optional) signature associated with the node info
* @return hash for the newly created node info entry * @return hash for the newly created node info entry
*/ */
fun putNodeInfo(nodeInfo: NodeInfo, signature: DigitalSignature? = null): SecureHash fun putNodeInfo(signedNodeInfo: SignedData<NodeInfo>): 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)
} }

View File

@ -98,7 +98,7 @@ class PersistentNetworkMapStorage(private val database: CordaPersistence) : Netw
session.createQuery(query).resultList.first() session.createQuery(query).resultList.first()
} }
override fun getDetachedSignedAndValidNodeInfoHashes(): List<SecureHash> = database.transaction { override fun getDetachedAndValidNodeInfoHashes(): List<SecureHash> = database.transaction {
val builder = session.criteriaBuilder val builder = session.criteriaBuilder
// Get signed NodeInfoEntities // Get signed NodeInfoEntities
val query = builder.createQuery(NodeInfoEntity::class.java).run { val query = builder.createQuery(NodeInfoEntity::class.java).run {

View File

@ -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.persistence.entity.NodeInfoEntity
import com.r3.corda.networkmanage.common.utils.buildCertPath import com.r3.corda.networkmanage.common.utils.buildCertPath
import com.r3.corda.networkmanage.common.utils.hashString 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.SecureHash
import net.corda.core.crypto.SignedData import net.corda.core.crypto.SignedData
import net.corda.core.crypto.sha256
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.serialize
import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.CordaPersistence
import org.hibernate.Session
import org.hibernate.jpa.QueryHints
import java.security.cert.CertPath import java.security.cert.CertPath
import java.sql.Connection import java.sql.Connection
@ -22,7 +17,8 @@ import java.sql.Connection
* Database implementation of the [NetworkMapStorage] interface * Database implementation of the [NetworkMapStorage] interface
*/ */
class PersistentNodeInfoStorage(private val database: CordaPersistence) : NodeInfoStorage { 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<NodeInfo>): SecureHash = database.transaction(Connection.TRANSACTION_SERIALIZABLE) {
val nodeInfo = signedNodeInfo.verified()
val publicKeyHash = nodeInfo.legalIdentities.first().owningKey.hashString() val publicKeyHash = nodeInfo.legalIdentities.first().owningKey.hashString()
val request = singleRequestWhere(CertificateDataEntity::class.java) { builder, path -> val request = singleRequestWhere(CertificateDataEntity::class.java) { builder, path ->
val certPublicKeyHashEq = builder.equal(path.get<String>(CertificateDataEntity::publicKeyHash.name), publicKeyHash) val certPublicKeyHashEq = builder.equal(path.get<String>(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") 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 * 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 * 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 * 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 -> deleteRequest(NodeInfoEntity::class.java) { builder, path ->
builder.equal(path.get<CertificateSigningRequestEntity>(NodeInfoEntity::certificateSigningRequest.name), request.certificateSigningRequest) builder.equal(path.get<CertificateSigningRequestEntity>(NodeInfoEntity::certificateSigningRequest.name), request.certificateSigningRequest)
} }
val serializedNodeInfo = nodeInfo.serialize().bytes val hash = signedNodeInfo.raw.hash
val hash = serializedNodeInfo.sha256()
val hashedNodeInfo = NodeInfoEntity( val hashedNodeInfo = NodeInfoEntity(
nodeInfoHash = hash.toString(), nodeInfoHash = hash.toString(),
certificateSigningRequest = request.certificateSigningRequest, certificateSigningRequest = request.certificateSigningRequest,
nodeInfoBytes = serializedNodeInfo, nodeInfoBytes = signedNodeInfo.raw.bytes,
signatureBytes = signature?.bytes) signatureBytes = signedNodeInfo.sig.bytes,
signaturePublicKeyBytes = signedNodeInfo.sig.by.encoded,
signaturePublicKeyAlgorithm = signedNodeInfo.sig.by.algorithm)
session.save(hashedNodeInfo) session.save(hashedNodeInfo)
hash hash
} }
override fun getSignedNodeInfo(nodeInfoHash: SecureHash): SignedData<NodeInfo>? = database.transaction { override fun getNodeInfo(nodeInfoHash: SecureHash): SignedData<NodeInfo>? = database.transaction {
val nodeInfoEntity = session.find(NodeInfoEntity::class.java, nodeInfoHash.toString()) val nodeInfoEntity = session.find(NodeInfoEntity::class.java, nodeInfoHash.toString())
if (nodeInfoEntity?.signatureBytes == null) { if (nodeInfoEntity?.signatureBytes == null) {
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<SecureHash, ByteArray> {
return getUnsignedNodeInfoEntities().associate { SecureHash.parse(it.nodeInfoHash) to it.nodeInfoBytes }
}
override fun getUnsignedNodeInfoHashes(): List<SecureHash> {
return getUnsignedNodeInfoEntities().map { SecureHash.parse(it.nodeInfoHash) }
}
override fun getCertificatePath(publicKeyHash: SecureHash): CertPath? { override fun getCertificatePath(publicKeyHash: SecureHash): CertPath? {
return database.transaction { return database.transaction {
val builder = session.criteriaBuilder val builder = session.criteriaBuilder
@ -86,44 +71,4 @@ class PersistentNodeInfoStorage(private val database: CordaPersistence) : NodeIn
session.createQuery(query).uniqueResultOptional().orElseGet { null }?.let { buildCertPath(it) } 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<NodeInfoEntity> = 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<ByteArray>(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<String, Any> {
val graph = session.createEntityGraph(NodeInfoEntity::class.java)
graph.addAttributeNodes(NodeInfoEntity::certificateSigningRequest.name)
return QueryHints.HINT_LOADGRAPH to graph
}
} }

View File

@ -31,7 +31,7 @@ class NetworkMapSigner(private val networkMapStorage: NetworkMapStorage,
fun signNetworkMap() { fun signNetworkMap() {
val currentSignedNetworkMap = networkMapStorage.getCurrentNetworkMap() val currentSignedNetworkMap = networkMapStorage.getCurrentNetworkMap()
val currentNetworkMapValidNodeInfo = networkMapStorage.getCurrentNetworkMapNodeInfoHashes(listOf(CertificateStatus.VALID)) val currentNetworkMapValidNodeInfo = networkMapStorage.getCurrentNetworkMapNodeInfoHashes(listOf(CertificateStatus.VALID))
val detachedValidNodeInfo = networkMapStorage.getDetachedSignedAndValidNodeInfoHashes() val detachedValidNodeInfo = networkMapStorage.getDetachedAndValidNodeInfoHashes()
val nodeInfoHashes = currentNetworkMapValidNodeInfo + detachedValidNodeInfo val nodeInfoHashes = currentNetworkMapValidNodeInfo + detachedValidNodeInfo
val networkParameters = networkMapStorage.getLatestNetworkParameters() val networkParameters = networkMapStorage.getLatestNetworkParameters()
val networkMap = NetworkMap(nodeInfoHashes.map { it.toString() }, networkParameters.serialize().hash.toString()) val networkMap = NetworkMap(nodeInfoHashes.map { it.toString() }, networkParameters.serialize().hash.toString())

View File

@ -79,7 +79,7 @@ class DoormanServer(hostAndPort: NetworkHostAndPort, private vararg val webServi
webServices.forEach { register(it) } webServices.forEach { register(it) }
} }
val jerseyServlet = ServletHolder(ServletContainer(resourceConfig)).apply { initOrder = 0 }// Initialise at server start 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 networkMapStorage = PersistentNetworkMapStorage(database)
val nodeInfoStorage = PersistentNodeInfoStorage(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() doorman.start()
val networkMapSigner = if (signer != null) NetworkMapSigner(networkMapStorage, signer) else null val networkMapSigner = if (signer != null) NetworkMapSigner(networkMapStorage, signer) else null

View File

@ -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.NetworkMapStorage
import com.r3.corda.networkmanage.common.persistence.NodeInfoStorage import com.r3.corda.networkmanage.common.persistence.NodeInfoStorage
import com.r3.corda.networkmanage.common.utils.hashString 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.NETWORK_MAP_PATH
import com.r3.corda.networkmanage.doorman.webservice.NodeInfoWebService.Companion.networkMapPath
import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.SignedData import net.corda.core.crypto.SignedData
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
@ -23,42 +21,39 @@ import javax.ws.rs.core.Response
import javax.ws.rs.core.Response.ok import javax.ws.rs.core.Response.ok
import javax.ws.rs.core.Response.status import javax.ws.rs.core.Response.status
@Path(networkMapPath) @Path(NETWORK_MAP_PATH)
class NodeInfoWebService(private val nodeInfoStorage: NodeInfoStorage, class NodeInfoWebService(private val nodeInfoStorage: NodeInfoStorage,
private val networkMapStorage: NetworkMapStorage, private val networkMapStorage: NetworkMapStorage) {
private val signer: LocalSigner? = null) {
companion object { companion object {
const val networkMapPath = "network-map" const val NETWORK_MAP_PATH = "network-map"
} }
@POST @POST
@Path("register") @Path("publish")
@Consumes(MediaType.APPLICATION_OCTET_STREAM) @Consumes(MediaType.APPLICATION_OCTET_STREAM)
fun registerNode(input: InputStream): Response { fun registerNode(input: InputStream): Response {
// TODO: Use JSON instead.
val registrationData = input.readBytes().deserialize<SignedData<NodeInfo>>() val registrationData = input.readBytes().deserialize<SignedData<NodeInfo>>()
val nodeInfo = registrationData.verified() 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) { return if (certPath != null) {
try { try {
val serializedNodeInfo = nodeInfo.serialize().bytes
val nodeCAPubKey = certPath.certificates.first().publicKey val nodeCAPubKey = certPath.certificates.first().publicKey
// Validate node public key // Validate node public key
nodeInfo.legalIdentitiesAndCerts.forEach { nodeInfo.legalIdentitiesAndCerts.forEach {
require(it.certPath.certificates.any { it.publicKey == nodeCAPubKey }) require(it.certPath.certificates.any { it.publicKey == nodeCAPubKey })
} }
require(Crypto.doVerify(nodeCAPubKey, digitalSignature.bytes, serializedNodeInfo)) val digitalSignature = registrationData.sig
// Store the NodeInfo and notify registration listener require(Crypto.doVerify(nodeCAPubKey, digitalSignature.bytes, registrationData.raw.bytes))
nodeInfoStorage.putNodeInfo(nodeInfo, signer?.sign(serializedNodeInfo)?.signature) // Store the NodeInfo
nodeInfoStorage.putNodeInfo(registrationData)
ok() ok()
} catch (e: Exception) { } catch (e: Exception) {
// Catch exceptions thrown by signature verification. // Catch exceptions thrown by signature verification.
when (e) { when (e) {
is IllegalArgumentException, is InvalidKeyException, is SignatureException -> status(Response.Status.UNAUTHORIZED).entity(e.message) is IllegalArgumentException, is InvalidKeyException, is SignatureException -> status(Response.Status.UNAUTHORIZED).entity(e.message)
// Rethrow e if its not one of the expected exception, the server will return http 500 internal error. // Rethrow e if its not one of the expected exception, the server will return http 500 internal error.
else -> throw e else -> throw e
} }
} }
@ -76,9 +71,12 @@ class NodeInfoWebService(private val nodeInfoStorage: NodeInfoStorage,
@GET @GET
@Path("{nodeInfoHash}") @Path("{nodeInfoHash}")
fun getNodeInfo(@PathParam("nodeInfoHash") nodeInfoHash: String): Response { fun getNodeInfo(@PathParam("nodeInfoHash") nodeInfoHash: String): Response {
return nodeInfoStorage.getSignedNodeInfo(SecureHash.parse(nodeInfoHash))?.let { val nodeInfo = nodeInfoStorage.getNodeInfo(SecureHash.parse(nodeInfoHash))
ok(it.serialize().bytes).build() return if (nodeInfo != null) {
} ?: status(Response.Status.NOT_FOUND).build() ok(nodeInfo.serialize().bytes).build()
} else {
status(Response.Status.NOT_FOUND).build()
}
} }
@GET @GET

View File

@ -1,7 +1,6 @@
package com.r3.corda.networkmanage.hsm package com.r3.corda.networkmanage.hsm
import com.r3.corda.networkmanage.common.persistence.PersistentNetworkMapStorage 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.common.persistence.SchemaService
import com.r3.corda.networkmanage.hsm.authentication.AuthMode import com.r3.corda.networkmanage.hsm.authentication.AuthMode
import com.r3.corda.networkmanage.hsm.authentication.Authenticator import com.r3.corda.networkmanage.hsm.authentication.Authenticator
@ -32,10 +31,8 @@ fun run(parameters: Parameters) {
}, SchemaService()) }, SchemaService())
val csrStorage = DBSignedCertificateRequestStorage(database) val csrStorage = DBSignedCertificateRequestStorage(database)
val networkMapStorage = PersistentNetworkMapStorage(database) val networkMapStorage = PersistentNetworkMapStorage(database)
val nodeInfoStorage = PersistentNodeInfoStorage(database)
val hsmNetworkMapSigningThread = HsmNetworkMapSigner( val hsmNetworkMapSigningThread = HsmNetworkMapSigner(
networkMapStorage, networkMapStorage,
nodeInfoStorage,
networkMapCertificateName, networkMapCertificateName,
networkMapPrivateKeyPass, networkMapPrivateKeyPass,
keyStorePass, keyStorePass,

View File

@ -93,7 +93,7 @@ class Authenticator(private val provider: CryptoServerProvider,
/* /*
* Configuration class for [CryptoServerProvider] * Configuration class for [CryptoServerProvider]
*/ */
internal data class CryptoServerProviderConfig( data class CryptoServerProviderConfig(
val Device: String = "3001@127.0.0.1", val Device: String = "3001@127.0.0.1",
val ConnectionTimeout: Int = 30000, val ConnectionTimeout: Int = 30000,
val Timeout: Int = 60000, val Timeout: Int = 60000,
@ -113,6 +113,10 @@ fun Parameters.createProvider(): CryptoServerProvider {
KeyGroup = keyGroup, KeyGroup = keyGroup,
KeySpecifier = keySpecifier KeySpecifier = keySpecifier
) )
return createProvider(config)
}
fun createProvider(config: CryptoServerProviderConfig): CryptoServerProvider {
val cfgBuffer = ByteArrayOutputStream() val cfgBuffer = ByteArrayOutputStream()
val writer = cfgBuffer.writer(Charsets.UTF_8) val writer = cfgBuffer.writer(Charsets.UTF_8)
for (property in CryptoServerProviderConfig::class.memberProperties) { for (property in CryptoServerProviderConfig::class.memberProperties) {

View File

@ -31,7 +31,7 @@ class KeyCertificateGenerator(private val authenticator: Authenticator,
* @param parentPrivateKeyPassword password for the parent private key * @param parentPrivateKeyPassword password for the parent private key
* @param validDays days of certificate validity * @param validDays days of certificate validity
*/ */
fun generateAllCertificates(keyStorePassword: String?, fun generateAllCertificates(keyStorePassword: String? = null,
intermediateCertificatesCredentials: List<CertificateNameAndPass>, intermediateCertificatesCredentials: List<CertificateNameAndPass>,
parentCertificateName: String, parentCertificateName: String,
parentPrivateKeyPassword: String, parentPrivateKeyPassword: String,

View File

@ -1,7 +1,7 @@
package com.r3.corda.networkmanage.hsm.signer 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.NetworkMapStorage
import com.r3.corda.networkmanage.common.persistence.NodeInfoStorage
import com.r3.corda.networkmanage.common.signer.NetworkMapSigner import com.r3.corda.networkmanage.common.signer.NetworkMapSigner
import com.r3.corda.networkmanage.common.signer.SignatureAndCertPath import com.r3.corda.networkmanage.common.signer.SignatureAndCertPath
import com.r3.corda.networkmanage.common.signer.Signer 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.KeyPair
import java.security.PrivateKey import java.security.PrivateKey
import java.time.Duration import java.time.Duration
import java.util.* import java.util.concurrent.Executors
import kotlin.concurrent.fixedRateTimer import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit
/** /**
* Encapsulates logic for periodic network map signing execution. * Encapsulates logic for periodic network map signing execution.
* It uses HSM as the signing entity with keys and certificates specified at the construction time. * It uses HSM as the signing entity with keys and certificates specified at the construction time.
*/ */
class HsmNetworkMapSigner(networkMapStorage: NetworkMapStorage, class HsmNetworkMapSigner(networkMapStorage: NetworkMapStorage,
private val nodeInfoStorage: NodeInfoStorage,
private val caCertificateKeyName: String, private val caCertificateKeyName: String,
private val caPrivateKeyPass: String, private val caPrivateKeyPass: String,
private val keyStorePassword: String?, private val keyStorePassword: String?,
@ -33,45 +33,28 @@ class HsmNetworkMapSigner(networkMapStorage: NetworkMapStorage,
companion object { companion object {
val log = loggerFor<HsmNetworkMapSigner>() val log = loggerFor<HsmNetworkMapSigner>()
val DEFAULT_SIGNING_PERIOD_MS = 10.minutes val DEFAULT_SIGNING_PERIOD_MS = 10.minutes
private val TERMINATION_TIMEOUT_SEC = 2L
} }
private val networkMapSigner = NetworkMapSigner(networkMapStorage, this) private val networkMapSigner = NetworkMapSigner(networkMapStorage, this)
private var fixedRateTimer: Timer? = null private lateinit var scheduledExecutor: ScheduledExecutorService
fun start(): HsmNetworkMapSigner { fun start(): HsmNetworkMapSigner {
stop() val signingPeriodMillis = signingPeriod.toMillis()
fixedRateTimer = fixedRateTimer( scheduledExecutor = Executors.newSingleThreadScheduledExecutor()
name = "Network Map Signing Thread", scheduledExecutor.scheduleAtFixedRate({
period = signingPeriod.toMillis(), try {
action = { networkMapSigner.signNetworkMap()
try { } catch (exception: Exception) {
signNodeInfo() log.warn("Exception thrown while signing network map", exception)
networkMapSigner.signNetworkMap() }
} catch (exception: Exception) { }, signingPeriodMillis, signingPeriodMillis, TimeUnit.MILLISECONDS)
log.warn("Exception thrown while signing network map", exception)
}
})
return this return this
} }
fun stop() { fun stop() {
fixedRateTimer?.cancel() MoreExecutors.shutdownAndAwaitTermination(scheduledExecutor, TERMINATION_TIMEOUT_SEC, TimeUnit.SECONDS)
}
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)
}
}
} }
/** /**

View File

@ -23,28 +23,6 @@ abstract class TestBase {
@JvmField @JvmField
val testSerialization = SerializationEnvironmentRule() val testSerialization = SerializationEnvironmentRule()
protected fun certificateSigningRequestEntity(
requestId: String = SecureHash.randomSHA256().toString(),
status: RequestStatus = RequestStatus.NEW,
legalName: String = "TestLegalName",
modifiedBy: List<String> = 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( protected fun certificateSigningRequest(
requestId: String = SecureHash.randomSHA256().toString(), requestId: String = SecureHash.randomSHA256().toString(),
status: RequestStatus = RequestStatus.NEW, status: RequestStatus = RequestStatus.NEW,

View File

@ -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.buildCertPath
import com.r3.corda.networkmanage.common.utils.toX509Certificate import com.r3.corda.networkmanage.common.utils.toX509Certificate
import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SignedData
import net.corda.core.crypto.sign import net.corda.core.crypto.sign
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.node.NotaryInfo
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.utilities.CertificateType import net.corda.node.utilities.CertificateType
@ -62,15 +62,15 @@ class DBNetworkMapStorageTest : TestBase() {
val certPath = buildCertPath(clientCert.toX509Certificate(), intermediateCACert.toX509Certificate(), rootCACert.toX509Certificate()) val certPath = buildCertPath(clientCert.toX509Certificate(), intermediateCACert.toX509Certificate(), rootCACert.toX509Certificate())
requestStorage.putCertificatePath(requestId, certPath, emptyList()) requestStorage.putCertificatePath(requestId, certPath, emptyList())
val nodeInfo = NodeInfo(listOf(NetworkHostAndPort("my.company.com", 1234)), listOf(PartyAndCertificate(certPath)), 1, serial = 1L) val nodeInfo = NodeInfo(listOf(NetworkHostAndPort("my.company.com", 1234)), listOf(PartyAndCertificate(certPath)), 1, serial = 1L)
val nodeInfoHash = nodeInfoStorage.putNodeInfo(nodeInfo) // Put signed node info data
// Some random bytes val nodeInfoBytes = nodeInfo.serialize()
val signature = keyPair.sign(nodeInfo.serialize()) val nodeInfoHash = nodeInfoStorage.putNodeInfo(SignedData(nodeInfoBytes, keyPair.sign(nodeInfoBytes)))
nodeInfoStorage.signNodeInfo(nodeInfoHash, signature)
// Create network parameters // Create network parameters
val networkParametersHash = networkMapStorage.putNetworkParameters(testNetworkParameters(emptyList())) 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) val signedNetworkMap = SignedNetworkMap(NetworkMap(listOf(nodeInfoHash.toString()), networkParametersHash.toString()), signatureData)
// when // when
@ -121,7 +121,7 @@ class DBNetworkMapStorageTest : TestBase() {
} }
@Test @Test
fun `getDetachedSignedAndValidNodeInfoHashes returns only valid and signed node info hashes`() { fun `getDetachedAndValidNodeInfoHashes returns only valid and signed node info hashes`() {
// given // given
// Create node info. // Create node info.
val organisationA = "TestA" val organisationA = "TestA"
@ -139,11 +139,11 @@ class DBNetworkMapStorageTest : TestBase() {
requestStorage.putCertificatePath(requestIdB, certPathB, emptyList()) requestStorage.putCertificatePath(requestIdB, certPathB, emptyList())
val nodeInfoA = NodeInfo(listOf(NetworkHostAndPort("my.companyA.com", 1234)), listOf(PartyAndCertificate(certPathA)), 1, serial = 1L) 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 nodeInfoB = NodeInfo(listOf(NetworkHostAndPort("my.companyB.com", 1234)), listOf(PartyAndCertificate(certPathB)), 1, serial = 1L)
val nodeInfoHashA = nodeInfoStorage.putNodeInfo(nodeInfoA) // Put signed node info data
val nodeInfoHashB = nodeInfoStorage.putNodeInfo(nodeInfoB) val nodeInfoABytes = nodeInfoA.serialize()
// Sign node info val nodeInfoBBytes = nodeInfoB.serialize()
nodeInfoStorage.signNodeInfo(nodeInfoHashA, keyPair.sign(nodeInfoA.serialize())) val nodeInfoHashA = nodeInfoStorage.putNodeInfo(SignedData(nodeInfoABytes, keyPair.sign(nodeInfoABytes)))
nodeInfoStorage.signNodeInfo(nodeInfoHashB, keyPair.sign(nodeInfoB.serialize())) val nodeInfoHashB = nodeInfoStorage.putNodeInfo(SignedData(nodeInfoBBytes, keyPair.sign(nodeInfoBBytes)))
// Create network parameters // Create network parameters
val networkParametersHash = networkMapStorage.putNetworkParameters(createNetworkParameters()) val networkParametersHash = networkMapStorage.putNetworkParameters(createNetworkParameters())
@ -155,7 +155,7 @@ class DBNetworkMapStorageTest : TestBase() {
networkMapStorage.saveNetworkMap(signedNetworkMap) networkMapStorage.saveNetworkMap(signedNetworkMap)
// when // when
val detachedHashes = networkMapStorage.getDetachedSignedAndValidNodeInfoHashes() val detachedHashes = networkMapStorage.getDetachedAndValidNodeInfoHashes()
// then // then
assertEquals(1, detachedHashes.size) assertEquals(1, detachedHashes.size)

View File

@ -6,12 +6,11 @@ import com.r3.corda.networkmanage.common.utils.hashString
import com.r3.corda.networkmanage.common.utils.toX509Certificate import com.r3.corda.networkmanage.common.utils.toX509Certificate
import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash 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.crypto.sign
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.utilities.CertificateType import net.corda.node.utilities.CertificateType
@ -25,7 +24,6 @@ import org.junit.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertNotNull import kotlin.test.assertNotNull
import kotlin.test.assertNull import kotlin.test.assertNull
import kotlin.test.assertTrue
class PersitenceNodeInfoStorageTest : TestBase() { class PersitenceNodeInfoStorageTest : TestBase() {
private lateinit var requestStorage: CertificationRequestStorage private lateinit var requestStorage: CertificationRequestStorage
@ -72,8 +70,8 @@ class PersitenceNodeInfoStorageTest : TestBase() {
} }
@Test @Test
fun `test getNodeInfoHashes`() { fun `test getNodeInfoHash returns correct data`() {
// Create node info. // given
val organisationA = "TestA" val organisationA = "TestA"
val requestIdA = requestStorage.saveRequest(createRequest(organisationA).first) val requestIdA = requestStorage.saveRequest(createRequest(organisationA).first)
requestStorage.approveRequest(requestIdA, "TestUser") requestStorage.approveRequest(requestIdA, "TestUser")
@ -88,22 +86,23 @@ class PersitenceNodeInfoStorageTest : TestBase() {
val certPathB = buildCertPath(clientCertB.toX509Certificate(), intermediateCACert.toX509Certificate(), rootCACert.toX509Certificate()) val certPathB = buildCertPath(clientCertB.toX509Certificate(), intermediateCACert.toX509Certificate(), rootCACert.toX509Certificate())
requestStorage.putCertificatePath(requestIdB, certPathB, emptyList()) requestStorage.putCertificatePath(requestIdB, certPathB, emptyList())
val nodeInfoA = NodeInfo(listOf(NetworkHostAndPort("my.company.com", 1234)), listOf(PartyAndCertificate(certPathA)), 1, serial = 1L) 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) val nodeInfoB = NodeInfo(listOf(NetworkHostAndPort("my.company.com", 1234)), listOf(PartyAndCertificate(certPathB)), 1, serial = 1L)
nodeInfoStorage.putNodeInfo(nodeInfoA) // Put signed node info data
nodeInfoStorage.putNodeInfo(nodeInfoSame) 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. // when
assertEquals(listOf(nodeInfoA.serialize().sha256()), nodeInfoStorage.getUnsignedNodeInfoHashes()) val persistedNodeInfoA = nodeInfoStorage.getNodeInfo(nodeInfoABytes.hash)
val persistedNodeInfoB = nodeInfoStorage.getNodeInfo(nodeInfoBBytes.hash)
nodeInfoStorage.putNodeInfo(nodeInfoB) // then
// getNodeInfoHashes should contain 2 hash. assertNotNull(persistedNodeInfoA)
assertEquals(listOf(nodeInfoB.serialize().sha256(), nodeInfoA.serialize().sha256()).sorted(), nodeInfoStorage.getUnsignedNodeInfoHashes().sorted()) assertNotNull(persistedNodeInfoB)
assertEquals(persistedNodeInfoA!!.verified(), nodeInfoA)
// Test retrieve NodeInfo. assertEquals(persistedNodeInfoB!!.verified(), nodeInfoB)
assertEquals(nodeInfoA, nodeInfoStorage.getNodeInfo(nodeInfoA.serialize().sha256()))
assertEquals(nodeInfoB, nodeInfoStorage.getNodeInfo(nodeInfoB.serialize().sha256()))
} }
@Test @Test
@ -119,19 +118,21 @@ class PersitenceNodeInfoStorageTest : TestBase() {
val nodeInfo = NodeInfo(listOf(NetworkHostAndPort("my.company.com", 1234)), listOf(PartyAndCertificate(certPath)), 1, serial = 1L) 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 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) val nodeInfoSamePubKeyBytes = nodeInfoSamePubKey.serialize()
assertEquals(nodeInfo, nodeInfoStorage.getNodeInfo(nodeInfo.serialize().sha256()))
// This should replace the node info. // This should replace the node info.
nodeInfoStorage.putNodeInfo(nodeInfoSamePubKey) nodeInfoStorage.putNodeInfo(SignedData(nodeInfoSamePubKeyBytes, keyPair.sign(nodeInfoSamePubKeyBytes)))
// Old node info should be removed. // Old node info should be removed.
assertNull(nodeInfoStorage.getNodeInfo(nodeInfo.serialize().sha256())) assertNull(nodeInfoStorage.getNodeInfo(nodeInfoHash))
assertEquals(nodeInfoSamePubKey, nodeInfoStorage.getNodeInfo(nodeInfoSamePubKey.serialize().sha256())) assertEquals(nodeInfoSamePubKey, nodeInfoStorage.getNodeInfo(nodeInfoSamePubKeyBytes.hash)?.verified())
} }
@Test @Test
fun `signNodeInfo associates signature to with node info`() { fun `putNodeInfo persists node info data with its signature`() {
// given // given
// Create node info. // Create node info.
val organisation = "Test" val organisation = "Test"
@ -143,38 +144,16 @@ class PersitenceNodeInfoStorageTest : TestBase() {
requestStorage.putCertificatePath(requestId, certPath, emptyList()) requestStorage.putCertificatePath(requestId, certPath, emptyList())
val nodeInfo = NodeInfo(listOf(NetworkHostAndPort("my.company.com", 1234)), listOf(PartyAndCertificate(certPath)), 1, serial = 1L) val nodeInfo = NodeInfo(listOf(NetworkHostAndPort("my.company.com", 1234)), listOf(PartyAndCertificate(certPath)), 1, serial = 1L)
val nodeInfoHash = nodeInfoStorage.putNodeInfo(nodeInfo) val nodeInfoBytes = nodeInfo.serialize()
// Some random bytes val signature = keyPair.sign(nodeInfoBytes)
val signature = keyPair.sign(nodeInfo.serialize())
// when // when
nodeInfoStorage.signNodeInfo(nodeInfoHash, signature) val nodeInfoHash = nodeInfoStorage.putNodeInfo(SignedData(nodeInfoBytes, signature))
// then // then
val signedNodeInfo = nodeInfoStorage.getSignedNodeInfo(nodeInfoHash) val persistedNodeInfo = nodeInfoStorage.getNodeInfo(nodeInfoHash)
assertEquals(signature, signedNodeInfo?.sig) assertNotNull(persistedNodeInfo)
} assertEquals(nodeInfo, persistedNodeInfo!!.verified())
assertEquals(signature, persistedNodeInfo.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()!!)
} }
} }

View File

@ -32,7 +32,7 @@ class NetworkMapSignerTest : TestBase() {
whenever(networkMapStorage.getCurrentNetworkMap()) whenever(networkMapStorage.getCurrentNetworkMap())
.thenReturn(SignedNetworkMap(NetworkMap(signedNodeInfoHashes.map { it.toString() }, "Dummy"), mock())) .thenReturn(SignedNetworkMap(NetworkMap(signedNodeInfoHashes.map { it.toString() }, "Dummy"), mock()))
whenever(networkMapStorage.getCurrentNetworkMapNodeInfoHashes(any())).thenReturn(signedNodeInfoHashes) whenever(networkMapStorage.getCurrentNetworkMapNodeInfoHashes(any())).thenReturn(signedNodeInfoHashes)
whenever(networkMapStorage.getDetachedSignedAndValidNodeInfoHashes()).thenReturn(detachedNodeInfoHashes) whenever(networkMapStorage.getDetachedAndValidNodeInfoHashes()).thenReturn(detachedNodeInfoHashes)
whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(networkMapParameters) whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(networkMapParameters)
whenever(signer.sign(any())).thenReturn(mock()) whenever(signer.sign(any())).thenReturn(mock())
@ -42,7 +42,7 @@ class NetworkMapSignerTest : TestBase() {
// then // then
// Verify networkMapStorage calls // Verify networkMapStorage calls
verify(networkMapStorage).getCurrentNetworkMapNodeInfoHashes(any()) verify(networkMapStorage).getCurrentNetworkMapNodeInfoHashes(any())
verify(networkMapStorage).getDetachedSignedAndValidNodeInfoHashes() verify(networkMapStorage).getDetachedAndValidNodeInfoHashes()
verify(networkMapStorage).getLatestNetworkParameters() verify(networkMapStorage).getLatestNetworkParameters()
argumentCaptor<SignedNetworkMap>().apply { argumentCaptor<SignedNetworkMap>().apply {
verify(networkMapStorage).saveNetworkMap(capture()) verify(networkMapStorage).saveNetworkMap(capture())
@ -63,7 +63,7 @@ class NetworkMapSignerTest : TestBase() {
val signedNetworkMap = SignedNetworkMap(networkMap, mock()) val signedNetworkMap = SignedNetworkMap(networkMap, mock())
whenever(networkMapStorage.getCurrentNetworkMap()).thenReturn(signedNetworkMap) whenever(networkMapStorage.getCurrentNetworkMap()).thenReturn(signedNetworkMap)
whenever(networkMapStorage.getCurrentNetworkMapNodeInfoHashes(any())).thenReturn(emptyList()) whenever(networkMapStorage.getCurrentNetworkMapNodeInfoHashes(any())).thenReturn(emptyList())
whenever(networkMapStorage.getDetachedSignedAndValidNodeInfoHashes()).thenReturn(emptyList()) whenever(networkMapStorage.getDetachedAndValidNodeInfoHashes()).thenReturn(emptyList())
whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(networkMapParameters) whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(networkMapParameters)
// when // when

View File

@ -4,7 +4,6 @@ import com.nhaarman.mockito_kotlin.any
import com.nhaarman.mockito_kotlin.mock import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.times import com.nhaarman.mockito_kotlin.times
import com.nhaarman.mockito_kotlin.verify 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.NetworkMapStorage
import com.r3.corda.networkmanage.common.persistence.NodeInfoStorage import com.r3.corda.networkmanage.common.persistence.NodeInfoStorage
import com.r3.corda.networkmanage.common.signer.NetworkMap 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.core.utilities.NetworkHostAndPort
import net.corda.node.utilities.CertificateType import net.corda.node.utilities.CertificateType
import net.corda.node.utilities.X509Utilities import net.corda.node.utilities.X509Utilities
import net.corda.nodeapi.internal.serialization.* import net.corda.testing.SerializationEnvironmentRule
import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme
import net.corda.testing.common.internal.testNetworkParameters
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import org.junit.Rule
import org.junit.Test import org.junit.Test
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.io.IOException import java.io.IOException
@ -34,7 +32,12 @@ import javax.ws.rs.core.MediaType
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFailsWith 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 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 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) 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 { DoormanServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(nodeInfoStorage, mock())).use {
it.start() 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 val nodeInfoAndSignature = SignedData(nodeInfo.serialize(), digitalSignature).serialize().bytes
// Post node info and signature to doorman // Post node info and signature to doorman
doPost(registerURL, nodeInfoAndSignature) doPost(registerURL, nodeInfoAndSignature)
@ -83,7 +86,7 @@ class NodeInfoWebServiceTest : TestBase() {
DoormanServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(nodeInfoStorage, mock())).use { DoormanServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(nodeInfoStorage, mock())).use {
it.start() 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 val nodeInfoAndSignature = SignedData(nodeInfo.serialize(), digitalSignature).serialize().bytes
// Post node info and signature to doorman // Post node info and signature to doorman
assertFailsWith(IOException::class) { assertFailsWith(IOException::class) {
@ -101,7 +104,7 @@ class NodeInfoWebServiceTest : TestBase() {
} }
DoormanServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(mock(), networkMapStorage)).use { DoormanServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(mock(), networkMapStorage)).use {
it.start() 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<SignedNetworkMap>() val signedHashedNetworkMap = conn.inputStream.readBytes().deserialize<SignedNetworkMap>()
verify(networkMapStorage, times(1)).getCurrentNetworkMap() verify(networkMapStorage, times(1)).getCurrentNetworkMap()
assertEquals(signedHashedNetworkMap.networkMap, hashedNetworkMap) assertEquals(signedHashedNetworkMap.networkMap, hashedNetworkMap)
@ -119,19 +122,19 @@ class NodeInfoWebServiceTest : TestBase() {
val nodeInfoStorage: NodeInfoStorage = mock { val nodeInfoStorage: NodeInfoStorage = mock {
val serializedNodeInfo = nodeInfo.serialize() 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 { DoormanServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(nodeInfoStorage, mock())).use {
it.start() 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 conn = nodeInfoURL.openConnection()
val nodeInfoResponse = conn.inputStream.readBytes().deserialize<SignedData<NodeInfo>>() val nodeInfoResponse = conn.inputStream.readBytes().deserialize<SignedData<NodeInfo>>()
verify(nodeInfoStorage, times(1)).getSignedNodeInfo(nodeInfoHash) verify(nodeInfoStorage, times(1)).getNodeInfo(nodeInfoHash)
assertEquals(nodeInfo, nodeInfoResponse.verified()) assertEquals(nodeInfo, nodeInfoResponse.verified())
assertFailsWith(FileNotFoundException::class) { 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()
} }
} }
} }

View File

@ -169,7 +169,7 @@ class RegistrationWebServiceTest : TestBase() {
} }
private fun submitRequest(request: PKCS10CertificationRequest): String { 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.doOutput = true
conn.requestMethod = "POST" conn.requestMethod = "POST"
conn.setRequestProperty("Content-Type", MediaType.APPLICATION_OCTET_STREAM) conn.setRequestProperty("Content-Type", MediaType.APPLICATION_OCTET_STREAM)
@ -178,7 +178,7 @@ class RegistrationWebServiceTest : TestBase() {
} }
private fun pollForResponse(id: String): PollResponse { 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 val conn = url.openConnection() as HttpURLConnection
conn.requestMethod = "GET" conn.requestMethod = "GET"