Some long overdue cleaning up of some of the doorman code. (#362)

This commit is contained in:
Shams Asari 2018-01-18 23:08:02 +00:00 committed by GitHub
parent e9f0c8eca8
commit 2f8836c8ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 577 additions and 516 deletions

View File

@ -1,6 +1,7 @@
package com.r3.corda.networkmanage.doorman
import com.r3.corda.networkmanage.common.persistence.configureDatabase
import com.r3.corda.networkmanage.common.utils.CertPathAndKey
import com.r3.corda.networkmanage.doorman.signer.LocalSigner
import net.corda.cordform.CordformNode
import net.corda.core.crypto.random63BitValue
@ -141,11 +142,11 @@ class NodeRegistrationTest : IntegrationTest() {
start(
serverAddress,
configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true)),
LocalSigner(csrCa.keyPair, arrayOf(csrCa.certificate, rootCaCert)),
CertPathAndKey(listOf(csrCa.certificate, rootCaCert), csrCa.keyPair.private),
DoormanConfig(approveAll = true, jiraConfig = null, approveInterval = timeoutMillis),
networkParameters?.let {
NetworkMapStartParams(
LocalSigner(networkMapCa.keyPair, arrayOf(networkMapCa.certificate, rootCaCert)),
LocalSigner(networkMapCa),
networkParameters,
NetworkMapConfig(cacheTimeout = timeoutMillis, signInterval = timeoutMillis)
)

View File

@ -12,6 +12,7 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.test.assertTrue
class HsmTest {
@ -19,7 +20,12 @@ class HsmTest {
@Rule
@JvmField
val hsmSimulator: HsmSimulator = HsmSimulator()
val testParameters = Parameters(
@Rule
@JvmField
val tempFolder = TemporaryFolder()
private val testParameters = Parameters(
dataSourceProperties = mock(),
device = "${hsmSimulator.port}@${hsmSimulator.host}",
keySpecifier = 1,
@ -30,10 +36,6 @@ class HsmTest {
validDays = 3650
)
@Rule
@JvmField
val tempFolder = TemporaryFolder()
private lateinit var inputReader: InputReader
@Before
@ -47,14 +49,12 @@ class HsmTest {
fun `Authenticator executes the block once user is successfully authenticated`() {
// given
val authenticator = Authenticator(testParameters.createProvider(), inputReader = inputReader)
var executed = false
val executed = AtomicBoolean(false)
// when
authenticator.connectAndAuthenticate({ provider, signers ->
executed = true
})
authenticator.connectAndAuthenticate { _, _ -> executed.set(true) }
// then
assertTrue(executed)
assertTrue(executed.get())
}
}

View File

@ -90,7 +90,12 @@ class SigningServiceIntegrationTest {
val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true))
NetworkManagementServer().use { server ->
server.start(NetworkHostAndPort(HOST, 0), database, doormanServiceParameter = DoormanConfig(approveAll = true, approveInterval = 2.seconds.toMillis(), jiraConfig = null), startNetworkMap = null)
server.start(
hostAndPort = NetworkHostAndPort(HOST, 0),
database = database,
csrCertPathAndKey = null,
doormanServiceParameter = DoormanConfig(approveAll = true, approveInterval = 2.seconds.toMillis(), jiraConfig = null),
startNetworkMap = null)
val doormanHostAndPort = server.hostAndPort
// Start Corda network registration.
val config = createConfig().also {

View File

@ -39,10 +39,9 @@ interface NetworkMapStorage {
fun getSignedNetworkParameters(hash: SecureHash): SignedNetworkParameters?
/**
* Retrieve network map parameters.
* @return signed current network map parameters or null if they don't exist
* Retrieve the network parameters of the current network map, or null if there's no network map.
*/
fun getCurrentSignedNetworkParameters(): SignedNetworkParameters?
fun getNetworkParametersOfNetworkMap(): SignedNetworkParameters?
/**
* Persists given network parameters with signature if provided.
@ -55,5 +54,5 @@ interface NetworkMapStorage {
* Note that they may not have been signed up yet.
* @return latest network parameters
*/
fun getLatestUnsignedNetworkParameters(): NetworkParameters
}
fun getLatestNetworkParameters(): NetworkParameters?
}

View File

@ -6,10 +6,7 @@ import com.r3.corda.networkmanage.common.utils.SignedNetworkParameters
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import net.corda.core.internal.DigitalSignatureWithCert
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.nodeapi.internal.network.NetworkMap
import net.corda.nodeapi.internal.network.NetworkParameters
import net.corda.nodeapi.internal.persistence.CordaPersistence
@ -19,18 +16,16 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence
class PersistentNetworkMapStorage(private val database: CordaPersistence) : NetworkMapStorage {
override fun getCurrentNetworkMap(): SignedNetworkMap? {
return database.transaction {
getCurrentNetworkMapEntity()?.let {
val signatureAndCertPath = it.signatureAndCertificate()
SignedNetworkMap(SerializedBytes(it.networkMap), signatureAndCertPath)
}
getCurrentNetworkMapEntity()?.toSignedNetworkMap()
}
}
override fun getCurrentSignedNetworkParameters(): SignedNetworkParameters? {
override fun getNetworkParametersOfNetworkMap(): SignedNetworkParameters? {
return database.transaction {
getCurrentNetworkMapEntity()?.let {
val netParamsHash = it.networkMap.deserialize<NetworkMap>().networkParameterHash
getSignedNetworkParameters(netParamsHash)
val netParamsHash = it.toNetworkMap().networkParameterHash
getSignedNetworkParameters(netParamsHash) ?:
throw IllegalStateException("Current network map is pointing to network parameters that do not exist: $netParamsHash")
}
}
}
@ -47,7 +42,9 @@ class PersistentNetworkMapStorage(private val database: CordaPersistence) : Netw
}
override fun getSignedNetworkParameters(hash: SecureHash): SignedNetworkParameters? {
return getNetworkParametersEntity(hash.toString())?.signedParameters()
return getNetworkParametersEntity(hash.toString())?.let {
if (it.isSigned) it.toSignedNetworkParameters() else null
}
}
override fun getNodeInfoHashes(certificateStatus: CertificateStatus): List<SecureHash> {
@ -79,18 +76,17 @@ class PersistentNetworkMapStorage(private val database: CordaPersistence) : Netw
}
}
override fun getLatestUnsignedNetworkParameters(): NetworkParameters = getLatestNetworkParametersEntity().networkParameters()
private fun getLatestNetworkParametersEntity(): NetworkParametersEntity {
override fun getLatestNetworkParameters(): NetworkParameters? {
return database.transaction {
val builder = session.criteriaBuilder
val query = builder.createQuery(NetworkParametersEntity::class.java).run {
from(NetworkParametersEntity::class.java).run {
orderBy(builder.desc(get<String>(NetworkParametersEntity::created.name)))
val query = session.criteriaBuilder.run {
createQuery(NetworkParametersEntity::class.java).run {
from(NetworkParametersEntity::class.java).run {
orderBy(desc(get<String>(NetworkParametersEntity::created.name)))
}
}
}
// We just want the last entry
session.createQuery(query).setMaxResults(1).resultList.singleOrNull() ?: throw IllegalArgumentException("No network parameters found in network map storage")
session.createQuery(query).setMaxResults(1).uniqueResult()?.toNetworkParameters()
}
}

View File

@ -1,7 +1,11 @@
package com.r3.corda.networkmanage.common.persistence.entity
import com.r3.corda.networkmanage.common.utils.SignedNetworkMap
import net.corda.core.internal.DigitalSignatureWithCert
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
import net.corda.nodeapi.internal.network.NetworkMap
import javax.persistence.*
@Entity
@ -23,11 +27,12 @@ class NetworkMapEntity(
@Column(name = "certificate")
val certificate: ByteArray
) {
/**
* Deserializes NetworkMapEntity.signatureBytes into the [DigitalSignatureWithCert] instance
*/
fun signatureAndCertificate(): DigitalSignatureWithCert {
return DigitalSignatureWithCert(X509CertificateFactory().generateCertificate(certificate.inputStream()), signature)
}
fun toNetworkMap(): NetworkMap = networkMap.deserialize()
}
fun toSignedNetworkMap(): SignedNetworkMap {
return SignedNetworkMap(
SerializedBytes(networkMap),
DigitalSignatureWithCert(X509CertificateFactory().generateCertificate(certificate.inputStream()), signature)
)
}
}

View File

@ -35,13 +35,15 @@ class NetworkParametersEntity(
@Column(name = "certificate")
val certificate: ByteArray?
) {
fun networkParameters(): NetworkParameters = parametersBytes.deserialize()
val isSigned: Boolean get() = certificate != null && signature != null
// Return signed network parameters or null if they haven't been signed yet.
fun signedParameters(): SignedNetworkParameters? {
return if (certificate != null && signature != null) {
val sigWithCert = DigitalSignatureWithCert(X509CertificateFactory().generateCertificate(certificate.inputStream()), signature)
SignedDataWithCert(SerializedBytes(parametersBytes), sigWithCert)
} else null
fun toNetworkParameters(): NetworkParameters = parametersBytes.deserialize()
fun toSignedNetworkParameters(): SignedNetworkParameters {
if (certificate == null || signature == null) throw IllegalStateException("Network parameters entity is not signed: $parametersHash")
return SignedDataWithCert(
SerializedBytes(parametersBytes),
DigitalSignatureWithCert(X509CertificateFactory().generateCertificate(certificate.inputStream()), signature)
)
}
}
}

View File

@ -4,20 +4,30 @@ import com.r3.corda.networkmanage.common.persistence.CertificateStatus
import com.r3.corda.networkmanage.common.persistence.NetworkMapStorage
import net.corda.core.internal.SignedDataWithCert
import net.corda.core.serialization.serialize
import net.corda.core.utilities.contextLogger
import net.corda.nodeapi.internal.network.NetworkMap
import net.corda.nodeapi.internal.network.NetworkParameters
class NetworkMapSigner(private val networkMapStorage: NetworkMapStorage, private val signer: Signer) {
private companion object {
val logger = contextLogger()
}
/**
* Signs the network map and latest network parameters if they haven't been signed yet.
*/
fun signNetworkMap() {
// TODO There is no network parameters update process in place yet. We assume that latest parameters are to be used
// in current network map.
val latestNetworkParameters = networkMapStorage.getLatestUnsignedNetworkParameters()
val currentNetworkParameters = networkMapStorage.getCurrentSignedNetworkParameters()
if (currentNetworkParameters?.verified() != latestNetworkParameters)
signNetworkParameters(latestNetworkParameters)
val latestNetworkParameters = networkMapStorage.getLatestNetworkParameters()
if (latestNetworkParameters == null) {
logger.debug("No network parameters present")
return
}
val currentNetworkParameters = networkMapStorage.getNetworkParametersOfNetworkMap()
if (currentNetworkParameters?.verified() != latestNetworkParameters) {
persistSignedNetworkParameters(latestNetworkParameters)
}
val currentSignedNetworkMap = networkMapStorage.getCurrentNetworkMap()
val nodeInfoHashes = networkMapStorage.getNodeInfoHashes(CertificateStatus.VALID)
val serialisedNetworkMap = NetworkMap(nodeInfoHashes, latestNetworkParameters.serialize().hash).serialize()
@ -27,10 +37,8 @@ class NetworkMapSigner(private val networkMapStorage: NetworkMapStorage, private
}
}
/**
* Signs latest inserted network parameters.
*/
fun signNetworkParameters(networkParameters: NetworkParameters) {
fun persistSignedNetworkParameters(networkParameters: NetworkParameters) {
logger.info("Signing and persisting network parameters: $networkParameters")
val digitalSignature = signer.signObject(networkParameters).sig
networkMapStorage.saveNetworkParameters(networkParameters, digitalSignature)
}

View File

@ -10,14 +10,20 @@ import net.corda.core.internal.SignedDataWithCert
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
import net.corda.nodeapi.internal.network.NetworkMap
import net.corda.nodeapi.internal.network.NetworkParameters
import java.security.KeyPair
import java.security.PrivateKey
import java.security.PublicKey
import java.security.cert.CertPath
import java.security.cert.Certificate
import java.security.cert.X509Certificate
// TODO These should be defined in node-api
typealias SignedNetworkParameters = SignedDataWithCert<NetworkParameters>
typealias SignedNetworkMap = SignedDataWithCert<NetworkMap>
data class CertPathAndKey(val certPath: List<X509Certificate>, val key: PrivateKey) {
fun toKeyPair(): KeyPair = KeyPair(certPath[0].publicKey, key)
}
// TODO: replace this with Crypto.hash when its available.
/**
* Returns SHA256 hash of this public key
@ -42,7 +48,7 @@ fun Array<out String>.toConfigWithOptions(registerOptions: OptionParser.() -> Un
class ShowHelpException(val parser: OptionParser, val errorMessage: String? = null) : Exception()
fun buildCertPath(vararg certificates: Certificate): CertPath = X509CertificateFactory().delegate.generateCertPath(certificates.asList())
fun buildCertPath(vararg certificates: X509Certificate): CertPath = X509CertificateFactory().generateCertPath(certificates.asList())
fun buildCertPath(certPathBytes: ByteArray): CertPath = X509CertificateFactory().delegate.generateCertPath(certPathBytes.inputStream())

View File

@ -1,16 +1,11 @@
package com.r3.corda.networkmanage.doorman
import com.atlassian.jira.rest.client.internal.async.AsynchronousJiraRestClientFactory
import com.r3.corda.networkmanage.common.persistence.*
import com.r3.corda.networkmanage.common.persistence.CertificationRequestStorage
import com.r3.corda.networkmanage.common.persistence.CertificationRequestStorage.Companion.DOORMAN_SIGNATURE
import com.r3.corda.networkmanage.common.signer.NetworkMapSigner
import com.r3.corda.networkmanage.common.persistence.configureDatabase
import com.r3.corda.networkmanage.common.utils.CertPathAndKey
import com.r3.corda.networkmanage.common.utils.ShowHelpException
import com.r3.corda.networkmanage.doorman.signer.DefaultCsrHandler
import com.r3.corda.networkmanage.doorman.signer.JiraCsrHandler
import com.r3.corda.networkmanage.doorman.signer.LocalSigner
import com.r3.corda.networkmanage.doorman.webservice.MonitoringWebService
import com.r3.corda.networkmanage.doorman.webservice.NodeInfoWebService
import com.r3.corda.networkmanage.doorman.webservice.RegistrationWebService
import com.r3.corda.networkmanage.hsm.configuration.Parameters.Companion.DEFAULT_CSR_CERTIFICATE_NAME
import com.r3.corda.networkmanage.hsm.configuration.Parameters.Companion.DEFAULT_NETWORK_MAP_CERTIFICATE_NAME
import net.corda.core.crypto.Crypto
@ -21,141 +16,19 @@ import net.corda.core.internal.div
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
import net.corda.core.serialization.internal.nodeSerializationEnv
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.loggerFor
import net.corda.nodeapi.internal.crypto.*
import net.corda.nodeapi.internal.network.NetworkParameters
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
import net.corda.nodeapi.internal.serialization.amqp.AMQPClientSerializationScheme
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import java.io.Closeable
import java.net.URI
import java.nio.file.Path
import java.security.cert.X509Certificate
import java.time.Instant
import java.util.*
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import javax.security.auth.x500.X500Principal
import kotlin.concurrent.thread
import kotlin.system.exitProcess
class NetworkManagementServer : Closeable {
private val doOnClose = mutableListOf<() -> Unit>()
lateinit var hostAndPort: NetworkHostAndPort
override fun close() = doOnClose.forEach { it() }
companion object {
private val logger = loggerFor<NetworkManagementServer>()
}
private fun getNetworkMapService(config: NetworkMapConfig, database: CordaPersistence, signer: LocalSigner?, updateNetworkParameters: NetworkParameters?): NodeInfoWebService {
val networkMapStorage = PersistentNetworkMapStorage(database)
val nodeInfoStorage = PersistentNodeInfoStorage(database)
val localNetworkMapSigner = if (signer != null) NetworkMapSigner(networkMapStorage, signer) else null
updateNetworkParameters?.let {
// Persisting new network parameters
val currentNetworkParameters = networkMapStorage.getCurrentSignedNetworkParameters()
if (currentNetworkParameters == null) {
localNetworkMapSigner?.signNetworkParameters(it) ?: networkMapStorage.saveNetworkParameters(it, null)
} else {
throw UnsupportedOperationException("Network parameters already exist. Updating them via the file config is not supported yet.")
}
}
// This call will fail if parameter is null in DB.
try {
val latestParameter = networkMapStorage.getLatestUnsignedNetworkParameters()
logger.info("Starting network map service with network parameters : $latestParameter")
} catch (e: NoSuchElementException) {
logger.error("No network parameter found, please upload new network parameter before starting network map service. The server will now exit.")
exitProcess(-1)
}
// Thread sign network map in case of change (i.e. a new node info has been added or a node info has been removed).
if (localNetworkMapSigner != null) {
val scheduledExecutor = Executors.newScheduledThreadPool(1)
val signingThread = Runnable {
try {
localNetworkMapSigner.signNetworkMap()
} catch (e: Exception) {
// Log the error and carry on.
logger.error("Error encountered when processing node info changes.", e)
}
}
scheduledExecutor.scheduleAtFixedRate(signingThread, config.signInterval, config.signInterval, TimeUnit.MILLISECONDS)
doOnClose += { scheduledExecutor.shutdown() }
}
return NodeInfoWebService(nodeInfoStorage, networkMapStorage, config)
}
private fun getDoormanService(config: DoormanConfig, database: CordaPersistence, signer: LocalSigner?, serverStatus: NetworkManagementServerStatus): RegistrationWebService {
logger.info("Starting Doorman server.")
val requestService = if (config.approveAll) {
logger.warn("Doorman server is in 'Approve All' mode, this will approve all incoming certificate signing requests.")
ApproveAllCertificateRequestStorage(PersistentCertificateRequestStorage(database))
} else {
PersistentCertificateRequestStorage(database)
}
val jiraConfig = config.jiraConfig
val requestProcessor = if (jiraConfig != null) {
val jiraWebAPI = AsynchronousJiraRestClientFactory().createWithBasicHttpAuthentication(URI(jiraConfig.address), jiraConfig.username, jiraConfig.password)
val jiraClient = JiraClient(jiraWebAPI, jiraConfig.projectCode, jiraConfig.doneTransitionCode)
JiraCsrHandler(jiraClient, requestService, DefaultCsrHandler(requestService, signer))
} else {
DefaultCsrHandler(requestService, signer)
}
val scheduledExecutor = Executors.newScheduledThreadPool(1)
val approvalThread = Runnable {
try {
serverStatus.lastRequestCheckTime = Instant.now()
// Create tickets for requests which don't have one yet.
requestProcessor.createTickets()
// Process Jira approved tickets.
requestProcessor.processApprovedRequests()
} catch (e: Exception) {
// Log the error and carry on.
logger.error("Error encountered when approving request.", e)
}
}
scheduledExecutor.scheduleAtFixedRate(approvalThread, config.approveInterval, config.approveInterval, TimeUnit.MILLISECONDS)
doOnClose += { scheduledExecutor.shutdown() }
return RegistrationWebService(requestProcessor)
}
fun start(hostAndPort: NetworkHostAndPort,
database: CordaPersistence,
doormanSigner: LocalSigner? = null,
doormanServiceParameter: DoormanConfig?, // TODO Doorman config shouldn't be optional as the doorman is always required to run
startNetworkMap: NetworkMapStartParams?
) {
val services = mutableListOf<Any>()
val serverStatus = NetworkManagementServerStatus()
startNetworkMap?.let { services += getNetworkMapService(it.config, database, it.signer, it.updateNetworkParameters) }
doormanServiceParameter?.let { services += getDoormanService(it, database, doormanSigner, serverStatus) }
require(services.isNotEmpty()) { "No service created, please provide at least one service config." }
// TODO: use mbean to expose audit data?
services += MonitoringWebService(serverStatus)
val webServer = NetworkManagementWebServer(hostAndPort, *services.toTypedArray())
webServer.start()
doOnClose += webServer::close
this.hostAndPort = webServer.hostAndPort
}
}
data class NetworkMapStartParams(val signer: LocalSigner?, val updateNetworkParameters: NetworkParameters?, val config: NetworkMapConfig)
data class NetworkManagementServerStatus(var serverStartTime: Instant = Instant.now(), var lastRequestCheckTime: Instant? = null)
@ -181,9 +54,8 @@ fun generateRootKeyPair(rootStoreFile: Path, rootKeystorePass: String?, rootPriv
val rootPrivateKeyPassword = rootPrivateKeyPass ?: readPassword("Root Private Key Password: ")
if (rootStore.containsAlias(X509Utilities.CORDA_ROOT_CA)) {
val oldKey = loadOrCreateKeyStore(rootStoreFile, rootKeystorePassword).getCertificate(X509Utilities.CORDA_ROOT_CA).publicKey
println("Key ${X509Utilities.CORDA_ROOT_CA} already exists in keystore, process will now terminate.")
println(oldKey)
println("${X509Utilities.CORDA_ROOT_CA} already exists in keystore, process will now terminate.")
println(rootStore.getCertificate(X509Utilities.CORDA_ROOT_CA))
exitProcess(1)
}
@ -203,7 +75,7 @@ fun generateRootKeyPair(rootStoreFile: Path, rootKeystorePass: String?, rootPriv
println("Trust store for distribution to nodes created in $nodeTrustStore")
println("Root CA keypair and certificate stored in ${rootStoreFile.toAbsolutePath()}.")
println(loadKeyStore(rootStoreFile, rootKeystorePassword).getCertificate(X509Utilities.CORDA_ROOT_CA).publicKey)
println(selfSignCert)
}
fun generateSigningKeyPairs(keystoreFile: Path, rootStoreFile: Path, rootKeystorePass: String?, rootPrivateKeyPass: String?, keystorePass: String?, caPrivateKeyPass: String?) {
@ -262,7 +134,7 @@ fun generateSigningKeyPairs(keystoreFile: Path, rootStoreFile: Path, rootKeystor
}
private fun buildLocalSigners(parameters: NetworkManagementServerParameters): Pair<LocalSigner, LocalSigner>? {
private fun processKeyStore(parameters: NetworkManagementServerParameters): Pair<CertPathAndKey, LocalSigner>? {
if (parameters.keystorePath == null) return null
// Get password from console if not in config.
@ -270,19 +142,22 @@ private fun buildLocalSigners(parameters: NetworkManagementServerParameters): Pa
val privateKeyPassword = parameters.caPrivateKeyPassword ?: readPassword("Private key password: ")
val keyStore = loadOrCreateKeyStore(parameters.keystorePath, keyStorePassword)
val (doormanSigner, networkMapSigner) = listOf(DEFAULT_CSR_CERTIFICATE_NAME, DEFAULT_NETWORK_MAP_CERTIFICATE_NAME).map {
val keyPair = keyStore.getKeyPair(it, privateKeyPassword)
val certPath = keyStore.getCertificateChain(it).map { it as X509Certificate }
LocalSigner(keyPair, certPath.toTypedArray())
val csrCertPathAndKey = keyStore.run {
CertPathAndKey(
keyStore.getCertificateChain(DEFAULT_CSR_CERTIFICATE_NAME).map { it as X509Certificate },
keyStore.getSupportedKey(DEFAULT_CSR_CERTIFICATE_NAME, privateKeyPassword)
)
}
return Pair(doormanSigner, networkMapSigner)
val networkMapSigner = LocalSigner(keyStore.getCertificateAndKeyPair(DEFAULT_NETWORK_MAP_CERTIFICATE_NAME, privateKeyPassword))
return Pair(csrCertPathAndKey, networkMapSigner)
}
/**
* This storage automatically approves all created requests.
*/
private class ApproveAllCertificateRequestStorage(private val delegate: CertificationRequestStorage) : CertificationRequestStorage by delegate {
class ApproveAllCertificateRequestStorage(private val delegate: CertificationRequestStorage) : CertificationRequestStorage by delegate {
override fun saveRequest(request: PKCS10CertificationRequest): String {
val requestId = delegate.saveRequest(request)
delegate.markRequestTicketCreated(requestId)
@ -311,9 +186,9 @@ fun main(args: Array<String>) {
initialiseSerialization()
val database = configureDatabase(dataSourceProperties)
// TODO: move signing to signing server.
val localSigners = buildLocalSigners(this)
val csrAndNetworkMap = processKeyStore(this)
if (localSigners != null) {
if (csrAndNetworkMap != null) {
println("Starting network management services with local signing")
}
@ -325,10 +200,10 @@ fun main(args: Array<String>) {
parseNetworkParametersFrom(it)
}
val networkMapStartParams = networkMapConfig?.let {
NetworkMapStartParams(localSigners?.second, networkParameters, it)
NetworkMapStartParams(csrAndNetworkMap?.second, networkParameters, it)
}
networkManagementServer.start(NetworkHostAndPort(host, port), database, localSigners?.first, doormanConfig, networkMapStartParams)
networkManagementServer.start(NetworkHostAndPort(host, port), database, csrAndNetworkMap?.first, doormanConfig, networkMapStartParams)
Runtime.getRuntime().addShutdownHook(thread(start = false) {
networkManagementServer.close()

View File

@ -1,58 +1,142 @@
package com.r3.corda.networkmanage.doorman
import com.atlassian.jira.rest.client.internal.async.AsynchronousJiraRestClientFactory
import com.r3.corda.networkmanage.common.persistence.PersistentCertificateRequestStorage
import com.r3.corda.networkmanage.common.persistence.PersistentNetworkMapStorage
import com.r3.corda.networkmanage.common.persistence.PersistentNodeInfoStorage
import com.r3.corda.networkmanage.common.signer.NetworkMapSigner
import com.r3.corda.networkmanage.common.utils.CertPathAndKey
import com.r3.corda.networkmanage.doorman.signer.DefaultCsrHandler
import com.r3.corda.networkmanage.doorman.signer.JiraCsrHandler
import com.r3.corda.networkmanage.doorman.signer.LocalSigner
import com.r3.corda.networkmanage.doorman.webservice.MonitoringWebService
import com.r3.corda.networkmanage.doorman.webservice.NetworkMapWebService
import com.r3.corda.networkmanage.doorman.webservice.RegistrationWebService
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.loggerFor
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.server.ServerConnector
import org.eclipse.jetty.server.handler.HandlerCollection
import org.eclipse.jetty.servlet.ServletContextHandler
import org.eclipse.jetty.servlet.ServletHolder
import org.glassfish.jersey.server.ResourceConfig
import org.glassfish.jersey.servlet.ServletContainer
import net.corda.nodeapi.internal.network.NetworkParameters
import net.corda.nodeapi.internal.persistence.CordaPersistence
import java.io.Closeable
import java.net.InetSocketAddress
import java.net.URI
import java.time.Instant
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
/**
* NetworkManagementWebServer runs on Jetty server and provides service via http.
*/
class NetworkManagementWebServer(hostAndPort: NetworkHostAndPort, private vararg val webServices: Any) : Closeable {
class NetworkManagementServer : Closeable {
companion object {
val logger = loggerFor<NetworkManagementServer>()
private val logger = loggerFor<NetworkManagementServer>()
}
private val server: Server = Server(InetSocketAddress(hostAndPort.host, hostAndPort.port)).apply {
handler = HandlerCollection().apply {
addHandler(buildServletContextHandler())
}
}
val hostAndPort: NetworkHostAndPort
get() = server.connectors.mapNotNull { it as? ServerConnector }
.map { NetworkHostAndPort(it.host, it.localPort) }
.first()
private val closeActions = mutableListOf<() -> Unit>()
lateinit var hostAndPort: NetworkHostAndPort
override fun close() {
logger.info("Shutting down network management web services...")
server.stop()
server.join()
}
fun start() {
logger.info("Starting network management web services...")
server.start()
logger.info("Network management web services started on $hostAndPort with ${webServices.map { it.javaClass.simpleName }}")
println("Network management web services started on $hostAndPort with ${webServices.map { it.javaClass.simpleName }}")
}
private fun buildServletContextHandler(): ServletContextHandler {
return ServletContextHandler().apply {
contextPath = "/"
val resourceConfig = ResourceConfig().apply {
// Add your API provider classes (annotated for JAX-RS) here
webServices.forEach { register(it) }
for (closeAction in closeActions) {
try {
closeAction()
} catch (e: Exception) {
logger.warn("Discregarding exception thrown during close", e)
}
val jerseyServlet = ServletHolder(ServletContainer(resourceConfig)).apply { initOrder = 0 }// Initialise at server start
addServlet(jerseyServlet, "/*")
}
}
private fun getNetworkMapService(config: NetworkMapConfig, database: CordaPersistence, signer: LocalSigner?, newNetworkParameters: NetworkParameters?): NetworkMapWebService {
val networkMapStorage = PersistentNetworkMapStorage(database)
val nodeInfoStorage = PersistentNodeInfoStorage(database)
val localNetworkMapSigner = signer?.let { NetworkMapSigner(networkMapStorage, it) }
newNetworkParameters?.let {
val netParamsOfNetworkMap = networkMapStorage.getNetworkParametersOfNetworkMap()
if (netParamsOfNetworkMap == null) {
localNetworkMapSigner?.persistSignedNetworkParameters(it) ?: networkMapStorage.saveNetworkParameters(it, null)
} else {
throw UnsupportedOperationException("Network parameters already exist. Updating them is not supported yet.")
}
}
val latestParameters = networkMapStorage.getLatestNetworkParameters() ?:
throw IllegalStateException("No network parameters were found. Please upload new network parameters before starting network map service")
logger.info("Starting network map service with network parameters: $latestParameters")
if (localNetworkMapSigner != null) {
logger.info("Starting background worker for signing the network map using the local key store")
val scheduledExecutor = Executors.newScheduledThreadPool(1)
scheduledExecutor.scheduleAtFixedRate({
try {
localNetworkMapSigner.signNetworkMap()
} catch (e: Exception) {
// Log the error and carry on.
logger.error("Unable to sign network map", e)
}
}, config.signInterval, config.signInterval, TimeUnit.MILLISECONDS)
closeActions += scheduledExecutor::shutdown
}
return NetworkMapWebService(nodeInfoStorage, networkMapStorage, config)
}
private fun getDoormanService(config: DoormanConfig,
database: CordaPersistence,
csrCertPathAndKey: CertPathAndKey?,
serverStatus: NetworkManagementServerStatus): RegistrationWebService {
logger.info("Starting Doorman server.")
val requestService = if (config.approveAll) {
logger.warn("Doorman server is in 'Approve All' mode, this will approve all incoming certificate signing requests.")
ApproveAllCertificateRequestStorage(PersistentCertificateRequestStorage(database))
} else {
PersistentCertificateRequestStorage(database)
}
val jiraConfig = config.jiraConfig
val requestProcessor = if (jiraConfig != null) {
val jiraWebAPI = AsynchronousJiraRestClientFactory().createWithBasicHttpAuthentication(URI(jiraConfig.address), jiraConfig.username, jiraConfig.password)
val jiraClient = JiraClient(jiraWebAPI, jiraConfig.projectCode, jiraConfig.doneTransitionCode)
JiraCsrHandler(jiraClient, requestService, DefaultCsrHandler(requestService, csrCertPathAndKey))
} else {
DefaultCsrHandler(requestService, csrCertPathAndKey)
}
val scheduledExecutor = Executors.newScheduledThreadPool(1)
val approvalThread = Runnable {
try {
serverStatus.lastRequestCheckTime = Instant.now()
// Create tickets for requests which don't have one yet.
requestProcessor.createTickets()
// Process Jira approved tickets.
requestProcessor.processApprovedRequests()
} catch (e: Exception) {
// Log the error and carry on.
logger.error("Error encountered when approving request.", e)
}
}
scheduledExecutor.scheduleAtFixedRate(approvalThread, config.approveInterval, config.approveInterval, TimeUnit.MILLISECONDS)
closeActions += scheduledExecutor::shutdown
return RegistrationWebService(requestProcessor)
}
fun start(hostAndPort: NetworkHostAndPort,
database: CordaPersistence,
csrCertPathAndKey: CertPathAndKey?,
doormanServiceParameter: DoormanConfig?, // TODO Doorman config shouldn't be optional as the doorman is always required to run
startNetworkMap: NetworkMapStartParams?
) {
val services = mutableListOf<Any>()
val serverStatus = NetworkManagementServerStatus()
startNetworkMap?.let { services += getNetworkMapService(it.config, database, it.signer, it.updateNetworkParameters) }
doormanServiceParameter?.let { services += getDoormanService(it, database, csrCertPathAndKey, serverStatus) }
require(services.isNotEmpty()) { "No service created, please provide at least one service config." }
// TODO: use mbean to expose audit data?
services += MonitoringWebService(serverStatus)
val webServer = NetworkManagementWebServer(hostAndPort, *services.toTypedArray())
webServer.start()
closeActions += webServer::close
this.hostAndPort = webServer.hostAndPort
}
}

View File

@ -0,0 +1,59 @@
package com.r3.corda.networkmanage.doorman
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.loggerFor
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.server.ServerConnector
import org.eclipse.jetty.server.handler.HandlerCollection
import org.eclipse.jetty.servlet.ServletContextHandler
import org.eclipse.jetty.servlet.ServletHolder
import org.glassfish.jersey.server.ResourceConfig
import org.glassfish.jersey.servlet.ServletContainer
import java.io.Closeable
import java.net.InetSocketAddress
/**
* NetworkManagementWebServer runs on Jetty server and provides service via http.
*/
class NetworkManagementWebServer(hostAndPort: NetworkHostAndPort, private vararg val webServices: Any) : Closeable {
companion object {
val logger = contextLogger()
}
private val server: Server = Server(InetSocketAddress(hostAndPort.host, hostAndPort.port)).apply {
handler = HandlerCollection().apply {
addHandler(buildServletContextHandler())
}
}
val hostAndPort: NetworkHostAndPort
get() = server.connectors.mapNotNull { it as? ServerConnector }
.map { NetworkHostAndPort(it.host, it.localPort) }
.first()
override fun close() {
logger.info("Shutting down network management web services...")
server.stop()
server.join()
}
fun start() {
logger.info("Starting network management web services...")
server.start()
logger.info("Network management web services started on $hostAndPort with ${webServices.map { it.javaClass.simpleName }}")
println("Network management web services started on $hostAndPort with ${webServices.map { it.javaClass.simpleName }}")
}
private fun buildServletContextHandler(): ServletContextHandler {
return ServletContextHandler().apply {
contextPath = "/"
val resourceConfig = ResourceConfig().apply {
// Add your API provider classes (annotated for JAX-RS) here
webServices.forEach { register(it) }
}
val jerseyServlet = ServletHolder(ServletContainer(resourceConfig)).apply { initOrder = 0 }// Initialise at server start
addServlet(jerseyServlet, "/*")
}
}
}

View File

@ -1,13 +1,20 @@
package com.r3.corda.networkmanage.doorman.signer
import com.r3.corda.networkmanage.common.persistence.CertificateResponse
import com.r3.corda.networkmanage.common.persistence.CertificateSigningRequest
import com.r3.corda.networkmanage.common.persistence.CertificationRequestStorage
import com.r3.corda.networkmanage.common.persistence.CertificationRequestStorage.Companion.DOORMAN_SIGNATURE
import com.r3.corda.networkmanage.common.persistence.RequestStatus
import com.r3.corda.networkmanage.doorman.JiraClient
import net.corda.core.utilities.loggerFor
import com.r3.corda.networkmanage.common.utils.CertPathAndKey
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
import net.corda.nodeapi.internal.crypto.X509Utilities
import org.bouncycastle.asn1.x509.GeneralName
import org.bouncycastle.asn1.x509.GeneralSubtree
import org.bouncycastle.asn1.x509.NameConstraints
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
import java.security.cert.CertPath
import javax.security.auth.x500.X500Principal
interface CsrHandler {
fun saveRequest(rawRequest: PKCS10CertificationRequest): String
@ -16,26 +23,21 @@ interface CsrHandler {
fun getResponse(requestId: String): CertificateResponse
}
class DefaultCsrHandler(private val storage: CertificationRequestStorage, private val signer: LocalSigner?) : CsrHandler {
class DefaultCsrHandler(private val storage: CertificationRequestStorage,
private val csrCertPathAndKey: CertPathAndKey?) : CsrHandler {
override fun processApprovedRequests() {
storage.getRequests(RequestStatus.APPROVED)
.forEach { processRequest(it.requestId, it.request) }
if (csrCertPathAndKey == null) return
storage.getRequests(RequestStatus.APPROVED).forEach {
val nodeCertPath = createSignedNodeCertificate(it.request, csrCertPathAndKey)
// Since Doorman is deployed in the auto-signing mode, we use DOORMAN_SIGNATURE as the signer.
storage.putCertificatePath(it.requestId, nodeCertPath, listOf(DOORMAN_SIGNATURE))
}
}
override fun createTickets() {}
private fun processRequest(requestId: String, request: PKCS10CertificationRequest) {
if (signer != null) {
val certs = signer.createSignedClientCertificate(request)
// Since Doorman is deployed in the auto-signing mode (i.e. signer != null),
// we use DOORMAN_SIGNATURE as the signer.
storage.putCertificatePath(requestId, certs, listOf(DOORMAN_SIGNATURE))
}
}
override fun saveRequest(rawRequest: PKCS10CertificationRequest): String {
return storage.saveRequest(rawRequest)
}
override fun saveRequest(rawRequest: PKCS10CertificationRequest): String = storage.saveRequest(rawRequest)
override fun getResponse(requestId: String): CertificateResponse {
val response = storage.getRequest(requestId)
@ -45,63 +47,25 @@ class DefaultCsrHandler(private val storage: CertificationRequestStorage, privat
RequestStatus.SIGNED -> CertificateResponse.Ready(response.certData?.certPath ?: throw IllegalArgumentException("Certificate should not be null."))
}
}
}
class JiraCsrHandler(private val jiraClient: JiraClient, private val storage: CertificationRequestStorage, private val delegate: CsrHandler) : CsrHandler by delegate {
private companion object {
val log = loggerFor<JiraCsrHandler>()
}
override fun saveRequest(rawRequest: PKCS10CertificationRequest): String {
val requestId = delegate.saveRequest(rawRequest)
// Make sure request has been accepted.
try {
if (delegate.getResponse(requestId) !is CertificateResponse.Unauthorised) {
jiraClient.createRequestTicket(requestId, rawRequest)
storage.markRequestTicketCreated(requestId)
}
} catch (e: Exception) {
log.warn("There was an error while creating Jira tickets", e)
} finally {
return requestId
}
}
override fun processApprovedRequests() {
val approvedRequest = jiraClient.getApprovedRequests()
approvedRequest.forEach { (id, approvedBy) -> storage.approveRequest(id, approvedBy) }
delegate.processApprovedRequests()
val signedRequests = approvedRequest.mapNotNull { (id, _) ->
val request = storage.getRequest(id)
if (request != null && request.status == RequestStatus.SIGNED) {
request.certData?.certPath?.let { certs -> id to certs }
} else {
null
}
}.toMap()
jiraClient.updateSignedRequests(signedRequests)
}
/**
* Creates Jira tickets for all request in [RequestStatus.NEW] state.
*
* Usually requests are expected to move to the [RequestStatus.TICKET_CREATED] state immediately,
* they might be left in the [RequestStatus.NEW] state if Jira is down.
*/
override fun createTickets() {
try {
for (signingRequest in storage.getRequests(RequestStatus.NEW)) {
createTicket(signingRequest)
}
} catch (e: Exception) {
log.warn("There were errors while creating Jira tickets", e)
}
}
private fun createTicket(signingRequest: CertificateSigningRequest) {
jiraClient.createRequestTicket(signingRequest.requestId, signingRequest.request)
storage.markRequestTicketCreated(signingRequest.requestId)
private fun createSignedNodeCertificate(certificationRequest: PKCS10CertificationRequest,
csrCertPathAndKey: CertPathAndKey): CertPath {
// The sub certs issued by the client must satisfy this directory name (or legal name in Corda) constraints,
// sub certs' directory name must be within client CA's name's subtree,
// please see [sun.security.x509.X500Name.isWithinSubtree()] for more information.
// We assume all attributes in the subject name has been checked prior approval.
// TODO: add validation to subject name.
val request = JcaPKCS10CertificationRequest(certificationRequest)
val nameConstraints = NameConstraints(
arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, request.subject))),
arrayOf())
val nodeCaCert = X509Utilities.createCertificate(
CertificateType.NODE_CA,
csrCertPathAndKey.certPath[0],
csrCertPathAndKey.toKeyPair(),
X500Principal(request.subject.encoded),
request.publicKey,
nameConstraints = nameConstraints)
return X509CertificateFactory().generateCertPath(listOf(nodeCaCert) + csrCertPathAndKey.certPath)
}
}

View File

@ -0,0 +1,69 @@
package com.r3.corda.networkmanage.doorman.signer
import com.r3.corda.networkmanage.common.persistence.CertificateResponse
import com.r3.corda.networkmanage.common.persistence.CertificateSigningRequest
import com.r3.corda.networkmanage.common.persistence.CertificationRequestStorage
import com.r3.corda.networkmanage.common.persistence.RequestStatus
import com.r3.corda.networkmanage.doorman.JiraClient
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.loggerFor
import org.bouncycastle.pkcs.PKCS10CertificationRequest
class JiraCsrHandler(private val jiraClient: JiraClient, private val storage: CertificationRequestStorage, private val delegate: CsrHandler) : CsrHandler by delegate {
private companion object {
val log = contextLogger()
}
override fun saveRequest(rawRequest: PKCS10CertificationRequest): String {
val requestId = delegate.saveRequest(rawRequest)
// Make sure request has been accepted.
try {
if (delegate.getResponse(requestId) !is CertificateResponse.Unauthorised) {
jiraClient.createRequestTicket(requestId, rawRequest)
storage.markRequestTicketCreated(requestId)
}
} catch (e: Exception) {
log.warn("There was an error while creating Jira tickets", e)
} finally {
return requestId
}
}
override fun processApprovedRequests() {
val approvedRequest = jiraClient.getApprovedRequests()
approvedRequest.forEach { (id, approvedBy) -> storage.approveRequest(id, approvedBy) }
delegate.processApprovedRequests()
val signedRequests = approvedRequest.mapNotNull { (id, _) ->
val request = storage.getRequest(id)
if (request != null && request.status == RequestStatus.SIGNED) {
request.certData?.certPath?.let { certs -> id to certs }
} else {
null
}
}.toMap()
jiraClient.updateSignedRequests(signedRequests)
}
/**
* Creates Jira tickets for all request in [RequestStatus.NEW] state.
*
* Usually requests are expected to move to the [RequestStatus.TICKET_CREATED] state immediately,
* they might be left in the [RequestStatus.NEW] state if Jira is down.
*/
override fun createTickets() {
try {
for (signingRequest in storage.getRequests(RequestStatus.NEW)) {
createTicket(signingRequest)
}
} catch (e: Exception) {
log.warn("There were errors while creating Jira tickets", e)
}
}
private fun createTicket(signingRequest: CertificateSigningRequest) {
jiraClient.createRequestTicket(signingRequest.requestId, signingRequest.request)
storage.markRequestTicketCreated(signingRequest.requestId)
}
}

View File

@ -1,48 +1,20 @@
package com.r3.corda.networkmanage.doorman.signer
import com.r3.corda.networkmanage.common.signer.Signer
import com.r3.corda.networkmanage.common.utils.buildCertPath
import net.corda.core.crypto.Crypto
import net.corda.core.internal.DigitalSignatureWithCert
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities
import org.bouncycastle.asn1.x509.GeneralName
import org.bouncycastle.asn1.x509.GeneralSubtree
import org.bouncycastle.asn1.x509.NameConstraints
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
import java.security.KeyPair
import java.security.cert.CertPath
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import java.security.KeyStore
import java.security.PrivateKey
import java.security.cert.X509Certificate
import javax.security.auth.x500.X500Principal
/**
* The [LocalSigner] class signs [PKCS10CertificationRequest] using provided CA key pair and certificate path.
* This is intended to be used in testing environment where hardware signing module is not available.
* This local signer is intended to be used in testing environment where hardware signing module is not available.
*/
//TODO Use a list instead of array
class LocalSigner(private val signingKeyPair: KeyPair, private val signingCertPath: Array<X509Certificate>) : Signer {
// TODO This doesn't belong in this class
fun createSignedClientCertificate(certificationRequest: PKCS10CertificationRequest): CertPath {
// The sub certs issued by the client must satisfy this directory name (or legal name in Corda) constraints, sub certs' directory name must be within client CA's name's subtree,
// please see [sun.security.x509.X500Name.isWithinSubtree()] for more information.
// We assume all attributes in the subject name has been checked prior approval.
// TODO: add validation to subject name.
val request = JcaPKCS10CertificationRequest(certificationRequest)
val nameConstraints = NameConstraints(
arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, request.subject))),
arrayOf())
val nodeCaCert = X509Utilities.createCertificate(
CertificateType.NODE_CA,
signingCertPath[0],
signingKeyPair,
X500Principal(request.subject.encoded),
request.publicKey,
nameConstraints = nameConstraints)
return buildCertPath(nodeCaCert, *signingCertPath)
}
class LocalSigner(private val signingKey: PrivateKey, private val signingCert: X509Certificate) : Signer {
constructor(certAndKeyPair: CertificateAndKeyPair) : this(certAndKeyPair.keyPair.private, certAndKeyPair.certificate)
override fun signBytes(data: ByteArray): DigitalSignatureWithCert {
return DigitalSignatureWithCert(signingCertPath[0], Crypto.doSign(signingKeyPair.private, data))
return DigitalSignatureWithCert(signingCert, Crypto.doSign(signingKey, data))
}
}

View File

@ -7,9 +7,8 @@ import com.r3.corda.networkmanage.common.persistence.NetworkMapStorage
import com.r3.corda.networkmanage.common.persistence.NodeInfoStorage
import com.r3.corda.networkmanage.common.persistence.NodeInfoWithSigned
import com.r3.corda.networkmanage.common.utils.SignedNetworkMap
import com.r3.corda.networkmanage.common.utils.SignedNetworkParameters
import com.r3.corda.networkmanage.doorman.NetworkMapConfig
import com.r3.corda.networkmanage.doorman.webservice.NodeInfoWebService.Companion.NETWORK_MAP_PATH
import com.r3.corda.networkmanage.doorman.webservice.NetworkMapWebService.Companion.NETWORK_MAP_PATH
import net.corda.core.crypto.SecureHash
import net.corda.core.node.NodeInfo
import net.corda.core.serialization.deserialize
@ -31,9 +30,9 @@ import javax.ws.rs.core.Response.ok
import javax.ws.rs.core.Response.status
@Path(NETWORK_MAP_PATH)
class NodeInfoWebService(private val nodeInfoStorage: NodeInfoStorage,
private val networkMapStorage: NetworkMapStorage,
private val config: NetworkMapConfig) {
class NetworkMapWebService(private val nodeInfoStorage: NodeInfoStorage,
private val networkMapStorage: NetworkMapStorage,
private val config: NetworkMapConfig) {
companion object {
val log = contextLogger()
@ -42,7 +41,7 @@ class NodeInfoWebService(private val nodeInfoStorage: NodeInfoStorage,
private val networkMapCache: LoadingCache<Boolean, Pair<SignedNetworkMap?, NetworkParameters?>> = CacheBuilder.newBuilder()
.expireAfterWrite(config.cacheTimeout, TimeUnit.MILLISECONDS)
.build(CacheLoader.from { _ -> Pair(networkMapStorage.getCurrentNetworkMap(), networkMapStorage.getCurrentSignedNetworkParameters()?.verified()) })
.build(CacheLoader.from { _ -> Pair(networkMapStorage.getCurrentNetworkMap(), networkMapStorage.getNetworkParametersOfNetworkMap()?.verified()) })
@POST
@Path("publish")

View File

@ -1,7 +1,10 @@
package com.r3.corda.networkmanage.hsm
import com.google.common.util.concurrent.MoreExecutors
import com.r3.corda.networkmanage.common.persistence.NetworkMapStorage
import com.r3.corda.networkmanage.common.persistence.PersistentNetworkMapStorage
import com.r3.corda.networkmanage.common.persistence.configureDatabase
import com.r3.corda.networkmanage.common.signer.NetworkMapSigner
import com.r3.corda.networkmanage.common.utils.ShowHelpException
import com.r3.corda.networkmanage.hsm.authentication.AuthMode
import com.r3.corda.networkmanage.hsm.authentication.Authenticator
@ -16,10 +19,19 @@ import com.r3.corda.networkmanage.hsm.persistence.DBSignedCertificateRequestStor
import com.r3.corda.networkmanage.hsm.signer.HsmCsrSigner
import com.r3.corda.networkmanage.hsm.signer.HsmNetworkMapSigner
import com.r3.corda.networkmanage.hsm.utils.mapCryptoServerException
import net.corda.core.utilities.minutes
import org.apache.logging.log4j.LogManager
import org.bouncycastle.jce.provider.BouncyCastleProvider
import java.security.Security
import java.time.Duration
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit.MILLISECONDS
import java.util.concurrent.TimeUnit.SECONDS
import javax.crypto.Cipher
private val log = LogManager.getLogger("com.r3.corda.networkmanage.hsm.Main")
fun main(args: Array<String>) {
// Grabbed from https://stackoverflow.com/questions/7953567/checking-if-unlimited-cryptography-is-available
if (Cipher.getMaxAllowedKeyLength("AES") < 256) {
@ -46,12 +58,15 @@ fun run(parameters: Parameters) {
checkNotNull(dataSourceProperties)
val database = configureDatabase(dataSourceProperties, databaseConfig)
val csrStorage = DBSignedCertificateRequestStorage(database)
val networkMapStorage = PersistentNetworkMapStorage(database)
val hsmNetworkMapSigningThread = HsmNetworkMapSigner(
networkMapStorage,
val hsmSigner = HsmNetworkMapSigner(
networkMapCertificateName,
networkMapPrivateKeyPassword,
Authenticator(createProvider(), AuthMode.KEY_FILE, autoUsername, authKeyFilePath, authKeyFilePassword, signAuthThreshold)).start()
Authenticator(createProvider(), AuthMode.KEY_FILE, autoUsername, authKeyFilePath, authKeyFilePassword, signAuthThreshold))
val networkMapStorage = PersistentNetworkMapStorage(database)
val scheduler = Executors.newSingleThreadScheduledExecutor()
startNetworkingMapSigningPolling(networkMapStorage, hsmSigner, scheduler, 10.minutes)
val sign: (List<ApprovedCertificateRequestData>) -> Unit = {
val signer = HsmCsrSigner(
csrStorage,
@ -62,6 +77,7 @@ fun run(parameters: Parameters) {
Authenticator(createProvider(), authMode, autoUsername, authKeyFilePath, authKeyFilePassword, signAuthThreshold))
signer.sign(it)
}
Menu().withExceptionHandler(::processError).addItem("1", "Generate root and intermediate certificates", {
if (confirmedKeyGen()) {
val generator = KeyCertificateGenerator(
@ -104,10 +120,25 @@ fun run(parameters: Parameters) {
println("There is no approved and unsigned CSR")
}
}).showMenu()
hsmNetworkMapSigningThread.stop()
MoreExecutors.shutdownAndAwaitTermination(scheduler, 30, SECONDS)
}
}
private fun startNetworkingMapSigningPolling(networkMapStorage: NetworkMapStorage,
signer: HsmNetworkMapSigner,
executor: ScheduledExecutorService,
signingPeriod: Duration) {
val networkMapSigner = NetworkMapSigner(networkMapStorage, signer)
executor.scheduleAtFixedRate({
try {
networkMapSigner.signNetworkMap()
} catch (e: Exception) {
log.warn("Exception thrown while signing network map", e)
}
}, signingPeriod.toMillis(), signingPeriod.toMillis(), MILLISECONDS)
}
private fun processError(exception: Exception) {
val processed = mapCryptoServerException(exception)
System.err.println("An error occurred: ${processed.message}")

View File

@ -1,78 +1,39 @@
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.signer.NetworkMapSigner
import com.r3.corda.networkmanage.common.signer.Signer
import com.r3.corda.networkmanage.hsm.authentication.Authenticator
import com.r3.corda.networkmanage.hsm.utils.X509Utilities
import com.r3.corda.networkmanage.hsm.utils.X509Utilities.getAndInitializeKeyStore
import com.r3.corda.networkmanage.hsm.utils.X509Utilities.verify
import net.corda.core.internal.DigitalSignatureWithCert
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.minutes
import net.corda.nodeapi.internal.crypto.getX509Certificate
import java.security.PrivateKey
import java.security.Signature
import java.security.cert.X509Certificate
import java.time.Duration
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.
* Signer which connects to a HSM using the given [authenticator] to sign bytes.
*/
// TODO Rename this to HsmSigner
class HsmNetworkMapSigner(networkMapStorage: NetworkMapStorage,
private val caCertificateKeyName: String,
private val caPrivateKeyPass: String,
private val authenticator: Authenticator,
private val signingPeriod: Duration = DEFAULT_SIGNING_PERIOD_MS) : Signer {
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 lateinit var scheduledExecutor: ScheduledExecutorService
// TODO This doesn't belong in this class
fun start(): HsmNetworkMapSigner {
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() {
MoreExecutors.shutdownAndAwaitTermination(scheduledExecutor, TERMINATION_TIMEOUT_SEC, TimeUnit.SECONDS)
}
class HsmNetworkMapSigner(private val certificateKeyName: String,
private val privateKeyPassword: String,
private val authenticator: Authenticator) : Signer {
/**
* Signs given data using [CryptoServerJCE.CryptoServerProvider], which connects to the underlying HSM.
*/
override fun signBytes(data: ByteArray): DigitalSignatureWithCert {
return authenticator.connectAndAuthenticate { provider, _ ->
val keyStore = getAndInitializeKeyStore(provider)
val caCertificateChain = keyStore.getCertificateChain(caCertificateKeyName)
val caKey = keyStore.getKey(caCertificateKeyName, caPrivateKeyPass.toCharArray()) as PrivateKey
val certificate = keyStore.getX509Certificate(certificateKeyName)
// Don't worry this is not a real private key but a pointer to one that resides in the HSM. It only works
// when used with the given provider.
val key = keyStore.getKey(certificateKeyName, privateKeyPassword.toCharArray()) as PrivateKey
val signature = Signature.getInstance(X509Utilities.SIGNATURE_ALGORITHM, provider).run {
initSign(caKey)
initSign(key)
update(data)
sign()
}
verify(data, signature, caCertificateChain[0].publicKey)
DigitalSignatureWithCert(caCertificateChain[0] as X509Certificate, signature)
verify(data, signature, certificate.publicKey)
DigitalSignatureWithCert(certificate, signature)
}
}
}

View File

@ -65,7 +65,7 @@ class PersistentNetworkMapStorageTest : TestBase() {
// then
val persistedSignedNetworkMap = networkMapStorage.getCurrentNetworkMap()
val persistedSignedParameters = networkMapStorage.getCurrentSignedNetworkParameters()
val persistedSignedParameters = networkMapStorage.getNetworkParametersOfNetworkMap()
assertEquals(networkParameters, persistedSignedParameters?.verifiedNetworkMapCert(rootCaCert))
assertEquals(parametersSignature, persistedSignedParameters?.sig)
@ -84,13 +84,13 @@ class PersistentNetworkMapStorageTest : TestBase() {
networkMapStorage.saveNetworkParameters(params2, null)
// when
val latest = networkMapStorage.getLatestUnsignedNetworkParameters()
val latest = networkMapStorage.getLatestNetworkParameters()?.minimumPlatformVersion
// then
assertEquals(2, latest.minimumPlatformVersion)
assertEquals(2, latest)
}
@Test
fun `getCurrentNetworkParameters returns current network map parameters`() {
fun `getNetworkParametersOfNetworkMap returns current network map parameters`() {
// given
// Create network parameters
val testParameters1 = testNetworkParameters(emptyList())
@ -107,7 +107,7 @@ class PersistentNetworkMapStorageTest : TestBase() {
networkMapStorage.saveNetworkParameters(testParameters2, testParameters2.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate).sig)
// when
val result = networkMapStorage.getCurrentSignedNetworkParameters()?.verifiedNetworkMapCert(rootCaCert)
val result = networkMapStorage.getNetworkParametersOfNetworkMap()?.verifiedNetworkMapCert(rootCaCert)
// then
assertEquals(1, result?.minimumPlatformVersion)

View File

@ -51,8 +51,8 @@ class NetworkMapSignerTest : TestBase() {
val signedNetworkMap = networkMap.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate)
whenever(networkMapStorage.getCurrentNetworkMap()).thenReturn(signedNetworkMap)
whenever(networkMapStorage.getNodeInfoHashes(any())).thenReturn(signedNodeInfoHashes)
whenever(networkMapStorage.getLatestUnsignedNetworkParameters()).thenReturn(latestNetworkParameters)
whenever(networkMapStorage.getCurrentSignedNetworkParameters()).thenReturn(currentParameters.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate))
whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(latestNetworkParameters)
whenever(networkMapStorage.getNetworkParametersOfNetworkMap()).thenReturn(currentParameters.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate))
whenever(signer.signBytes(any())).then {
DigitalSignatureWithCert(networkMapCa.certificate, Crypto.doSign(networkMapCa.keyPair.private, it.arguments[0] as ByteArray))
}
@ -67,8 +67,8 @@ class NetworkMapSignerTest : TestBase() {
// then
// Verify networkMapStorage calls
verify(networkMapStorage).getNodeInfoHashes(any())
verify(networkMapStorage).getLatestUnsignedNetworkParameters()
verify(networkMapStorage).getCurrentSignedNetworkParameters()
verify(networkMapStorage).getLatestNetworkParameters()
verify(networkMapStorage).getNetworkParametersOfNetworkMap()
argumentCaptor<SignedNetworkMap>().apply {
verify(networkMapStorage).saveNetworkMap(capture())
val capturedNetworkMap = firstValue.verifiedNetworkMapCert(rootCaCert)
@ -87,8 +87,8 @@ class NetworkMapSignerTest : TestBase() {
val signedNetworkMap = networkMap.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate)
whenever(networkMapStorage.getCurrentNetworkMap()).thenReturn(signedNetworkMap)
whenever(networkMapStorage.getNodeInfoHashes(any())).thenReturn(emptyList())
whenever(networkMapStorage.getLatestUnsignedNetworkParameters()).thenReturn(networkParameters)
whenever(networkMapStorage.getCurrentSignedNetworkParameters()).thenReturn(networkParameters.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate))
whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(networkParameters)
whenever(networkMapStorage.getNetworkParametersOfNetworkMap()).thenReturn(networkParameters.signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate))
// when
networkMapSigner.signNetworkMap()
@ -104,7 +104,7 @@ class NetworkMapSignerTest : TestBase() {
val networkParameters = testNetworkParameters(emptyList())
whenever(networkMapStorage.getCurrentNetworkMap()).thenReturn(null)
whenever(networkMapStorage.getNodeInfoHashes(any())).thenReturn(emptyList())
whenever(networkMapStorage.getLatestUnsignedNetworkParameters()).thenReturn(networkParameters)
whenever(networkMapStorage.getLatestNetworkParameters()).thenReturn(networkParameters)
whenever(signer.signBytes(any())).then {
DigitalSignatureWithCert(networkMapCa.certificate, Crypto.doSign(networkMapCa.keyPair.private, it.arguments[0] as ByteArray))
}
@ -118,7 +118,7 @@ class NetworkMapSignerTest : TestBase() {
// then
// Verify networkMapStorage calls
verify(networkMapStorage).getNodeInfoHashes(any())
verify(networkMapStorage).getLatestUnsignedNetworkParameters()
verify(networkMapStorage).getLatestNetworkParameters()
argumentCaptor<SignedNetworkMap>().apply {
verify(networkMapStorage).saveNetworkMap(capture())
val networkMap = firstValue.verifiedNetworkMapCert(rootCaCert)

View File

@ -1,62 +0,0 @@
package com.r3.corda.networkmanage.doorman
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.CertificateResponse
import com.r3.corda.networkmanage.common.persistence.CertificateStatus
import com.r3.corda.networkmanage.common.persistence.CertificationRequestStorage
import com.r3.corda.networkmanage.common.persistence.RequestStatus
import com.r3.corda.networkmanage.common.utils.buildCertPath
import com.r3.corda.networkmanage.doorman.signer.DefaultCsrHandler
import com.r3.corda.networkmanage.doorman.signer.LocalSigner
import net.corda.core.crypto.Crypto
import net.corda.nodeapi.internal.crypto.X509Utilities
import org.junit.Test
import javax.security.auth.x500.X500Principal
import kotlin.test.assertEquals
class DefaultRequestProcessorTest : TestBase() {
@Test
fun `get response`() {
val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val cert = X509Utilities.createSelfSignedCACertificate(X500Principal("O=Test,L=London,C=GB"), keyPair)
val requestStorage: CertificationRequestStorage = mock {
on { getRequest("New") }.thenReturn(certificateSigningRequest())
on { getRequest("Signed") }.thenReturn(certificateSigningRequest(status = RequestStatus.SIGNED, certData = certificateData("", CertificateStatus.VALID, buildCertPath(cert))))
on { getRequest("Rejected") }.thenReturn(certificateSigningRequest(status = RequestStatus.REJECTED, remark = "Random reason"))
}
val signer: LocalSigner = mock()
val requestProcessor = DefaultCsrHandler(requestStorage, signer)
assertEquals(CertificateResponse.NotReady, requestProcessor.getResponse("random"))
assertEquals(CertificateResponse.NotReady, requestProcessor.getResponse("New"))
assertEquals(CertificateResponse.Ready(buildCertPath(cert)), requestProcessor.getResponse("Signed"))
assertEquals(CertificateResponse.Unauthorised("Random reason"), requestProcessor.getResponse("Rejected"))
}
@Test
fun `process request`() {
val (request1, request2, request3) = (1..3).map {
X509Utilities.createCertificateSigningRequest(X500Principal("O=Test1,L=London,C=GB"), "my@email.com", Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME))
}
val requestStorage: CertificationRequestStorage = mock {
on { getRequests(RequestStatus.APPROVED) }.thenReturn(listOf(
certificateSigningRequest(requestId = "1", request = request1, status = RequestStatus.APPROVED),
certificateSigningRequest(requestId = "2", request = request2, status = RequestStatus.APPROVED),
certificateSigningRequest(requestId = "3", request = request3, status = RequestStatus.APPROVED)
))
}
val signer: LocalSigner = mock()
val requestProcessor = DefaultCsrHandler(requestStorage, signer)
requestProcessor.processApprovedRequests()
verify(signer, times(3)).createSignedClientCertificate(any())
verify(requestStorage, times(1)).getRequests(any())
}
}

View File

@ -0,0 +1,85 @@
package com.r3.corda.networkmanage.doorman.signer
import com.nhaarman.mockito_kotlin.*
import com.r3.corda.networkmanage.TestBase
import com.r3.corda.networkmanage.common.persistence.CertificateResponse
import com.r3.corda.networkmanage.common.persistence.CertificateStatus
import com.r3.corda.networkmanage.common.persistence.CertificationRequestStorage
import com.r3.corda.networkmanage.common.persistence.CertificationRequestStorage.Companion.DOORMAN_SIGNATURE
import com.r3.corda.networkmanage.common.persistence.RequestStatus
import com.r3.corda.networkmanage.common.utils.CertPathAndKey
import com.r3.corda.networkmanage.common.utils.buildCertPath
import net.corda.core.crypto.Crypto
import net.corda.core.internal.CertRole
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.testing.internal.createDevIntermediateCaCertPath
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import java.security.cert.CertPath
import java.security.cert.X509Certificate
import javax.security.auth.x500.X500Principal
import kotlin.test.assertEquals
class DefaultCsrHandlerTest : TestBase() {
@Test
fun getResponse() {
val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val cert = X509Utilities.createSelfSignedCACertificate(X500Principal("O=Test,L=London,C=GB"), keyPair)
val requestStorage: CertificationRequestStorage = mock {
on { getRequest("New") }.thenReturn(certificateSigningRequest())
on { getRequest("Signed") }.thenReturn(certificateSigningRequest(status = RequestStatus.SIGNED, certData = certificateData("", CertificateStatus.VALID, buildCertPath(cert))))
on { getRequest("Rejected") }.thenReturn(certificateSigningRequest(status = RequestStatus.REJECTED, remark = "Random reason"))
}
val requestProcessor = DefaultCsrHandler(requestStorage, null)
assertEquals(CertificateResponse.NotReady, requestProcessor.getResponse("random"))
assertEquals(CertificateResponse.NotReady, requestProcessor.getResponse("New"))
assertEquals(CertificateResponse.Ready(buildCertPath(cert)), requestProcessor.getResponse("Signed"))
assertEquals(CertificateResponse.Unauthorised("Random reason"), requestProcessor.getResponse("Rejected"))
}
@Test
fun processApprovedRequests() {
val requests = (1..3).map {
X509Utilities.createCertificateSigningRequest(
X500Principal("O=Test$it,L=London,C=GB"),
"my@email.com",
Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME))
}
val requestStorage: CertificationRequestStorage = mock {
on { getRequests(RequestStatus.APPROVED) }.thenReturn(listOf(
certificateSigningRequest(requestId = "1", request = requests[0], status = RequestStatus.APPROVED),
certificateSigningRequest(requestId = "2", request = requests[1], status = RequestStatus.APPROVED)
))
on { getRequests(RequestStatus.REJECTED) }.thenReturn(listOf(
certificateSigningRequest(requestId = "3", request = requests[2], status = RequestStatus.REJECTED)
))
}
val (rootCa, csrCa) = createDevIntermediateCaCertPath()
val csrCertPathAndKey = CertPathAndKey(listOf(csrCa.certificate, rootCa.certificate), csrCa.keyPair.private)
val requestProcessor = DefaultCsrHandler(requestStorage, csrCertPathAndKey)
requestProcessor.processApprovedRequests()
val certPathCapture = argumentCaptor<CertPath>()
// Verify only the approved requests are taken
verify(requestStorage, times(1)).getRequests(RequestStatus.APPROVED)
verify(requestStorage, times(1)).putCertificatePath(eq("1"), certPathCapture.capture(), eq(listOf(DOORMAN_SIGNATURE)))
verify(requestStorage, times(1)).putCertificatePath(eq("2"), certPathCapture.capture(), eq(listOf(DOORMAN_SIGNATURE)))
// Then make sure the generated node cert paths are correct
certPathCapture.allValues.forEachIndexed { index, certPath ->
X509Utilities.validateCertificateChain(rootCa.certificate, *certPath.certificates.toTypedArray())
assertThat(certPath.certificates).hasSize(3).element(1).isEqualTo(csrCa.certificate)
(certPath.certificates[0] as X509Certificate).apply {
assertThat(CertRole.extract(this)).isEqualTo(CertRole.NODE_CA)
assertThat(publicKey).isEqualTo(Crypto.toSupportedPublicKey(requests[index].subjectPublicKeyInfo))
assertThat(subjectX500Principal).isEqualTo(X500Principal("O=Test${index + 1},L=London,C=GB"))
}
}
}
}

View File

@ -14,25 +14,26 @@ import org.junit.Rule
import org.junit.Test
import org.mockito.Mock
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
import java.security.cert.CertPath
class JiraCsrHandlerTest {
@Rule
@JvmField
val mockitoRule = MockitoJUnit.rule()
val mockitoRule: MockitoRule = MockitoJUnit.rule()
@Mock
lateinit var jiraClient: JiraClient
private lateinit var jiraClient: JiraClient
@Mock
lateinit var certificationRequestStorage: CertificationRequestStorage
private lateinit var certificationRequestStorage: CertificationRequestStorage
@Mock
lateinit var defaultCsrHandler: DefaultCsrHandler
private lateinit var defaultCsrHandler: DefaultCsrHandler
@Mock
var certPath: CertPath = mock()
private val certPath: CertPath = mock()
private lateinit var jiraCsrHandler: JiraCsrHandler
private val requestId = "id"

View File

@ -1,4 +1,4 @@
package com.r3.corda.networkmanage.doorman
package com.r3.corda.networkmanage.doorman.webservice
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.times
@ -7,7 +7,8 @@ import com.r3.corda.networkmanage.common.persistence.NetworkMapStorage
import com.r3.corda.networkmanage.common.persistence.NodeInfoStorage
import com.r3.corda.networkmanage.common.utils.SignedNetworkMap
import com.r3.corda.networkmanage.common.utils.SignedNetworkParameters
import com.r3.corda.networkmanage.doorman.webservice.NodeInfoWebService
import com.r3.corda.networkmanage.doorman.NetworkManagementWebServer
import com.r3.corda.networkmanage.doorman.NetworkMapConfig
import net.corda.core.crypto.SecureHash.Companion.randomSHA256
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.checkOkResponse
@ -36,7 +37,7 @@ import java.security.cert.X509Certificate
import javax.ws.rs.core.MediaType
import kotlin.test.assertEquals
class NodeInfoWebServiceTest {
class NetworkMapWebServiceTest {
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule(true)
@ -56,12 +57,12 @@ class NodeInfoWebServiceTest {
@Test
fun `submit nodeInfo`() {
val networkMapStorage: NetworkMapStorage = mock {
on { getCurrentSignedNetworkParameters() }.thenReturn(testNetworkParameters(emptyList()).signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate))
on { getNetworkParametersOfNetworkMap() }.thenReturn(testNetworkParameters(emptyList()).signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate))
}
// Create node info.
val (_, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name("Test", "London", "GB"))
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(mock(), networkMapStorage, testNetworkMapConfig)).use {
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NetworkMapWebService(mock(), networkMapStorage, testNetworkMapConfig)).use {
it.start()
val nodeInfoAndSignature = signedNodeInfo.serialize().bytes
// Post node info and signature to doorman, this should pass without any exception.
@ -72,12 +73,12 @@ class NodeInfoWebServiceTest {
@Test
fun `submit old nodeInfo`() {
val networkMapStorage: NetworkMapStorage = mock {
on { getCurrentSignedNetworkParameters() }.thenReturn(testNetworkParameters(emptyList(), minimumPlatformVersion = 2).signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate))
on { getNetworkParametersOfNetworkMap() }.thenReturn(testNetworkParameters(emptyList(), minimumPlatformVersion = 2).signWithCert(networkMapCa.keyPair.private, networkMapCa.certificate))
}
// Create node info.
val (_, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name("Test", "London", "GB"), platformVersion = 1)
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(mock(), networkMapStorage, testNetworkMapConfig)).use {
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NetworkMapWebService(mock(), networkMapStorage, testNetworkMapConfig)).use {
it.start()
val nodeInfoAndSignature = signedNodeInfo.serialize().bytes
assertThatThrownBy { it.doPost("publish", nodeInfoAndSignature) }
@ -88,12 +89,12 @@ class NodeInfoWebServiceTest {
@Test
fun `submit nodeInfo when no network parameters`() {
val networkMapStorage: NetworkMapStorage = mock {
on { getCurrentSignedNetworkParameters() }.thenReturn(null)
on { getNetworkParametersOfNetworkMap() }.thenReturn(null)
}
// Create node info.
val (_, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name("Test", "London", "GB"), platformVersion = 1)
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(mock(), networkMapStorage, testNetworkMapConfig)).use {
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NetworkMapWebService(mock(), networkMapStorage, testNetworkMapConfig)).use {
it.start()
val nodeInfoAndSignature = signedNodeInfo.serialize().bytes
assertThatThrownBy { it.doPost("publish", nodeInfoAndSignature) }
@ -110,7 +111,7 @@ class NodeInfoWebServiceTest {
on { getCurrentNetworkMap() }.thenReturn(signedNetworkMap)
}
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(mock(), networkMapStorage, testNetworkMapConfig)).use {
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NetworkMapWebService(mock(), networkMapStorage, testNetworkMapConfig)).use {
it.start()
val signedNetworkMapResponse = it.doGet<SignedNetworkMap>("")
verify(networkMapStorage, times(1)).getCurrentNetworkMap()
@ -127,7 +128,7 @@ class NodeInfoWebServiceTest {
on { getNodeInfo(nodeInfoHash) }.thenReturn(signedNodeInfo)
}
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(nodeInfoStorage, mock(), testNetworkMapConfig)).use {
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NetworkMapWebService(nodeInfoStorage, mock(), testNetworkMapConfig)).use {
it.start()
val nodeInfoResponse = it.doGet<SignedNodeInfo>("node-info/$nodeInfoHash")
verify(nodeInfoStorage, times(1)).getNodeInfo(nodeInfoHash)
@ -149,7 +150,7 @@ class NodeInfoWebServiceTest {
on { getSignedNetworkParameters(networkParametersHash) }.thenReturn(signedNetworkParameters)
}
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NodeInfoWebService(mock(), networkMapStorage, testNetworkMapConfig)).use {
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NetworkMapWebService(mock(), networkMapStorage, testNetworkMapConfig)).use {
it.start()
val netParamsResponse = it.doGet<SignedNetworkParameters>("network-parameters/$networkParametersHash")
verify(networkMapStorage, times(1)).getSignedNetworkParameters(networkParametersHash)

View File

@ -1,11 +1,11 @@
package com.r3.corda.networkmanage.doorman
package com.r3.corda.networkmanage.doorman.webservice
import com.nhaarman.mockito_kotlin.*
import com.r3.corda.networkmanage.TestBase
import com.r3.corda.networkmanage.common.persistence.CertificateResponse
import com.r3.corda.networkmanage.common.utils.buildCertPath
import com.r3.corda.networkmanage.doorman.NetworkManagementWebServer
import com.r3.corda.networkmanage.doorman.signer.CsrHandler
import com.r3.corda.networkmanage.doorman.webservice.RegistrationWebService
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name