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 {
// 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') {

View File

@ -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<HsmSimulator>()
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
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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()

View File

@ -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<SecureHash>
fun getDetachedAndValidNodeInfoHashes(): List<SecureHash>
/**
* Retrieve network parameters by their hash.

View File

@ -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<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
* @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.
* @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<NodeInfo>): SecureHash
}

View File

@ -98,7 +98,7 @@ class PersistentNetworkMapStorage(private val database: CordaPersistence) : Netw
session.createQuery(query).resultList.first()
}
override fun getDetachedSignedAndValidNodeInfoHashes(): List<SecureHash> = database.transaction {
override fun getDetachedAndValidNodeInfoHashes(): List<SecureHash> = database.transaction {
val builder = session.criteriaBuilder
// Get signed NodeInfoEntities
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.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<NodeInfo>): 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<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")
/*
* 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<CertificateSigningRequestEntity>(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<NodeInfo>? = database.transaction {
override fun getNodeInfo(nodeInfoHash: SecureHash): SignedData<NodeInfo>? = 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<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? {
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<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() {
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())

View File

@ -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

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.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<SignedData<NodeInfo>>()
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

View File

@ -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,

View File

@ -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) {

View File

@ -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<CertificateNameAndPass>,
parentCertificateName: String,
parentPrivateKeyPassword: String,

View File

@ -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<HsmNetworkMapSigner>()
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)
}
/**

View File

@ -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<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(
requestId: String = SecureHash.randomSHA256().toString(),
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.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)

View File

@ -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)
}
}

View File

@ -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<SignedNetworkMap>().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

View File

@ -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<SignedNetworkMap>()
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<SignedData<NodeInfo>>()
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()
}
}
}

View File

@ -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"