diff --git a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/NetworkParametersUpdateTest.kt b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/NetworkParametersUpdateTest.kt index e3f527928c..002e29219c 100644 --- a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/NetworkParametersUpdateTest.kt +++ b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/NetworkParametersUpdateTest.kt @@ -146,6 +146,7 @@ class NetworkParametersUpdateTest : IntegrationTest() { serverAddress, CertPathAndKey(listOf(doormanCa.certificate, rootCaCert), doormanCa.keyPair.private), DoormanConfig(approveAll = true, jira = null, approveInterval = timeoutMillis), + null, if (startNetworkMap) { NetworkMapStartParams( LocalSigner(networkMapCa), diff --git a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/NodeRegistrationTest.kt b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/NodeRegistrationTest.kt index 2d067f4501..c8774db53d 100644 --- a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/NodeRegistrationTest.kt +++ b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/NodeRegistrationTest.kt @@ -159,6 +159,13 @@ class NodeRegistrationTest : IntegrationTest() { 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, + crlEndpoint = URL("http://test.com/crl"), + crlUpdateInterval = timeoutMillis), if (startNetworkMap) { NetworkMapStartParams( LocalSigner(networkMapCa), diff --git a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/SigningServiceIntegrationTest.kt b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/SigningServiceIntegrationTest.kt index 8eacf959ef..8bb15c0930 100644 --- a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/SigningServiceIntegrationTest.kt +++ b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/SigningServiceIntegrationTest.kt @@ -14,6 +14,7 @@ import com.nhaarman.mockito_kotlin.* import com.r3.corda.networkmanage.common.HOST import com.r3.corda.networkmanage.common.HsmBaseTest import com.r3.corda.networkmanage.common.persistence.configureDatabase +import com.r3.corda.networkmanage.doorman.CertificateRevocationConfig import com.r3.corda.networkmanage.doorman.DoormanConfig import com.r3.corda.networkmanage.doorman.NetworkManagementServer import com.r3.corda.networkmanage.hsm.persistence.ApprovedCertificateRequestData @@ -26,6 +27,8 @@ import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.core.internal.uncheckedCast import net.corda.core.utilities.NetworkHostAndPort +import net.corda.core.utilities.hours +import net.corda.core.utilities.minutes import net.corda.core.utilities.seconds import net.corda.node.NodeRegistrationOption import net.corda.node.services.config.NodeConfiguration @@ -101,6 +104,14 @@ class SigningServiceIntegrationTest : HsmBaseTest() { hostAndPort = NetworkHostAndPort(HOST, 0), csrCertPathAndKey = null, doormanConfig = DoormanConfig(approveAll = true, approveInterval = 2.seconds.toMillis(), jira = null), + revocationConfig = CertificateRevocationConfig( + approveAll = true, + jira = null, + crlUpdateInterval = 2.hours.toMillis(), + crlCacheTimeout = 30.minutes.toMillis(), + crlEndpoint = URL("http://test.com/crl"), + approveInterval = 10.minutes.toMillis() + ), startNetworkMap = null) val doormanHostAndPort = server.hostAndPort // Start Corda network registration. diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/ApproveAllCertificateRevocationRequestStorage.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/ApproveAllCertificateRevocationRequestStorage.kt new file mode 100644 index 0000000000..c7db99060a --- /dev/null +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/ApproveAllCertificateRevocationRequestStorage.kt @@ -0,0 +1,17 @@ +package com.r3.corda.networkmanage.common.persistence + +import com.r3.corda.networkmanage.common.persistence.CertificateRevocationRequestStorage.Companion.DOORMAN_SIGNATURE +import net.corda.nodeapi.internal.network.CertificateRevocationRequest + +/** + * This storage automatically approves all created requests. + */ +class ApproveAllCertificateRevocationRequestStorage(private val delegate: CertificateRevocationRequestStorage) : CertificateRevocationRequestStorage by delegate { + + override fun saveRevocationRequest(request: CertificateRevocationRequest): String { + val requestId = delegate.saveRevocationRequest(request) + delegate.markRequestTicketCreated(requestId) + approveRevocationRequest(requestId, DOORMAN_SIGNATURE) + return requestId + } +} diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/CertificateRevocationListStorage.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/CertificateRevocationListStorage.kt index f59634b67d..ba4851fee3 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/CertificateRevocationListStorage.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/CertificateRevocationListStorage.kt @@ -7,6 +7,10 @@ import java.time.Instant * Interface for managing certificate revocation list persistence */ interface CertificateRevocationListStorage { + companion object { + val DOORMAN_SIGNATURE = "Doorman-Crl-Signer" + } + /** * Retrieves the latest certificate revocation list. * diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/CertificateRevocationRequestStorage.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/CertificateRevocationRequestStorage.kt index 40ffb3ec26..c56d20c99f 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/CertificateRevocationRequestStorage.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/CertificateRevocationRequestStorage.kt @@ -5,6 +5,7 @@ import net.corda.core.serialization.CordaSerializable import net.corda.nodeapi.internal.network.CertificateRevocationRequest import java.math.BigInteger import java.security.cert.CRLReason +import java.security.cert.X509Certificate import java.time.Instant /** @@ -12,6 +13,8 @@ import java.time.Instant */ @CordaSerializable data class CertificateRevocationRequestData(val requestId: String, // This is a uniquely generated string + val certificateSigningRequestId: String, + val certificate: X509Certificate, val certificateSerialNumber: BigInteger, val modifiedAt: Instant, val legalName: CordaX500Name, @@ -23,6 +26,9 @@ data class CertificateRevocationRequestData(val requestId: String, // This is a * Interface for managing certificate revocation requests persistence */ interface CertificateRevocationRequestStorage { + companion object { + val DOORMAN_SIGNATURE = "Doorman-Crr-Signer" + } /** * Creates a new revocation request for the given [certificateSerialNumber]. @@ -70,5 +76,10 @@ interface CertificateRevocationRequestStorage { * @param rejectedBy who is rejecting it * @param reason description of the reason of this rejection. */ - fun rejectRevocationRequest(requestId: String, rejectedBy: String, reason: String) + fun rejectRevocationRequest(requestId: String, rejectedBy: String, reason: String?) + + /** + * Persist the fact that a ticket has been created for the given [requestId]. + */ + fun markRequestTicketCreated(requestId: String) } \ No newline at end of file diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/CertificateSigningRequestStorage.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/CertificateSigningRequestStorage.kt index 04cbe1d187..5a7e440807 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/CertificateSigningRequestStorage.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/CertificateSigningRequestStorage.kt @@ -32,7 +32,7 @@ data class CertificateSigningRequest(val requestId: String, */ interface CertificateSigningRequestStorage { companion object { - val DOORMAN_SIGNATURE = "Doorman" + val DOORMAN_SIGNATURE = "Doorman-Csr-Signer" } /** diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateRevocationRequestStorage.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateRevocationRequestStorage.kt index 302a176efe..54f97739bd 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateRevocationRequestStorage.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateRevocationRequestStorage.kt @@ -10,6 +10,7 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseTransaction import net.corda.nodeapi.internal.persistence.TransactionIsolationLevel import java.math.BigInteger +import java.security.cert.X509Certificate import java.time.Instant class PersistentCertificateRevocationRequestStorage(private val database: CordaPersistence) : CertificateRevocationRequestStorage { @@ -129,7 +130,7 @@ class PersistentCertificateRevocationRequestStorage(private val database: CordaP } } - override fun rejectRevocationRequest(requestId: String, rejectedBy: String, reason: String) { + override fun rejectRevocationRequest(requestId: String, rejectedBy: String, reason: String?) { database.transaction { val revocation = getRevocationRequestEntity(requestId) if (revocation == null) { @@ -145,10 +146,28 @@ class PersistentCertificateRevocationRequestStorage(private val database: CordaP } } - private fun getRevocationRequestEntity(requestId: String): CertificateRevocationRequestEntity? { + override fun markRequestTicketCreated(requestId: String) { + // Even though, we have an assumption that there is always a single instance of the doorman service running, + // the SERIALIZABLE isolation level is used here just to ensure data consistency between the updates. + return database.transaction(TransactionIsolationLevel.SERIALIZABLE) { + val request = getRevocationRequestEntity(requestId, RequestStatus.NEW) + request ?: throw IllegalArgumentException("Error when creating request ticket with id: $requestId. Request does not exist or its status is not NEW.") + val update = request.copy( + modifiedAt = Instant.now(), + status = RequestStatus.TICKET_CREATED) + session.merge(update) + } + } + + private fun getRevocationRequestEntity(requestId: String, status: RequestStatus? = null): CertificateRevocationRequestEntity? { return database.transaction { uniqueEntityWhere { builder, path -> - builder.equal(path.get(CertificateRevocationRequestEntity::requestId.name), requestId) + val idEqual = builder.equal(path.get(CertificateRevocationRequestEntity::requestId.name), requestId) + if (status == null) { + idEqual + } else { + builder.and(idEqual, builder.equal(path.get(CertificateRevocationRequestEntity::status.name), status)) + } } } } @@ -156,6 +175,8 @@ class PersistentCertificateRevocationRequestStorage(private val database: CordaP private fun CertificateRevocationRequestEntity.toCertificateRevocationRequestData(): CertificateRevocationRequestData { return CertificateRevocationRequestData( requestId, + certificateData.certificateSigningRequest.requestId, + certificateData.certPath.certificates.first() as X509Certificate, certificateSerialNumber, modifiedAt, legalName, diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/CrrJiraCient.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/CrrJiraCient.kt new file mode 100644 index 0000000000..e40fb5d397 --- /dev/null +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/CrrJiraCient.kt @@ -0,0 +1,57 @@ +/* + * R3 Proprietary and Confidential + * + * Copyright (c) 2018 R3 Limited. All rights reserved. + * + * The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law. + * + * Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited. + */ + +package com.r3.corda.networkmanage.doorman + +import com.atlassian.jira.rest.client.api.JiraRestClient +import com.atlassian.jira.rest.client.api.domain.input.IssueInputBuilder +import com.atlassian.jira.rest.client.api.domain.input.TransitionInput +import com.r3.corda.networkmanage.common.persistence.CertificateRevocationRequestData +import net.corda.core.utilities.contextLogger + +class CrrJiraClient(restClient: JiraRestClient, projectCode: String) : JiraClient(restClient, projectCode) { + companion object { + val logger = contextLogger() + } + + fun createCertificateRevocationRequestTicket(revocationRequest: CertificateRevocationRequestData) { + // Check there isn't already a ticket for this request. + if (getIssueById(revocationRequest.requestId) != null) { + logger.warn("There is already a ticket corresponding to request Id ${revocationRequest.requestId}, not creating a new one.") + return + } + val ticketDescription = "Legal name: ${revocationRequest.legalName}\n" + + "Certificate serial number: ${revocationRequest.certificateSerialNumber}\n" + + "Revocation reason: ${revocationRequest.reason.name}\n" + + "Reporter: ${revocationRequest.reporter}\n" + + "CSR request ID: ${revocationRequest.certificateSigningRequestId}" + + val issue = IssueInputBuilder().setIssueTypeId(taskIssueType.id) + .setProjectKey(projectCode) + .setDescription(ticketDescription) + .setFieldValue(requestIdField.id, revocationRequest.requestId) + // This will block until the issue is created. + val issueId = restClient.issueClient.createIssue(issue.build()).fail { logger.error("Exception when creating JIRA issue.", it) }.claim().key + val createdIssue = checkNotNull(getIssueById(issueId)) { "Missing the JIRA ticket for the request ID: $issueId" } + restClient.issueClient.addAttachment(createdIssue.attachmentsUri, revocationRequest.certificate.encoded.inputStream(), "${revocationRequest.certificateSerialNumber}.cer") + .fail { CsrJiraClient.logger.error("Error processing request '${createdIssue.key}' : Exception when uploading attachment to JIRA.", it) }.claim() + } + + fun updateDoneCertificateRevocationRequests(doneRequests: List) { + doneRequests.forEach { id -> + val issue = getIssueById(id) + issue ?: throw IllegalStateException("Missing the JIRA ticket for the request ID: $id") + if (doneTransitionId == -1) { + doneTransitionId = restClient.issueClient.getTransitions(issue.transitionsUri).claim().single { it.name == "Done" }.id + } + restClient.issueClient.transition(issue, TransitionInput(doneTransitionId)).fail { logger.error("Exception when transiting JIRA status.", it) }.claim() + } + } +} diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/CsrJiraCient.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/CsrJiraCient.kt new file mode 100644 index 0000000000..3be30e33d9 --- /dev/null +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/CsrJiraCient.kt @@ -0,0 +1,93 @@ +/* + * R3 Proprietary and Confidential + * + * Copyright (c) 2018 R3 Limited. All rights reserved. + * + * The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law. + * + * Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited. + */ + +package com.r3.corda.networkmanage.doorman + +import com.atlassian.jira.rest.client.api.JiraRestClient +import com.atlassian.jira.rest.client.api.domain.input.IssueInputBuilder +import com.atlassian.jira.rest.client.api.domain.input.TransitionInput +import com.r3.corda.networkmanage.common.utils.getCertRole +import com.r3.corda.networkmanage.common.utils.getEmail +import net.corda.core.identity.CordaX500Name +import net.corda.core.utilities.contextLogger +import net.corda.nodeapi.internal.crypto.X509Utilities +import org.bouncycastle.openssl.jcajce.JcaPEMWriter +import org.bouncycastle.pkcs.PKCS10CertificationRequest +import org.bouncycastle.util.io.pem.PemObject +import java.io.StringWriter +import java.security.cert.CertPath +import javax.security.auth.x500.X500Principal + +class CsrJiraClient(restClient: JiraRestClient, projectCode: String) : JiraClient(restClient, projectCode) { + companion object { + val logger = contextLogger() + } + + // TODO: Pass in a parsed object instead of raw PKCS10 request. + fun createCertificateSigningRequestTicket(requestId: String, signingRequest: PKCS10CertificationRequest) { + // Check there isn't already a ticket for this request. + if (getIssueById(requestId) != null) { + logger.warn("There is already a ticket corresponding to request Id $requestId, not creating a new one.") + return + } + + // Make sure request has been accepted. + val request = StringWriter() + JcaPEMWriter(request).use { + it.writeObject(PemObject("CERTIFICATE REQUEST", signingRequest.encoded)) + } + + // TODO The subject of the signing request has already been validated and parsed into a CordaX500Name. We shouldn't + // have to do it again here. + val subject = CordaX500Name.build(X500Principal(signingRequest.subject.encoded)) + val email = signingRequest.getEmail() + + val certRole = signingRequest.getCertRole() + + val ticketSummary = if (subject.organisationUnit != null) { + "${subject.organisationUnit}, ${subject.organisation}" + } else { + subject.organisation + } + + val data = mapOf("Requested Role Type" to certRole.name, + "Organisation" to subject.organisation, + "Organisation Unit" to subject.organisationUnit, + "Nearest City" to subject.locality, + "Country" to subject.country, + "Email" to email) + + val ticketDescription = data.filter { it.value != null }.map { "${it.key}: ${it.value}" }.joinToString("\n") + "\n\n{code}$request{code}" + + val issue = IssueInputBuilder().setIssueTypeId(taskIssueType.id) + .setProjectKey(projectCode) + .setDescription(ticketDescription) + .setSummary(ticketSummary) + .setFieldValue(requestIdField.id, requestId) + // This will block until the issue is created. + restClient.issueClient.createIssue(issue.build()).fail { logger.error("Exception when creating JIRA issue.", it) }.claim() + } + + fun updateDoneCertificateSigningRequests(signedRequests: Map) { + // Retrieving certificates for signed CSRs to attach to the jira tasks. + signedRequests.forEach { (id, certPath) -> + val certificate = certPath.certificates.first() + val issue = getIssueById(id) + if (issue != null) { + if (doneTransitionId == -1) { + doneTransitionId = restClient.issueClient.getTransitions(issue.transitionsUri).claim().single { it.name == "Done" }.id + } + restClient.issueClient.transition(issue, TransitionInput(doneTransitionId)).fail { logger.error("Exception when transiting JIRA status.", it) }.claim() + restClient.issueClient.addAttachment(issue.attachmentsUri, certificate.encoded.inputStream(), "${X509Utilities.CORDA_CLIENT_CA}.cer") + .fail { logger.error("Error processing request '${issue.key}' : Exception when uploading attachment to JIRA.", it) }.claim() + } + } + } +} diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/DoormanParameters.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/DoormanParameters.kt index 58c70bf71c..f7020b224b 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/DoormanParameters.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/DoormanParameters.kt @@ -14,6 +14,7 @@ import com.google.common.primitives.Booleans import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.seconds import net.corda.nodeapi.internal.persistence.DatabaseConfig +import java.net.URL import java.nio.file.Path import java.util.* @@ -24,6 +25,7 @@ data class NetworkManagementServerConfig( // TODO: Move local signing to signing val doorman: DoormanConfig?, val networkMap: NetworkMapConfig?, + val revocation: CertificateRevocationConfig?, // TODO Should be part of a localSigning sub-config val keystorePath: Path? = null, @@ -54,6 +56,19 @@ data class DoormanConfig(val approveAll: Boolean = false, } } +data class CertificateRevocationConfig(val approveAll: Boolean = false, + val jira: JiraConfig? = null, + val crlUpdateInterval: Long, + val crlEndpoint: URL, + val crlCacheTimeout: Long, + val approveInterval: Long = NetworkManagementServerConfig.DEFAULT_APPROVE_INTERVAL.toMillis()) { + init { + require(Booleans.countTrue(approveAll, jira != null) == 1) { + "Either 'approveAll' or 'jira' config settings need to be specified but not both" + } + } +} + data class NetworkMapConfig(val cacheTimeout: Long, // TODO: Move signing to signing server. val signInterval: Long = NetworkManagementServerConfig.DEFAULT_SIGN_INTERVAL.toMillis()) diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/JiraCient.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/JiraCient.kt index c7fd251c8d..07361dcd96 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/JiraCient.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/JiraCient.kt @@ -16,80 +16,23 @@ import com.atlassian.jira.rest.client.api.domain.Comment import com.atlassian.jira.rest.client.api.domain.Field import com.atlassian.jira.rest.client.api.domain.Issue import com.atlassian.jira.rest.client.api.domain.IssueType -import com.atlassian.jira.rest.client.api.domain.input.IssueInputBuilder import com.atlassian.jira.rest.client.api.domain.input.TransitionInput -import com.r3.corda.networkmanage.common.utils.getCertRole -import com.r3.corda.networkmanage.common.utils.getEmail -import net.corda.core.identity.CordaX500Name -import net.corda.core.internal.CertRole import net.corda.core.utilities.contextLogger -import net.corda.nodeapi.internal.crypto.X509Utilities -import org.bouncycastle.openssl.jcajce.JcaPEMWriter -import org.bouncycastle.pkcs.PKCS10CertificationRequest -import org.bouncycastle.util.io.pem.PemObject -import java.io.StringWriter -import java.security.cert.CertPath -import javax.security.auth.x500.X500Principal -class JiraClient(private val restClient: JiraRestClient, private val projectCode: String) { +abstract class JiraClient(protected val restClient: JiraRestClient, protected val projectCode: String) { companion object { val logger = contextLogger() } // The JIRA project must have a Request ID and reject reason field, and the Task issue type. - private val requestIdField: Field = restClient.metadataClient.fields.claim().find { it.name == "Request ID" } ?: throw IllegalArgumentException("Request ID field not found in JIRA '$projectCode'") - private val rejectReasonField: Field = restClient.metadataClient.fields.claim().find { it.name == "Reject Reason" } ?: throw IllegalArgumentException("Reject Reason field not found in JIRA '$projectCode'") - private val taskIssueType: IssueType = restClient.metadataClient.issueTypes.claim().find { it.name == "Task" } ?: throw IllegalArgumentException("Task issue type field not found in JIRA '$projectCode'") + protected val requestIdField: Field = restClient.metadataClient.fields.claim().find { it.name == "Request ID" } ?: throw IllegalArgumentException("Request ID field not found in JIRA '$projectCode'") + protected val taskIssueType: IssueType = restClient.metadataClient.issueTypes.claim().find { it.name == "Task" } ?: throw IllegalArgumentException("Task issue type field not found in JIRA '$projectCode'") + protected val rejectReasonField: Field = restClient.metadataClient.fields.claim().find { it.name == "Reject Reason" } ?: throw IllegalArgumentException("Reject Reason field not found in JIRA '$projectCode'") - private var doneTransitionId: Int = -1 + protected var doneTransitionId: Int = -1 private var canceledTransitionId: Int = -1 private var startProgressTransitionId: Int = -1 - // TODO: Pass in a parsed object instead of raw PKCS10 request. - fun createRequestTicket(requestId: String, signingRequest: PKCS10CertificationRequest) { - // Check there isn't already a ticket for this request. - if (getIssueById(requestId) != null) { - logger.warn("There is already a ticket corresponding to request Id $requestId, not creating a new one.") - return - } - - // Make sure request has been accepted. - val request = StringWriter() - JcaPEMWriter(request).use { - it.writeObject(PemObject("CERTIFICATE REQUEST", signingRequest.encoded)) - } - - // TODO The subject of the signing request has already been validated and parsed into a CordaX500Name. We shouldn't - // have to do it again here. - val subject = CordaX500Name.build(X500Principal(signingRequest.subject.encoded)) - val email = signingRequest.getEmail() - - val certRole = signingRequest.getCertRole() - - val ticketSummary = if (subject.organisationUnit != null) { - "${subject.organisationUnit}, ${subject.organisation}" - } else { - subject.organisation - } - - val data = mapOf("Requested Role Type" to certRole.name, - "Organisation" to subject.organisation, - "Organisation Unit" to subject.organisationUnit, - "Nearest City" to subject.locality, - "Country" to subject.country, - "Email" to email) - - val ticketDescription = data.filter { it.value != null }.map { "${it.key}: ${it.value}" }.joinToString("\n") + "\n\n{code}$request{code}" - - val issue = IssueInputBuilder().setIssueTypeId(taskIssueType.id) - .setProjectKey(projectCode) - .setDescription(ticketDescription) - .setSummary(ticketSummary) - .setFieldValue(requestIdField.id, requestId) - // This will block until the issue is created. - restClient.issueClient.createIssue(issue.build()).fail { logger.error("Exception when creating JIRA issue.", it) }.claim() - } - fun getApprovedRequests(): List { val issues = restClient.searchClient.searchJql("project = $projectCode AND status = Approved").claim().issues return issues.mapNotNull { issue -> @@ -113,22 +56,6 @@ class JiraClient(private val restClient: JiraRestClient, private val projectCode } } - fun updateSignedRequests(signedRequests: Map) { - // Retrieving certificates for signed CSRs to attach to the jira tasks. - signedRequests.forEach { (id, certPath) -> - val certificate = certPath.certificates.first() - val issue = getIssueById(id) - if (issue != null) { - if (doneTransitionId == -1) { - doneTransitionId = restClient.issueClient.getTransitions(issue.transitionsUri).claim().single { it.name == "Done" }.id - } - restClient.issueClient.transition(issue, TransitionInput(doneTransitionId)).fail { logger.error("Exception when transiting JIRA status.", it) }.claim() - restClient.issueClient.addAttachment(issue.attachmentsUri, certificate.encoded.inputStream(), "${X509Utilities.CORDA_CLIENT_CA}.cer") - .fail { logger.error("Error processing request '${issue.key}' : Exception when uploading attachment to JIRA.", it) }.claim() - } - } - } - fun updateRejectedRequests(rejectedRequests: List) { rejectedRequests.mapNotNull { getIssueById(it) } .forEach { issue -> @@ -146,7 +73,7 @@ class JiraClient(private val restClient: JiraRestClient, private val projectCode } } - private fun getIssueById(requestId: String): Issue? { + protected fun getIssueById(requestId: String): Issue? { // Jira only support ~ (contains) search for custom textfield. return restClient.searchClient.searchJql("'Request ID' ~ $requestId").claim().issues.firstOrNull() } diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt index ae23aa8b89..e992c08d12 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt @@ -100,6 +100,7 @@ private fun doormanMode(cmdLineOptions: DoormanCmdLineOptions, config: NetworkMa config.address, csrAndNetworkMap?.first, config.doorman, + config.revocation, networkMapStartParams) Runtime.getRuntime().addShutdownHook(object : Thread("ShutdownHook") { diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkManagementServer.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkManagementServer.kt index 1d8a96db7c..cfa57bc569 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkManagementServer.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkManagementServer.kt @@ -14,16 +14,13 @@ import com.atlassian.jira.rest.client.internal.async.AsynchronousJiraRestClientF import com.r3.corda.networkmanage.common.persistence.* 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 com.r3.corda.networkmanage.doorman.signer.* +import com.r3.corda.networkmanage.doorman.webservice.* import net.corda.core.crypto.SecureHash 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 @@ -48,7 +45,7 @@ class NetworkManagementServer(dataSourceProperties: Properties, databaseConfig: override fun close() { logger.info("Closing server...") - for (closeAction in closeActions) { + for (closeAction in closeActions.reversed()) { try { closeAction() } catch (e: Exception) { @@ -96,7 +93,7 @@ class NetworkManagementServer(dataSourceProperties: Properties, databaseConfig: val jiraConfig = config.jira val requestProcessor = if (jiraConfig != null) { val jiraWebAPI = AsynchronousJiraRestClientFactory().createWithBasicHttpAuthentication(URI(jiraConfig.address), jiraConfig.username, jiraConfig.password) - val jiraClient = JiraClient(jiraWebAPI, jiraConfig.projectCode) + val jiraClient = CsrJiraClient(jiraWebAPI, jiraConfig.projectCode) JiraCsrHandler(jiraClient, requestService, DefaultCsrHandler(requestService, csrCertPathAndKey)) } else { DefaultCsrHandler(requestService, csrCertPathAndKey) @@ -119,9 +116,51 @@ class NetworkManagementServer(dataSourceProperties: Properties, databaseConfig: return RegistrationWebService(requestProcessor, Duration.ofMillis(config.approveInterval)) } + private fun getRevocationServices(config: CertificateRevocationConfig, + database: CordaPersistence, + csrCertPathAndKeyPair: CertPathAndKey?): Pair { + 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) + + val crlHandler = csrCertPathAndKeyPair?.let { + LocalCrlHandler(crrStorage, crlStorage, CertificateAndKeyPair(it.certPath.first(), it.toKeyPair()), Duration.ofMillis(config.crlUpdateInterval), config.crlEndpoint) + } + + val jiraConfig = config.jira + val crrHandler = if (jiraConfig != null) { + val jiraWebAPI = AsynchronousJiraRestClientFactory().createWithBasicHttpAuthentication(URI(jiraConfig.address), jiraConfig.username, jiraConfig.password) + val jiraClient = CrrJiraClient(jiraWebAPI, jiraConfig.projectCode) + JiraCrrHandler(jiraClient, crrStorage, crlHandler) + } else { + DefaultCrrHandler(crrStorage, crlHandler) + } + + val scheduledExecutor = Executors.newScheduledThreadPool(1) + val approvalThread = Runnable { + try { + // Process Jira approved tickets. + crrHandler.processRequests() + } 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 + // TODO start socket server + return Pair(CertificateRevocationRequestWebService(crrHandler), CertificateRevocationListWebService(crlStorage, Duration.ofMillis(config.crlCacheTimeout))) + } + fun start(hostAndPort: NetworkHostAndPort, csrCertPathAndKey: CertPathAndKey?, - doormanConfig: DoormanConfig?, // TODO Doorman config shouldn't be optional as the doorman is always required to run + doormanConfig: DoormanConfig?, // TODO Doorman config shouldn't be optional as the doorman is always required to run + revocationConfig: CertificateRevocationConfig?, startNetworkMap: NetworkMapStartParams? ) { val services = mutableListOf() @@ -129,6 +168,11 @@ class NetworkManagementServer(dataSourceProperties: Properties, databaseConfig: startNetworkMap?.let { services += getNetworkMapService(it.config, it.signer) } doormanConfig?.let { services += getDoormanService(it, database, csrCertPathAndKey, serverStatus) } + revocationConfig?.let { + val revocationServices = getRevocationServices(it, database, csrCertPathAndKey) + services += revocationServices.first + services += revocationServices.second + } require(services.isNotEmpty()) { "No service created, please provide at least one service config." } diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/signer/CrlHandler.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/signer/CrlHandler.kt new file mode 100644 index 0000000000..c72c4e0e04 --- /dev/null +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/signer/CrlHandler.kt @@ -0,0 +1,56 @@ +/* + * R3 Proprietary and Confidential + * + * Copyright (c) 2018 R3 Limited. All rights reserved. + * + * The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law. + * + * Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited. + */ + +package com.r3.corda.networkmanage.doorman.signer + +import com.r3.corda.networkmanage.common.persistence.CertificateRevocationListStorage +import com.r3.corda.networkmanage.common.persistence.CertificateRevocationRequestStorage +import com.r3.corda.networkmanage.common.persistence.RequestStatus +import com.r3.corda.networkmanage.common.signer.CertificateRevocationListSigner +import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair +import java.net.URL +import java.time.Duration +import com.r3.corda.networkmanage.common.persistence.CertificateRevocationListStorage.Companion.DOORMAN_SIGNATURE +import net.corda.core.utilities.contextLogger +import net.corda.core.utilities.trace + +class LocalCrlHandler(private val crrStorage: CertificateRevocationRequestStorage, + crlStorage: CertificateRevocationListStorage, + issuerCertAndKey: CertificateAndKeyPair, + crlUpdateInterval: Duration, + crlEndpoint: URL) { + private companion object { + private val logger = contextLogger() + } + + private val crlSigner = CertificateRevocationListSigner( + crlStorage, + issuerCertAndKey.certificate, + crlUpdateInterval, + crlEndpoint, + LocalSigner(issuerCertAndKey)) + + fun signCrl() { + logger.info("Executing CRL signing...") + val approvedRequests = crrStorage.getRevocationRequests(RequestStatus.APPROVED) + logger.debug("Approved certificate revocation requests retrieved.") + logger.trace { approvedRequests.toString() } + if (approvedRequests.isEmpty()) { + // Nothing to add to the current CRL + logger.debug("There are no APPROVED certificate revocation requests. Aborting CRL signing.") + return + } + val currentRequests = crrStorage.getRevocationRequests(RequestStatus.DONE) + logger.debug("Existing certificate revocation requests retrieved.") + logger.trace { currentRequests.toString() } + crlSigner.createSignedCRL(approvedRequests, currentRequests, DOORMAN_SIGNATURE) + logger.info("New CRL signed.") + } +} diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/signer/CrrHandler.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/signer/CrrHandler.kt new file mode 100644 index 0000000000..5d6a13de4b --- /dev/null +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/signer/CrrHandler.kt @@ -0,0 +1,27 @@ +/* + * R3 Proprietary and Confidential + * + * Copyright (c) 2018 R3 Limited. All rights reserved. + * + * The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law. + * + * Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited. + */ + +package com.r3.corda.networkmanage.doorman.signer + +import com.r3.corda.networkmanage.common.persistence.CertificateRevocationRequestStorage +import net.corda.nodeapi.internal.network.CertificateRevocationRequest + +interface CrrHandler { + fun processRequests() + fun saveRevocationRequest(request: CertificateRevocationRequest): String +} + +class DefaultCrrHandler(private val crrStorage: CertificateRevocationRequestStorage, + private val localCrlHandler: LocalCrlHandler?) : CrrHandler { + override fun saveRevocationRequest(request: CertificateRevocationRequest): String = crrStorage.saveRevocationRequest(request) + override fun processRequests() { + localCrlHandler?.signCrl() + } +} diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/signer/JiraCrrHandler.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/signer/JiraCrrHandler.kt new file mode 100644 index 0000000000..ca3e998e56 --- /dev/null +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/signer/JiraCrrHandler.kt @@ -0,0 +1,93 @@ +/* + * R3 Proprietary and Confidential + * + * Copyright (c) 2018 R3 Limited. All rights reserved. + * + * The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law. + * + * Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited. + */ + +package com.r3.corda.networkmanage.doorman.signer + +import com.r3.corda.networkmanage.common.persistence.CertificateRevocationRequestData +import com.r3.corda.networkmanage.common.persistence.CertificateRevocationRequestStorage +import com.r3.corda.networkmanage.common.persistence.RequestStatus +import com.r3.corda.networkmanage.doorman.ApprovedRequest +import com.r3.corda.networkmanage.doorman.CrrJiraClient +import com.r3.corda.networkmanage.doorman.RejectedRequest +import net.corda.core.utilities.contextLogger +import net.corda.nodeapi.internal.network.CertificateRevocationRequest + +class JiraCrrHandler(private val jiraClient: CrrJiraClient, + private val crrStorage: CertificateRevocationRequestStorage, + private val localCrlHandler: LocalCrlHandler?) : CrrHandler { + private companion object { + val logger = contextLogger() + } + + override fun saveRevocationRequest(request: CertificateRevocationRequest): String { + try { + val requestId = crrStorage.saveRevocationRequest(request) + val requestData = crrStorage.getRevocationRequest(requestId) + requestData ?: throw IllegalStateException("Request $requestId does not exist.") + jiraClient.createCertificateRevocationRequestTicket(requestData) + crrStorage.markRequestTicketCreated(requestId) + return requestId + } catch (e: Exception) { + logger.error("There was an error while creating JIRA tickets", e) + throw e + } + } + + override fun processRequests() { + createTickets() + val (approvedRequests, rejectedRequests) = updateRequestStatuses() + updateJiraTickets(approvedRequests, rejectedRequests) + localCrlHandler?.signCrl() + } + + private fun updateRequestStatuses(): Pair, List> { + // Update local request statuses. + val approvedRequest = jiraClient.getApprovedRequests() + approvedRequest.forEach { (id, approvedBy) -> crrStorage.approveRevocationRequest(id, approvedBy) } + val rejectedRequest = jiraClient.getRejectedRequests() + rejectedRequest.forEach { (id, rejectedBy, reason) -> crrStorage.rejectRevocationRequest(id, rejectedBy, reason) } + return Pair(approvedRequest, rejectedRequest) + } + + private fun updateJiraTickets(approvedRequest: List, rejectedRequest: List) { + // Reconfirm request status and update jira status + val doneRequests = approvedRequest.mapNotNull { crrStorage.getRevocationRequest(it.requestId) } + .filter { it.status == RequestStatus.DONE } + .map { it.requestId } + + jiraClient.updateDoneCertificateRevocationRequests(doneRequests) + + val rejectedRequestIDs = rejectedRequest.mapNotNull { crrStorage.getRevocationRequest(it.requestId) } + .filter { it.status == RequestStatus.REJECTED } + .map { it.requestId } + jiraClient.updateRejectedRequests(rejectedRequestIDs) + } + + /** + * 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. + */ + private fun createTickets() { + crrStorage.getRevocationRequests(RequestStatus.NEW).forEach { + try { + createTicket(it) + } catch (e: Exception) { + logger.warn("There were errors while creating Jira tickets for request '${it.requestId}'", e) + } + } + } + + private fun createTicket(revocationRequest: CertificateRevocationRequestData) { + jiraClient.createCertificateRevocationRequestTicket(revocationRequest) + crrStorage.markRequestTicketCreated(revocationRequest.requestId) + } +} diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/signer/JiraCsrHandler.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/signer/JiraCsrHandler.kt index f17ecc3a5e..bfa3173afa 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/signer/JiraCsrHandler.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/signer/JiraCsrHandler.kt @@ -15,12 +15,12 @@ import com.r3.corda.networkmanage.common.persistence.CertificateSigningRequest import com.r3.corda.networkmanage.common.persistence.CertificateSigningRequestStorage import com.r3.corda.networkmanage.common.persistence.RequestStatus import com.r3.corda.networkmanage.doorman.ApprovedRequest -import com.r3.corda.networkmanage.doorman.JiraClient +import com.r3.corda.networkmanage.doorman.CsrJiraClient import com.r3.corda.networkmanage.doorman.RejectedRequest import net.corda.core.utilities.contextLogger import org.bouncycastle.pkcs.PKCS10CertificationRequest -class JiraCsrHandler(private val jiraClient: JiraClient, private val storage: CertificateSigningRequestStorage, private val delegate: CsrHandler) : CsrHandler by delegate { +class JiraCsrHandler(private val jiraClient: CsrJiraClient, private val storage: CertificateSigningRequestStorage, private val delegate: CsrHandler) : CsrHandler by delegate { private companion object { val log = contextLogger() } @@ -30,7 +30,7 @@ class JiraCsrHandler(private val jiraClient: JiraClient, private val storage: Ce // Make sure request has been accepted. try { if (delegate.getResponse(requestId) !is CertificateResponse.Unauthorised) { - jiraClient.createRequestTicket(requestId, rawRequest) + jiraClient.createCertificateSigningRequestTicket(requestId, rawRequest) storage.markRequestTicketCreated(requestId) } } catch (e: Exception) { @@ -62,7 +62,7 @@ class JiraCsrHandler(private val jiraClient: JiraClient, private val storage: Ce .filter { it.status == RequestStatus.DONE && it.certData != null } .associateBy { it.requestId } .mapValues { it.value.certData!!.certPath } - jiraClient.updateSignedRequests(signedRequests) + jiraClient.updateDoneCertificateSigningRequests(signedRequests) val rejectedRequestIDs = rejectedRequest.mapNotNull { storage.getRequest(it.requestId) } .filter { it.status == RequestStatus.REJECTED } @@ -87,7 +87,7 @@ class JiraCsrHandler(private val jiraClient: JiraClient, private val storage: Ce } private fun createTicket(signingRequest: CertificateSigningRequest) { - jiraClient.createRequestTicket(signingRequest.requestId, signingRequest.request) + jiraClient.createCertificateSigningRequestTicket(signingRequest.requestId, signingRequest.request) storage.markRequestTicketCreated(signingRequest.requestId) } } diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/webservice/CertificateRevocationListWebService.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/webservice/CertificateRevocationListWebService.kt index c037be93cb..f5b98da759 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/webservice/CertificateRevocationListWebService.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/webservice/CertificateRevocationListWebService.kt @@ -6,6 +6,7 @@ import com.r3.corda.networkmanage.common.persistence.CertificateRevocationListSt import com.r3.corda.networkmanage.common.persistence.CrlIssuer import com.r3.corda.networkmanage.doorman.webservice.CertificateRevocationListWebService.Companion.CRL_PATH import net.corda.core.utilities.contextLogger +import java.time.Duration import java.util.concurrent.TimeUnit import javax.ws.rs.GET import javax.ws.rs.Path @@ -16,7 +17,7 @@ import javax.ws.rs.core.Response.status @Path(CRL_PATH) class CertificateRevocationListWebService(private val revocationListStorage: CertificateRevocationListStorage, - cacheTimeout: Long) { + cacheTimeout: Duration) { companion object { private val logger = contextLogger() const val CRL_PATH = "certificate-revocation-list" @@ -26,7 +27,7 @@ class CertificateRevocationListWebService(private val revocationListStorage: Cer } private val crlCache: LoadingCache = Caffeine.newBuilder() - .expireAfterWrite(cacheTimeout, TimeUnit.MILLISECONDS) + .expireAfterWrite(cacheTimeout.toMillis(), TimeUnit.MILLISECONDS) .build({ key -> revocationListStorage.getCertificateRevocationList(key)?.encoded }) diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/webservice/CertificateRevocationRequestWebService.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/webservice/CertificateRevocationRequestWebService.kt index ba23b2632f..65a1e25ec0 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/webservice/CertificateRevocationRequestWebService.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/webservice/CertificateRevocationRequestWebService.kt @@ -1,6 +1,6 @@ package com.r3.corda.networkmanage.doorman.webservice -import com.r3.corda.networkmanage.common.persistence.CertificateRevocationRequestStorage +import com.r3.corda.networkmanage.doorman.signer.CrrHandler import com.r3.corda.networkmanage.doorman.webservice.CertificateRevocationRequestWebService.Companion.CRR_PATH import net.corda.core.serialization.deserialize import net.corda.nodeapi.internal.network.CertificateRevocationRequest @@ -14,7 +14,7 @@ import javax.ws.rs.core.Response import javax.ws.rs.core.Response.ok @Path(CRR_PATH) -class CertificateRevocationRequestWebService(private val revocationRequestStorage: CertificateRevocationRequestStorage) { +class CertificateRevocationRequestWebService(private val crrHandler: CrrHandler) { companion object { const val CRR_PATH = "certificate-revocation-request" @@ -25,7 +25,7 @@ class CertificateRevocationRequestWebService(private val revocationRequestStorag @Produces(MediaType.TEXT_PLAIN) fun submitRequest(input: InputStream): Response { val request = input.readBytes().deserialize() - val requestId = revocationRequestStorage.saveRevocationRequest(request) + val requestId = crrHandler.saveRevocationRequest(request) return ok(requestId).build() } } \ No newline at end of file diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/signer/CertificateRevocationListSignerTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/signer/CertificateRevocationListSignerTest.kt index e180c3e429..2f6777bac3 100644 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/signer/CertificateRevocationListSignerTest.kt +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/signer/CertificateRevocationListSignerTest.kt @@ -62,6 +62,8 @@ class CertificateRevocationListSignerTest : TestBase() { private fun givenCertificateRevocationRequest(status: RequestStatus): CertificateRevocationRequestData { return CertificateRevocationRequestData( SecureHash.randomSHA256().toString(), + "CSR-ID-1", + mock(), BigInteger.valueOf(random63BitValue()), Instant.now(), CordaX500Name.parse("CN=Bank A, O=$status, L=London, C=GB"), diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/JiraClientTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/CsrJiraClientTest.kt similarity index 85% rename from network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/JiraClientTest.kt rename to network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/CsrJiraClientTest.kt index 3ae3e9ee36..3f97bdb3c3 100644 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/JiraClientTest.kt +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/CsrJiraClientTest.kt @@ -23,18 +23,18 @@ import javax.security.auth.x500.X500Principal @Ignore // This is manual test for testing Jira API. -class JiraClientTest { - private lateinit var jiraClient: JiraClient +class CsrJiraClientTest { + private lateinit var jiraClient: CsrJiraClient @Before fun init() { val jiraWebAPI = AsynchronousJiraRestClientFactory().createWithBasicHttpAuthentication(URI("http://jira.url.com"), "username", "password") - jiraClient = JiraClient(jiraWebAPI, "DOOR") + jiraClient = CsrJiraClient(jiraWebAPI, "DOOR") } @Test fun createRequestTicket() { val request = X509Utilities.createCertificateSigningRequest(CordaX500Name("JiraAPITest", "R3 Ltd 3", "London", "GB").x500Principal, "test@test.com", Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)) - jiraClient.createRequestTicket(SecureHash.randomSHA256().toString(), request) + jiraClient.createCertificateSigningRequestTicket(SecureHash.randomSHA256().toString(), request) } @Test @@ -54,7 +54,7 @@ class JiraClientTest { val selfSignedCaCertPath = X509Utilities.buildCertPath(X509Utilities.createSelfSignedCACertificate( X500Principal("O=test,L=london,C=GB"), Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME))) - jiraClient.updateSignedRequests(requests.associateBy({ it.requestId }, { selfSignedCaCertPath })) + jiraClient.updateDoneCertificateSigningRequests(requests.associateBy({ it.requestId }, { selfSignedCaCertPath })) } @Test diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/signer/JiraCsrHandlerTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/signer/JiraCsrHandlerTest.kt index c9f118544c..c56b4a4405 100644 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/signer/JiraCsrHandlerTest.kt +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/signer/JiraCsrHandlerTest.kt @@ -14,14 +14,12 @@ import com.nhaarman.mockito_kotlin.* import com.r3.corda.networkmanage.TestBase import com.r3.corda.networkmanage.common.persistence.* import com.r3.corda.networkmanage.doorman.ApprovedRequest -import com.r3.corda.networkmanage.doorman.JiraClient +import com.r3.corda.networkmanage.doorman.CsrJiraClient import com.r3.corda.networkmanage.doorman.RejectedRequest import net.corda.core.crypto.Crypto import net.corda.core.crypto.SecureHash import net.corda.core.identity.CordaX500Name import net.corda.nodeapi.internal.crypto.X509Utilities -import net.corda.testing.core.ALICE_NAME -import net.corda.testing.core.BOB_NAME import org.junit.Before import org.junit.Rule import org.junit.Test @@ -37,7 +35,7 @@ class JiraCsrHandlerTest : TestBase() { val mockitoRule: MockitoRule = MockitoJUnit.rule() @Mock - private lateinit var jiraClient: JiraClient + private lateinit var jiraClient: CsrJiraClient @Mock private lateinit var certificationRequestStorage: CertificateSigningRequestStorage @@ -68,7 +66,7 @@ class JiraCsrHandlerTest : TestBase() { fun `If jira connection fails we don't mark the ticket as created`() { whenever(defaultCsrHandler.saveRequest(any())).thenReturn(requestId) whenever(defaultCsrHandler.getResponse(requestId)).thenReturn(certificateResponse) - whenever(jiraClient.createRequestTicket(eq(requestId), any())).thenThrow(IllegalStateException("something broke")) + whenever(jiraClient.createCertificateSigningRequestTicket(eq(requestId), any())).thenThrow(IllegalStateException("something broke")) // Test jiraCsrHandler.saveRequest(pkcS10CertificationRequest) @@ -91,7 +89,7 @@ class JiraCsrHandlerTest : TestBase() { fun `create tickets`() { val csr = certificateSigningRequest( requestId = requestId, - legalName = ALICE_NAME, + legalName = CordaX500Name.parse("O=Test Org., C=GB, L=London"), status = RequestStatus.NEW, request = pkcS10CertificationRequest) whenever(certificationRequestStorage.getRequests(RequestStatus.NEW)).thenReturn(listOf(csr)) @@ -99,7 +97,7 @@ class JiraCsrHandlerTest : TestBase() { // Test jiraCsrHandler.processRequests() - verify(jiraClient).createRequestTicket(requestId, csr.request) + verify(jiraClient).createCertificateSigningRequestTicket(requestId, csr.request) verify(certificationRequestStorage).markRequestTicketCreated(requestId) } @@ -107,8 +105,8 @@ class JiraCsrHandlerTest : TestBase() { fun `sync tickets status`() { val id1 = SecureHash.randomSHA256().toString() val id2 = SecureHash.randomSHA256().toString() - val csr1 = CertificateSigningRequest(id1, ALICE_NAME, SecureHash.randomSHA256(), RequestStatus.NEW, pkcS10CertificationRequest, null, "Test", null) - val csr2 = CertificateSigningRequest(id2, BOB_NAME, SecureHash.randomSHA256(), RequestStatus.NEW, pkcS10CertificationRequest, null, "Test", null) + val csr1 = CertificateSigningRequest(id1, CordaX500Name.parse("O=Test1 Org., C=GB, L=London"), SecureHash.randomSHA256(), RequestStatus.NEW, pkcS10CertificationRequest, null, "Test", null) + val csr2 = CertificateSigningRequest(id2, CordaX500Name.parse("O=Test2 Org., C=GB, L=London"), SecureHash.randomSHA256(), RequestStatus.NEW, pkcS10CertificationRequest, null, "Test", null) val requests = mutableMapOf(id1 to csr1, id2 to csr2) @@ -135,8 +133,8 @@ class JiraCsrHandlerTest : TestBase() { // Test. jiraCsrHandler.processRequests() - verify(jiraClient).createRequestTicket(id1, csr1.request) - verify(jiraClient).createRequestTicket(id2, csr2.request) + verify(jiraClient).createCertificateSigningRequestTicket(id1, csr1.request) + verify(jiraClient).createCertificateSigningRequestTicket(id2, csr2.request) verify(certificationRequestStorage).markRequestTicketCreated(id1) verify(certificationRequestStorage).markRequestTicketCreated(id2) @@ -147,7 +145,7 @@ class JiraCsrHandlerTest : TestBase() { // Verify jira client get the correct call. verify(jiraClient).updateRejectedRequests(listOf(id2)) - verify(jiraClient).updateSignedRequests(emptyMap()) + verify(jiraClient).updateDoneCertificateSigningRequests(emptyMap()) // Sign request 1 val certPath = mock() @@ -158,6 +156,6 @@ class JiraCsrHandlerTest : TestBase() { jiraCsrHandler.processRequests() // Update signed request should be called. - verify(jiraClient).updateSignedRequests(mapOf(id1 to certPath)) + verify(jiraClient).updateDoneCertificateSigningRequests(mapOf(id1 to certPath)) } } diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/sockets/CertificateRevocationSocketServerTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/sockets/CertificateRevocationSocketServerTest.kt index 1945f0a177..67122880d9 100644 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/sockets/CertificateRevocationSocketServerTest.kt +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/sockets/CertificateRevocationSocketServerTest.kt @@ -11,6 +11,7 @@ import com.r3.corda.networkmanage.common.utils.writeObject import net.corda.core.crypto.Crypto import net.corda.core.identity.CordaX500Name import net.corda.core.internal.x500Name +import net.corda.nodeapi.internal.createDevNodeCa import net.corda.nodeapi.internal.crypto.ContentSignerBuilder import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.freePort @@ -117,7 +118,9 @@ class CertificateRevocationSocketServerTest { certificateSerialNumber = BigInteger.TEN, status = status, reason = CRLReason.KEY_COMPROMISE, - modifiedAt = Instant.now() + modifiedAt = Instant.now(), + certificateSigningRequestId = "CSR-ID", + certificate = createDevNodeCa(DEV_INTERMEDIATE_CA, CordaX500Name.parse("O=R3Cev, L=London, C=GB")).certificate ) } } \ No newline at end of file