mirror of
https://github.com/corda/corda.git
synced 2024-12-27 08:22:35 +00:00
ENT-1626 Validating cert path on node info submission (#650)
* Validating cert path on node info submission * Addressing review comments * Refactoring user exceptions * Changing response message.
This commit is contained in:
parent
2543bb356e
commit
84398362ab
@ -141,12 +141,11 @@ class NetworkParametersUpdateTest : IntegrationTest() {
|
||||
}
|
||||
|
||||
private fun startServer(startNetworkMap: Boolean = true): NetworkManagementServer {
|
||||
val server = NetworkManagementServer(makeTestDataSourceProperties(dbName), DatabaseConfig(runMigration = true))
|
||||
val doormanConfig = DoormanConfig(approveAll = true, jira = null, approveInterval = timeoutMillis)
|
||||
val server = NetworkManagementServer(makeTestDataSourceProperties(dbName), DatabaseConfig(runMigration = true), doormanConfig, null)
|
||||
server.start(
|
||||
serverAddress,
|
||||
CertPathAndKey(listOf(doormanCa.certificate, rootCaCert), doormanCa.keyPair.private),
|
||||
DoormanConfig(approveAll = true, jira = null, approveInterval = timeoutMillis),
|
||||
null,
|
||||
if (startNetworkMap) {
|
||||
NetworkMapStartParams(
|
||||
LocalSigner(networkMapCa),
|
||||
@ -161,7 +160,11 @@ class NetworkParametersUpdateTest : IntegrationTest() {
|
||||
|
||||
private fun applyNetworkParametersAndStart(networkParametersCmd: NetworkParametersCmd) {
|
||||
server?.close()
|
||||
NetworkManagementServer(makeTestDataSourceProperties(dbName), DatabaseConfig(runMigration = true)).use {
|
||||
NetworkManagementServer(
|
||||
makeTestDataSourceProperties(dbName),
|
||||
DatabaseConfig(runMigration = true),
|
||||
DoormanConfig(approveAll = true, jira = null, approveInterval = timeoutMillis),
|
||||
null).use {
|
||||
it.processNetworkParameters(networkParametersCmd)
|
||||
}
|
||||
server = startServer(startNetworkMap = true)
|
||||
|
@ -73,6 +73,18 @@ class NodeRegistrationTest : IntegrationTest() {
|
||||
|
||||
private var server: NetworkManagementServer? = null
|
||||
|
||||
private val doormanConfig: DoormanConfig get() = DoormanConfig(approveAll = true, jira = null, approveInterval = timeoutMillis)
|
||||
private val revocationConfig: CertificateRevocationConfig
|
||||
get() = CertificateRevocationConfig(
|
||||
approveAll = true,
|
||||
jira = null,
|
||||
approveInterval = timeoutMillis,
|
||||
crlCacheTimeout = timeoutMillis,
|
||||
localSigning = CertificateRevocationConfig.LocalSigning(
|
||||
crlEndpoint = URL("http://test.com/crl"),
|
||||
crlUpdateInterval = timeoutMillis)
|
||||
)
|
||||
|
||||
@Before
|
||||
fun init() {
|
||||
dbName = random63BitValue().toString()
|
||||
@ -108,7 +120,6 @@ class NodeRegistrationTest : IntegrationTest() {
|
||||
},
|
||||
rootCert = rootCaCert
|
||||
)
|
||||
|
||||
internalDriver(
|
||||
portAllocation = portAllocation,
|
||||
compatibilityZone = compatibilityZone,
|
||||
@ -157,20 +168,10 @@ class NodeRegistrationTest : IntegrationTest() {
|
||||
}
|
||||
|
||||
private fun startServer(startNetworkMap: Boolean = true): NetworkManagementServer {
|
||||
val server = NetworkManagementServer(makeTestDataSourceProperties(dbName), DatabaseConfig(runMigration = true))
|
||||
val server = NetworkManagementServer(makeTestDataSourceProperties(dbName), DatabaseConfig(runMigration = true), doormanConfig, revocationConfig)
|
||||
server.start(
|
||||
serverAddress,
|
||||
CertPathAndKey(listOf(doormanCa.certificate, rootCaCert), doormanCa.keyPair.private),
|
||||
DoormanConfig(approveAll = true, jira = null, approveInterval = timeoutMillis),
|
||||
CertificateRevocationConfig(
|
||||
approveAll = true,
|
||||
jira = null,
|
||||
approveInterval = timeoutMillis,
|
||||
crlCacheTimeout = timeoutMillis,
|
||||
localSigning = CertificateRevocationConfig.LocalSigning(
|
||||
crlEndpoint = URL("http://test.com/crl"),
|
||||
crlUpdateInterval = timeoutMillis)
|
||||
),
|
||||
if (startNetworkMap) {
|
||||
NetworkMapStartParams(
|
||||
LocalSigner(networkMapCa),
|
||||
@ -185,7 +186,7 @@ class NodeRegistrationTest : IntegrationTest() {
|
||||
|
||||
private fun applyNetworkParametersAndStart(networkParametersCmd: NetworkParametersCmd) {
|
||||
server?.close()
|
||||
NetworkManagementServer(makeTestDataSourceProperties(dbName), DatabaseConfig(runMigration = true)).use {
|
||||
NetworkManagementServer(makeTestDataSourceProperties(dbName), DatabaseConfig(runMigration = true), doormanConfig, revocationConfig).use {
|
||||
it.processNetworkParameters(networkParametersCmd)
|
||||
}
|
||||
server = startServer(startNetworkMap = true)
|
||||
|
@ -66,6 +66,20 @@ class SigningServiceIntegrationTest : HsmBaseTest() {
|
||||
|
||||
private lateinit var dbName: String
|
||||
|
||||
private val doormanConfig: DoormanConfig get() = DoormanConfig(approveAll = true, approveInterval = 2.seconds.toMillis(), jira = null)
|
||||
private val revocationConfig: CertificateRevocationConfig
|
||||
get() = CertificateRevocationConfig(
|
||||
approveAll = true,
|
||||
jira = null,
|
||||
crlCacheTimeout = 30.minutes.toMillis(),
|
||||
approveInterval = 10.minutes.toMillis(),
|
||||
localSigning = CertificateRevocationConfig.LocalSigning(
|
||||
crlEndpoint = URL("http://test.com/crl"),
|
||||
crlUpdateInterval = 2.hours.toMillis()
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
dbName = random63BitValue().toString()
|
||||
@ -99,21 +113,10 @@ class SigningServiceIntegrationTest : HsmBaseTest() {
|
||||
@Test
|
||||
fun `Signing service signs approved CSRs`() {
|
||||
//Start doorman server
|
||||
NetworkManagementServer(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true)).use { server ->
|
||||
NetworkManagementServer(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), doormanConfig, revocationConfig).use { server ->
|
||||
server.start(
|
||||
hostAndPort = NetworkHostAndPort(HOST, 0),
|
||||
csrCertPathAndKey = null,
|
||||
doormanConfig = DoormanConfig(approveAll = true, approveInterval = 2.seconds.toMillis(), jira = null),
|
||||
revocationConfig = CertificateRevocationConfig(
|
||||
approveAll = true,
|
||||
jira = null,
|
||||
crlCacheTimeout = 30.minutes.toMillis(),
|
||||
approveInterval = 10.minutes.toMillis(),
|
||||
localSigning = CertificateRevocationConfig.LocalSigning(
|
||||
crlEndpoint = URL("http://test.com/crl"),
|
||||
crlUpdateInterval = 2.hours.toMillis()
|
||||
)
|
||||
),
|
||||
startNetworkMap = null)
|
||||
val doormanHostAndPort = server.hostAndPort
|
||||
// Start Corda network registration.
|
||||
|
@ -14,6 +14,7 @@ import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest
|
||||
import java.security.PublicKey
|
||||
import java.security.cert.CertPath
|
||||
|
||||
data class CertificateData(val certStatus: CertificateStatus, val certPath: CertPath)
|
||||
@ -83,6 +84,14 @@ interface CertificateSigningRequestStorage {
|
||||
* @throws IllegalArgumentException if request is not found or not in Approved state.
|
||||
*/
|
||||
fun putCertificatePath(requestId: String, certPath: CertPath, signedBy: String)
|
||||
|
||||
/**
|
||||
* Retrieves the certificate path for the given public key hash if such exists and its certificate is considered to be valid.
|
||||
*
|
||||
* @param publicKey public key corresponding to the certificate being searched.
|
||||
* @return certificate path for the given public key hash or null if such certificate does not exist or is not valid.
|
||||
*/
|
||||
fun getValidCertificatePath(publicKey: PublicKey): CertPath?
|
||||
}
|
||||
|
||||
sealed class CertificateResponse {
|
||||
|
@ -23,6 +23,7 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.persistence.DatabaseTransaction
|
||||
import net.corda.nodeapi.internal.persistence.TransactionIsolationLevel
|
||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest
|
||||
import java.security.PublicKey
|
||||
import java.security.cert.CertPath
|
||||
import java.time.Instant
|
||||
import javax.security.auth.x500.X500Principal
|
||||
@ -66,15 +67,14 @@ class PersistentCertificateSigningRequestStorage(private val database: CordaPers
|
||||
} catch (e: RequestValidationException) {
|
||||
e.rejectMessage
|
||||
}
|
||||
|
||||
val requestEntity = CertificateSigningRequestEntity(
|
||||
requestId = requestId,
|
||||
legalName = legalNameOrRejectMessage as? CordaX500Name,
|
||||
publicKeyHash = toSupportedPublicKey(request.subjectPublicKeyInfo).hashString(),
|
||||
request = request,
|
||||
remark = legalNameOrRejectMessage as? String,
|
||||
modifiedBy = CertificateSigningRequestStorage.DOORMAN_SIGNATURE,
|
||||
status = if (legalNameOrRejectMessage is CordaX500Name) RequestStatus.NEW else RequestStatus.REJECTED
|
||||
requestId = requestId,
|
||||
legalName = legalNameOrRejectMessage as? CordaX500Name,
|
||||
publicKeyHash = toSupportedPublicKey(request.subjectPublicKeyInfo).hashString(),
|
||||
request = request,
|
||||
remark = legalNameOrRejectMessage as? String,
|
||||
modifiedBy = CertificateSigningRequestStorage.DOORMAN_SIGNATURE,
|
||||
status = if (legalNameOrRejectMessage is CordaX500Name) RequestStatus.NEW else RequestStatus.REJECTED
|
||||
)
|
||||
session.save(requestEntity)
|
||||
}
|
||||
@ -131,6 +131,16 @@ class PersistentCertificateSigningRequestStorage(private val database: CordaPers
|
||||
}
|
||||
}
|
||||
|
||||
override fun getValidCertificatePath(publicKey: PublicKey): CertPath? {
|
||||
return database.transaction {
|
||||
session.createQuery(
|
||||
"select a.certificateData.certPath from ${CertificateSigningRequestEntity::class.java.name} a " +
|
||||
"where a.publicKeyHash = :publicKeyHash and a.status = 'DONE' and a.certificateData.certificateStatus = 'VALID'", CertPath::class.java)
|
||||
.setParameter("publicKeyHash", publicKey.hashString())
|
||||
.uniqueResult()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getRequest(requestId: String): CertificateSigningRequest? {
|
||||
return database.transaction {
|
||||
findRequest(requestId)?.toCertificateSigningRequest()
|
||||
|
@ -87,7 +87,7 @@ private fun caKeyGenMode(config: NetworkManagementServerConfig) {
|
||||
}
|
||||
|
||||
private fun doormanMode(cmdLineOptions: DoormanCmdLineOptions, config: NetworkManagementServerConfig) {
|
||||
val networkManagementServer = NetworkManagementServer(config.dataSourceProperties, config.database)
|
||||
val networkManagementServer = NetworkManagementServer(config.dataSourceProperties, config.database, config.doorman, config.revocation)
|
||||
|
||||
if (cmdLineOptions.networkParametersCmd == null) {
|
||||
// TODO: move signing to signing server.
|
||||
@ -103,8 +103,6 @@ private fun doormanMode(cmdLineOptions: DoormanCmdLineOptions, config: NetworkMa
|
||||
networkManagementServer.start(
|
||||
config.address,
|
||||
csrAndNetworkMap?.first,
|
||||
config.doorman,
|
||||
config.revocation,
|
||||
networkMapStartParams)
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(object : Thread("ShutdownHook") {
|
||||
|
@ -21,7 +21,6 @@ import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||
import java.io.Closeable
|
||||
import java.net.URI
|
||||
@ -31,7 +30,10 @@ import java.util.*
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class NetworkManagementServer(dataSourceProperties: Properties, databaseConfig: DatabaseConfig) : Closeable {
|
||||
class NetworkManagementServer(dataSourceProperties: Properties,
|
||||
databaseConfig: DatabaseConfig,
|
||||
private val doormanConfig: DoormanConfig?, // TODO Doorman config shouldn't be optional as the doorman is always required to run
|
||||
private val revocationConfig: CertificateRevocationConfig?) : Closeable {
|
||||
companion object {
|
||||
private val logger = contextLogger()
|
||||
}
|
||||
@ -40,7 +42,21 @@ class NetworkManagementServer(dataSourceProperties: Properties, databaseConfig:
|
||||
private val database = configureDatabase(dataSourceProperties, databaseConfig).also { closeActions += it::close }
|
||||
private val networkMapStorage = PersistentNetworkMapStorage(database)
|
||||
private val nodeInfoStorage = PersistentNodeInfoStorage(database)
|
||||
|
||||
private val crlStorage = PersistentCertificateRevocationListStorage(database)
|
||||
private val csrStorage = doormanConfig?.let {
|
||||
if (it.approveAll) {
|
||||
ApproveAllCertificateSigningRequestStorage(PersistentCertificateSigningRequestStorage(database))
|
||||
} else {
|
||||
PersistentCertificateSigningRequestStorage(database)
|
||||
}
|
||||
}
|
||||
private val crrStorage = revocationConfig?.let {
|
||||
if (it.approveAll) {
|
||||
ApproveAllCertificateRevocationRequestStorage(PersistentCertificateRevocationRequestStorage(database))
|
||||
} else {
|
||||
PersistentCertificateRevocationRequestStorage(database)
|
||||
}
|
||||
}
|
||||
lateinit var hostAndPort: NetworkHostAndPort
|
||||
|
||||
override fun close() {
|
||||
@ -55,6 +71,8 @@ class NetworkManagementServer(dataSourceProperties: Properties, databaseConfig:
|
||||
}
|
||||
|
||||
private fun getNetworkMapService(config: NetworkMapConfig, signer: LocalSigner?): NetworkMapWebService {
|
||||
logger.info("Starting Network Map server.")
|
||||
csrStorage ?: throw IllegalStateException("Certificate signing request storage cannot be null when creating the network map service.")
|
||||
val localNetworkMapSigner = signer?.let { NetworkMapSigner(networkMapStorage, it) }
|
||||
val latestParameters = networkMapStorage.getLatestNetworkParameters()?.networkParameters ?:
|
||||
throw IllegalStateException("No network parameters were found. Please upload new network parameters before starting network map service")
|
||||
@ -75,28 +93,21 @@ class NetworkManagementServer(dataSourceProperties: Properties, databaseConfig:
|
||||
closeActions += scheduledExecutor::shutdown
|
||||
}
|
||||
|
||||
return NetworkMapWebService(nodeInfoStorage, networkMapStorage, config)
|
||||
return NetworkMapWebService(nodeInfoStorage, networkMapStorage, csrStorage, 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.")
|
||||
ApproveAllCertificateSigningRequestStorage(PersistentCertificateSigningRequestStorage(database))
|
||||
} else {
|
||||
PersistentCertificateSigningRequestStorage(database)
|
||||
}
|
||||
|
||||
csrStorage ?: throw IllegalStateException("Certificate signing request storage cannot be null when creating the doorman service.")
|
||||
val jiraConfig = config.jira
|
||||
val requestProcessor = if (jiraConfig != null) {
|
||||
val jiraWebAPI = AsynchronousJiraRestClientFactory().createWithBasicHttpAuthentication(URI(jiraConfig.address), jiraConfig.username, jiraConfig.password)
|
||||
val jiraClient = CsrJiraClient(jiraWebAPI, jiraConfig.projectCode)
|
||||
JiraCsrHandler(jiraClient, requestService, DefaultCsrHandler(requestService, csrCertPathAndKey))
|
||||
JiraCsrHandler(jiraClient, csrStorage, DefaultCsrHandler(csrStorage, csrCertPathAndKey))
|
||||
} else {
|
||||
DefaultCsrHandler(requestService, csrCertPathAndKey)
|
||||
DefaultCsrHandler(csrStorage, csrCertPathAndKey)
|
||||
}
|
||||
|
||||
val scheduledExecutor = Executors.newScheduledThreadPool(1)
|
||||
@ -117,17 +128,9 @@ class NetworkManagementServer(dataSourceProperties: Properties, databaseConfig:
|
||||
}
|
||||
|
||||
private fun getRevocationServices(config: CertificateRevocationConfig,
|
||||
database: CordaPersistence,
|
||||
csrCertPathAndKeyPair: CertPathAndKey?): Pair<CertificateRevocationRequestWebService, CertificateRevocationListWebService> {
|
||||
logger.info("Starting Revocation server.")
|
||||
val crrStorage = if (config.approveAll) {
|
||||
logger.warn("Revocation server is in 'Approve All' mode, this will approve all incoming certificate signing requests.")
|
||||
ApproveAllCertificateRevocationRequestStorage(PersistentCertificateRevocationRequestStorage(database))
|
||||
} else {
|
||||
PersistentCertificateRevocationRequestStorage(database)
|
||||
}
|
||||
val crlStorage = PersistentCertificateRevocationListStorage(database)
|
||||
|
||||
crrStorage ?: throw IllegalStateException("Certificate revocation request storage cannot be null when creating the revocation service.")
|
||||
val crlHandler = csrCertPathAndKeyPair?.let {
|
||||
LocalCrlHandler(crrStorage,
|
||||
crlStorage,
|
||||
@ -163,17 +166,15 @@ class NetworkManagementServer(dataSourceProperties: Properties, databaseConfig:
|
||||
|
||||
fun start(hostAndPort: NetworkHostAndPort,
|
||||
csrCertPathAndKey: CertPathAndKey?,
|
||||
doormanConfig: DoormanConfig?, // TODO Doorman config shouldn't be optional as the doorman is always required to run
|
||||
revocationConfig: CertificateRevocationConfig?,
|
||||
startNetworkMap: NetworkMapStartParams?
|
||||
) {
|
||||
val services = mutableListOf<Any>()
|
||||
val serverStatus = NetworkManagementServerStatus()
|
||||
|
||||
startNetworkMap?.let { services += getNetworkMapService(it.config, it.signer) }
|
||||
doormanConfig?.let { services += getDoormanService(it, database, csrCertPathAndKey, serverStatus) }
|
||||
doormanConfig?.let { services += getDoormanService(it, csrCertPathAndKey, serverStatus) }
|
||||
revocationConfig?.let {
|
||||
val revocationServices = getRevocationServices(it, database, csrCertPathAndKey)
|
||||
val revocationServices = getRevocationServices(it, csrCertPathAndKey)
|
||||
services += revocationServices.first
|
||||
services += revocationServices.second
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ package com.r3.corda.networkmanage.doorman.webservice
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Caffeine
|
||||
import com.github.benmanes.caffeine.cache.LoadingCache
|
||||
import com.r3.corda.networkmanage.common.persistence.CertificateSigningRequestStorage
|
||||
import com.r3.corda.networkmanage.common.persistence.NetworkMapStorage
|
||||
import com.r3.corda.networkmanage.common.persistence.NodeInfoStorage
|
||||
import com.r3.corda.networkmanage.doorman.NetworkMapConfig
|
||||
@ -20,6 +21,7 @@ import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.SignedData
|
||||
import net.corda.core.crypto.sha256
|
||||
import net.corda.core.internal.CertRole
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.serialization.deserialize
|
||||
@ -29,10 +31,14 @@ import net.corda.core.utilities.debug
|
||||
import net.corda.core.utilities.trace
|
||||
import net.corda.nodeapi.internal.NodeInfoAndSigned
|
||||
import net.corda.nodeapi.internal.SignedNodeInfo
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities.validateCertPath
|
||||
import net.corda.nodeapi.internal.crypto.x509
|
||||
import net.corda.nodeapi.internal.crypto.x509Certificates
|
||||
import net.corda.nodeapi.internal.network.SignedNetworkMap
|
||||
import java.io.InputStream
|
||||
import java.security.InvalidKeyException
|
||||
import java.security.SignatureException
|
||||
import java.security.cert.CertPathValidatorException
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
@ -46,6 +52,7 @@ import javax.ws.rs.core.Response.status
|
||||
@Path(NETWORK_MAP_PATH)
|
||||
class NetworkMapWebService(private val nodeInfoStorage: NodeInfoStorage,
|
||||
private val networkMapStorage: NetworkMapStorage,
|
||||
private val certificateSigningRequestStorage: CertificateSigningRequestStorage,
|
||||
private val config: NetworkMapConfig) {
|
||||
|
||||
companion object {
|
||||
@ -91,7 +98,7 @@ class NetworkMapWebService(private val nodeInfoStorage: NodeInfoStorage,
|
||||
logger.warn("Unable to process node-info: $nodeInfo", e)
|
||||
when (e) {
|
||||
is NetworkMapNotInitialisedException -> status(Response.Status.SERVICE_UNAVAILABLE).entity(e.message)
|
||||
is InvalidPlatformVersionException -> status(Response.Status.BAD_REQUEST).entity(e.message)
|
||||
is RequestException -> status(Response.Status.BAD_REQUEST).entity(e.message)
|
||||
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.
|
||||
else -> throw e
|
||||
@ -154,11 +161,12 @@ class NetworkMapWebService(private val nodeInfoStorage: NodeInfoStorage,
|
||||
}
|
||||
|
||||
private fun verifyNodeInfo(nodeInfo: NodeInfo) {
|
||||
checkCertificates(nodeInfo)
|
||||
checkCompositeKeys(nodeInfo)
|
||||
val minimumPlatformVersion = currentNetworkParameters?.minimumPlatformVersion
|
||||
?: throw NetworkMapNotInitialisedException("Network parameters have not been initialised")
|
||||
if (nodeInfo.platformVersion < minimumPlatformVersion) {
|
||||
throw InvalidPlatformVersionException("Minimum platform version is $minimumPlatformVersion")
|
||||
throw RequestException("Minimum platform version is $minimumPlatformVersion")
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,7 +177,9 @@ class NetworkMapWebService(private val nodeInfoStorage: NodeInfoStorage,
|
||||
}
|
||||
val parameters = checkNotNull(currentNetworkParameters) { "Network parameters not available." }
|
||||
val notaryIdentities = parameters.notaries.map { it.identity }
|
||||
require(notaryIdentities.containsAll(compositeKeyIdentities)) { "A composite key needs to belong to a notary." }
|
||||
if (!notaryIdentities.containsAll(compositeKeyIdentities)) {
|
||||
throw RequestException("A composite key needs to belong to a notary.")
|
||||
}
|
||||
}
|
||||
|
||||
private fun createResponse(payload: Any?, addCacheTimeout: Boolean = false): Response {
|
||||
@ -184,8 +194,23 @@ class NetworkMapWebService(private val nodeInfoStorage: NodeInfoStorage,
|
||||
}.build()
|
||||
}
|
||||
|
||||
private fun checkCertificates(nodeInfo: NodeInfo) {
|
||||
val nodeCaCert = nodeInfo.legalIdentitiesAndCerts.first().certPath.x509Certificates.find { CertRole.extract(it) == CertRole.NODE_CA }
|
||||
nodeCaCert ?: throw RequestException("The node certificate path does not contain the node CA certificate type in it.")
|
||||
val nodeCertPath = certificateSigningRequestStorage.getValidCertificatePath(nodeCaCert.publicKey)
|
||||
nodeCertPath ?: throw RequestException("Node certificate is either no longer valid or was never registered.")
|
||||
val rootCert = nodeCertPath.certificates.last().x509
|
||||
try {
|
||||
nodeInfo.legalIdentitiesAndCerts.forEach {
|
||||
validateCertPath(rootCert, it.certPath)
|
||||
}
|
||||
} catch (e: CertPathValidatorException) {
|
||||
throw RequestException("Invalid certificate path.")
|
||||
}
|
||||
}
|
||||
|
||||
class NetworkMapNotInitialisedException(message: String?) : Exception(message)
|
||||
class InvalidPlatformVersionException(message: String?) : Exception(message)
|
||||
class RequestException(message: String) : Exception(message)
|
||||
|
||||
private data class CachedData(val signedNetworkMap: SignedNetworkMap,
|
||||
val nodeInfoHashes: Set<SecureHash>,
|
||||
|
@ -124,6 +124,39 @@ class PersistentCertificateRequestStorageTest : TestBase() {
|
||||
assertNotNull(storage.getRequest(requestId)!!.certData)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `get valid certificate path returns correct value`() {
|
||||
// given
|
||||
val (csr, nodeKeyPair) = createRequest("LegalName", certRole = CertRole.NODE_CA)
|
||||
val requestId = storage.saveRequest(csr)
|
||||
storage.markRequestTicketCreated(requestId)
|
||||
storage.approveRequest(requestId, DOORMAN_SIGNATURE)
|
||||
val certPath = generateSignedCertPath(csr, nodeKeyPair)
|
||||
storage.putCertificatePath(
|
||||
requestId,
|
||||
certPath,
|
||||
DOORMAN_SIGNATURE
|
||||
)
|
||||
|
||||
// when
|
||||
val storedCertPath = storage.getValidCertificatePath(nodeKeyPair.public)
|
||||
|
||||
// then
|
||||
assertEquals(certPath, storedCertPath)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `get valid certificate path returns null if the certificate path cannot be found`() {
|
||||
// given
|
||||
val (_, nodeKeyPair) = createRequest("LegalName", certRole = CertRole.NODE_CA)
|
||||
|
||||
// when
|
||||
val storedCertPath = storage.getValidCertificatePath(nodeKeyPair.public)
|
||||
|
||||
// then
|
||||
assertNull(storedCertPath)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `sign request ignores subsequent sign requests`() {
|
||||
val (csr, nodeKeyPair) = createRequest("LegalName", certRole = CertRole.NODE_CA)
|
||||
|
@ -11,6 +11,7 @@
|
||||
package com.r3.corda.networkmanage.doorman.webservice
|
||||
|
||||
import com.nhaarman.mockito_kotlin.*
|
||||
import com.r3.corda.networkmanage.common.persistence.CertificateSigningRequestStorage
|
||||
import com.r3.corda.networkmanage.common.persistence.NetworkMapStorage
|
||||
import com.r3.corda.networkmanage.common.persistence.NodeInfoStorage
|
||||
import com.r3.corda.networkmanage.createNetworkMapEntity
|
||||
@ -42,6 +43,7 @@ import java.io.IOException
|
||||
import java.net.URL
|
||||
import java.security.cert.X509Certificate
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class NetworkMapWebServiceTest {
|
||||
@Rule
|
||||
@ -62,28 +64,54 @@ class NetworkMapWebServiceTest {
|
||||
|
||||
@Test
|
||||
fun `submit nodeInfo`() {
|
||||
// Create node info.
|
||||
val (_, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name("Test", "London", "GB"))
|
||||
val networkMapStorage: NetworkMapStorage = mock {
|
||||
on { getActiveNetworkMap() }.thenReturn(createNetworkMapEntity())
|
||||
}
|
||||
// Create node info.
|
||||
val (_, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name("Test", "London", "GB"))
|
||||
val csrStorage: CertificateSigningRequestStorage = mock {
|
||||
on { getValidCertificatePath(any()) }.thenReturn(signedNodeInfo.verified().legalIdentitiesAndCerts.first().certPath)
|
||||
}
|
||||
|
||||
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NetworkMapWebService(mock(), networkMapStorage, testNetworkMapConfig)).use {
|
||||
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NetworkMapWebService(mock(), networkMapStorage, csrStorage, testNetworkMapConfig)).use {
|
||||
it.start()
|
||||
// Post node info and signature to doorman, this should pass without any exception.
|
||||
it.doPost("publish", signedNodeInfo.serialize())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `submit nodeInfo with an unknown public key fails`() {
|
||||
// Create node info.
|
||||
val (_, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name("Test", "London", "GB"))
|
||||
val networkMapStorage: NetworkMapStorage = mock {
|
||||
on { getActiveNetworkMap() }.thenReturn(createNetworkMapEntity())
|
||||
}
|
||||
val csrStorage: CertificateSigningRequestStorage = mock {
|
||||
on { getValidCertificatePath(any()) }.thenReturn(null)
|
||||
}
|
||||
|
||||
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NetworkMapWebService(mock(), networkMapStorage, csrStorage, testNetworkMapConfig)).use {
|
||||
it.start()
|
||||
// Post node info and signature to doorman, this should pass without any exception.
|
||||
assertFailsWith<IOException>("Response Code 400") {
|
||||
it.doPost("publish", signedNodeInfo.serialize())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `submit old nodeInfo`() {
|
||||
// Create node info.
|
||||
val (_, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name("Test", "London", "GB"), platformVersion = 1)
|
||||
val networkMapStorage: NetworkMapStorage = mock {
|
||||
on { getActiveNetworkMap() }.thenReturn(createNetworkMapEntity(networkParameters = testNetworkParameters(minimumPlatformVersion = 2)))
|
||||
}
|
||||
// Create node info.
|
||||
val (_, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name("Test", "London", "GB"), platformVersion = 1)
|
||||
val csrStorage: CertificateSigningRequestStorage = mock {
|
||||
on { getValidCertificatePath(any()) }.thenReturn(signedNodeInfo.verified().legalIdentitiesAndCerts.first().certPath)
|
||||
}
|
||||
|
||||
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NetworkMapWebService(mock(), networkMapStorage, testNetworkMapConfig)).use {
|
||||
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NetworkMapWebService(mock(), networkMapStorage, csrStorage, testNetworkMapConfig)).use {
|
||||
it.start()
|
||||
assertThatThrownBy { it.doPost("publish", signedNodeInfo.serialize()) }
|
||||
.hasMessageStartingWith("Response Code 400: Minimum platform version is 2")
|
||||
@ -92,13 +120,16 @@ class NetworkMapWebServiceTest {
|
||||
|
||||
@Test
|
||||
fun `submit nodeInfo when no network map`() {
|
||||
// Create node info.
|
||||
val (_, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name("Test", "London", "GB"), platformVersion = 1)
|
||||
val networkMapStorage: NetworkMapStorage = mock {
|
||||
on { getActiveNetworkMap() }.thenReturn(null)
|
||||
}
|
||||
// Create node info.
|
||||
val (_, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name("Test", "London", "GB"), platformVersion = 1)
|
||||
val csrStorage: CertificateSigningRequestStorage = mock {
|
||||
on { getValidCertificatePath(any()) }.thenReturn(signedNodeInfo.verified().legalIdentitiesAndCerts.first().certPath)
|
||||
}
|
||||
|
||||
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NetworkMapWebService(mock(), networkMapStorage, testNetworkMapConfig)).use {
|
||||
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NetworkMapWebService(mock(), networkMapStorage, csrStorage, testNetworkMapConfig)).use {
|
||||
it.start()
|
||||
assertThatThrownBy { it.doPost("publish", signedNodeInfo.serialize()) }
|
||||
.hasMessageStartingWith("Response Code 503: Network parameters have not been initialised")
|
||||
@ -115,7 +146,7 @@ class NetworkMapWebServiceTest {
|
||||
on { getActiveNetworkMap() }.thenReturn(networkMapEntity)
|
||||
}
|
||||
|
||||
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NetworkMapWebService(mock(), networkMapStorage, testNetworkMapConfig)).use {
|
||||
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NetworkMapWebService(mock(), networkMapStorage, mock(), testNetworkMapConfig)).use {
|
||||
it.start()
|
||||
val signedNetworkMapResponse = it.doGet<SignedNetworkMap>("")
|
||||
verify(networkMapStorage, times(1)).getActiveNetworkMap()
|
||||
@ -137,7 +168,7 @@ class NetworkMapWebServiceTest {
|
||||
on { getActiveNetworkMap() }.thenReturn(createNetworkMapEntity(nodeInfoHashes = listOf(nodeInfoHash)))
|
||||
}
|
||||
|
||||
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NetworkMapWebService(nodeInfoStorage, networkMapStorage, testNetworkMapConfig)).use {
|
||||
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NetworkMapWebService(nodeInfoStorage, networkMapStorage, mock(), testNetworkMapConfig)).use {
|
||||
it.start()
|
||||
val nodeInfoResponse = it.doGet<SignedNodeInfo>("node-info/$nodeInfoHash")
|
||||
verify(nodeInfoStorage, times(1)).getNodeInfo(nodeInfoHash)
|
||||
@ -159,7 +190,7 @@ class NetworkMapWebServiceTest {
|
||||
on { getSignedNetworkParameters(networkParametersHash) }.thenReturn(signedNetworkParameters)
|
||||
}
|
||||
|
||||
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NetworkMapWebService(mock(), networkMapStorage, testNetworkMapConfig)).use {
|
||||
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NetworkMapWebService(mock(), networkMapStorage, mock(), testNetworkMapConfig)).use {
|
||||
it.start()
|
||||
val netParamsResponse = it.doGet<SignedNetworkParameters>("network-parameters/$networkParametersHash")
|
||||
verify(networkMapStorage, times(1)).getSignedNetworkParameters(networkParametersHash)
|
||||
@ -181,7 +212,7 @@ class NetworkMapWebServiceTest {
|
||||
val networkMapStorage: NetworkMapStorage = mock {
|
||||
on { getSignedNetworkParameters(hash) }.thenReturn(signingCertAndKeyPair.sign(netParams))
|
||||
}
|
||||
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NetworkMapWebService(nodeInfoStorage, networkMapStorage, testNetworkMapConfig)).use {
|
||||
NetworkManagementWebServer(NetworkHostAndPort("localhost", 0), NetworkMapWebService(nodeInfoStorage, networkMapStorage, mock(), testNetworkMapConfig)).use {
|
||||
it.start()
|
||||
val keyPair = Crypto.generateKeyPair()
|
||||
val signedHash = hash.serialize().sign { keyPair.sign(it) }
|
||||
|
Loading…
Reference in New Issue
Block a user