Adding CRL signing (#528)

* Adding CRL signing

* Adding CRL

* Addressing review comments

* Address review comments
This commit is contained in:
Michal Kit 2018-03-16 11:48:41 +00:00 committed by GitHub
parent c2b7755824
commit 6d99f7e5d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 693 additions and 161 deletions

View File

@ -4,6 +4,8 @@ keySpecifier = -1
doorman {
crlDistributionPoint = "http://test.com/revoked.crl"
compatibilityZoneURL = "http://test.com/api"
crlUpdatePeriod = 200000
validDays = 3650
rootKeyStoreFile = "dummyfile.jks"
rootKeyStorePassword = "trustpass"

View File

@ -28,6 +28,7 @@ import net.corda.nodeapi.internal.crypto.CertificateType
import org.junit.Before
import org.junit.Rule
import org.junit.rules.TemporaryFolder
import java.net.URL
import java.nio.file.Path
import java.util.*
import com.r3.corda.networkmanage.hsm.authentication.AuthMode as SigningServiceAuthMode
@ -127,35 +128,46 @@ abstract class HsmBaseTest {
), hsmUserConfigs)
}
protected fun createHsmSigningServiceConfig(): SigningServiceConfig {
protected fun createHsmSigningServiceConfig(doormanCertConfig: DoormanCertificateConfig?,
networkMapCertificateConfig: NetworkMapCertificateConfig?): SigningServiceConfig {
return SigningServiceConfig(
dataSourceProperties = mock(),
device = "${hsmSimulator.port}@${hsmSimulator.host}",
keySpecifier = 1,
doorman = DoormanCertificateConfig(
rootKeyStoreFile = rootKeyStoreFile,
keyGroup = DOORMAN_CERT_KEY_GROUP,
validDays = 3650,
rootKeyStorePassword = TRUSTSTORE_PASSWORD,
crlDistributionPoint = "http://test.com/revoked.crl",
authParameters = AuthParametersConfig(
mode = SigningServiceAuthMode.PASSWORD,
threshold = 2
)
),
networkMap = NetworkMapCertificateConfig(
username = "INTEGRATION_TEST",
keyGroup = NETWORK_MAP_CERT_KEY_GROUP,
authParameters = AuthParametersConfig(
mode = SigningServiceAuthMode.PASSWORD,
password = "INTEGRATION_TEST",
threshold = 2
)
doorman = doormanCertConfig,
networkMap = networkMapCertificateConfig
)
}
protected fun createDoormanCertificateConfig(): DoormanCertificateConfig {
return DoormanCertificateConfig(
rootKeyStoreFile = rootKeyStoreFile,
keyGroup = DOORMAN_CERT_KEY_GROUP,
validDays = 3650,
rootKeyStorePassword = TRUSTSTORE_PASSWORD,
crlDistributionPoint = URL("http://test.com/revoked.crl"),
compatibilityZoneURL = URL("http://test.com/api"),
crlUpdatePeriod = 1000,
authParameters = AuthParametersConfig(
mode = SigningServiceAuthMode.PASSWORD,
threshold = 2
)
)
}
protected fun createNetworkMapCertificateConfig(): NetworkMapCertificateConfig {
return NetworkMapCertificateConfig(
username = "INTEGRATION_TEST",
keyGroup = NETWORK_MAP_CERT_KEY_GROUP,
authParameters = AuthParametersConfig(
mode = SigningServiceAuthMode.PASSWORD,
password = "INTEGRATION_TEST",
threshold = 2
)
)
}
protected fun givenHsmUserAuthenticationInput(username: String = HSM_USERNAME,
password: String = HSM_PASSWORD): InputReader {
val inputReader = mock<InputReader>()

View File

@ -23,7 +23,7 @@ class HsmAuthenticatorTest : HsmBaseTest() {
fun `Authenticator executes the block once user is successfully authenticated`() {
// given
val userInput = givenHsmUserAuthenticationInput()
val hsmSigningServiceConfig = createHsmSigningServiceConfig()
val hsmSigningServiceConfig = createHsmSigningServiceConfig(createDoormanCertificateConfig(), null)
val doormanCertificateConfig = hsmSigningServiceConfig.doorman!!
val authenticator = Authenticator(provider = createProvider(
doormanCertificateConfig.keyGroup,

View File

@ -56,7 +56,7 @@ class HsmPermissionTest : HsmBaseTest() {
val userInput = givenHsmUserAuthenticationInput(HSM_USERNAME_OPS_CERT_)
// given HSM CSR signer
val hsmSigningServiceConfig = createHsmSigningServiceConfig()
val hsmSigningServiceConfig = createHsmSigningServiceConfig(createDoormanCertificateConfig(), null)
val signer = HsmCsrSigner(
mock(),
hsmSigningServiceConfig.doorman!!.loadRootKeyStore(),
@ -112,7 +112,7 @@ class HsmPermissionTest : HsmBaseTest() {
val userInput = givenHsmUserAuthenticationInput(HSM_USERNAME_OPS_CERT)
// given HSM CSR signer
val hsmSigningServiceConfig = createHsmSigningServiceConfig()
val hsmSigningServiceConfig = createHsmSigningServiceConfig(createDoormanCertificateConfig(), null)
val signer = HsmCsrSigner(
mock(),
hsmSigningServiceConfig.doorman!!.loadRootKeyStore(),

View File

@ -15,6 +15,7 @@ import com.r3.corda.networkmanage.common.HsmBaseTest
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.CORDA_NETWORK_MAP
import com.r3.corda.networkmanage.common.utils.initialiseSerialization
import com.r3.corda.networkmanage.hsm.authentication.Authenticator
import com.r3.corda.networkmanage.hsm.authentication.createProvider
@ -54,7 +55,7 @@ class HsmSigningServiceTest : HsmBaseTest() {
val userInput = givenHsmUserAuthenticationInput()
// given HSM CSR signer
val hsmSigningServiceConfig = createHsmSigningServiceConfig()
val hsmSigningServiceConfig = createHsmSigningServiceConfig(createDoormanCertificateConfig(), null)
val doormanCertificateConfig = hsmSigningServiceConfig.doorman!!
val signer = HsmCsrSigner(
mock(),
@ -98,7 +99,7 @@ class HsmSigningServiceTest : HsmBaseTest() {
val userInput = givenHsmUserAuthenticationInput()
// given HSM CSR signer
val hsmSigningServiceConfig = createHsmSigningServiceConfig()
val hsmSigningServiceConfig = createHsmSigningServiceConfig(createDoormanCertificateConfig(), null)
val doormanCertificateConfig = hsmSigningServiceConfig.doorman!!
val signer = HsmCsrSigner(
mock(),
@ -143,14 +144,15 @@ class HsmSigningServiceTest : HsmBaseTest() {
val userInput = givenHsmUserAuthenticationInput()
// given HSM network map signer
val hsmSigningServiceConfig = createHsmSigningServiceConfig()
val hsmSigningServiceConfig = createHsmSigningServiceConfig(null, createNetworkMapCertificateConfig())
val networkMapCertificateConfig = hsmSigningServiceConfig.networkMap!!
val hsmDataSigner = HsmSigner(Authenticator(
provider = createProvider(
networkMapCertificateConfig.keyGroup,
hsmSigningServiceConfig.keySpecifier,
hsmSigningServiceConfig.device),
inputReader = userInput))
inputReader = userInput),
keyName = CORDA_NETWORK_MAP)
val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true))
val networkMapStorage = PersistentNetworkMapStorage(database)
@ -170,7 +172,7 @@ class HsmSigningServiceTest : HsmBaseTest() {
assertThat(persistedNetworkMap.nodeInfoHashes).isEmpty()
}
private fun setupCertificates(){
private fun setupCertificates() {
// when root cert is created
run(createGeneratorParameters(
keyGroup = ROOT_CERT_KEY_GROUP,

View File

@ -18,7 +18,7 @@ import com.r3.corda.networkmanage.doorman.DoormanConfig
import com.r3.corda.networkmanage.doorman.NetworkManagementServer
import com.r3.corda.networkmanage.hsm.persistence.ApprovedCertificateRequestData
import com.r3.corda.networkmanage.hsm.persistence.DBSignedCertificateRequestStorage
import com.r3.corda.networkmanage.hsm.persistence.SignedCertificateRequestStorage
import com.r3.corda.networkmanage.hsm.persistence.SignedCertificateSigningRequestStorage
import com.r3.corda.networkmanage.hsm.signer.HsmCsrSigner
import net.corda.core.crypto.random63BitValue
import net.corda.core.identity.CordaX500Name
@ -77,7 +77,7 @@ class SigningServiceIntegrationTest : HsmBaseTest() {
timer.cancel()
}
private fun givenSignerSigningAllRequests(storage: SignedCertificateRequestStorage): HsmCsrSigner {
private fun givenSignerSigningAllRequests(storage: SignedCertificateSigningRequestStorage): HsmCsrSigner {
// Mock signing logic but keep certificate persistence
return mock {
on { sign(any()) }.then {

View File

@ -13,7 +13,7 @@ interface CertificateRevocationListStorage {
* @param crlIssuer CRL issuer CA type.
* @return latest revocation list.
*/
fun getCertificateRevocationList(crlIssuer: CrlIssuer): X509CRL
fun getCertificateRevocationList(crlIssuer: CrlIssuer): X509CRL?
/**
* Persists a new revocation list. Upon saving, statuses

View File

@ -1,13 +1,20 @@
package com.r3.corda.networkmanage.common.persistence
import net.corda.core.identity.CordaX500Name
import net.corda.core.serialization.CordaSerializable
import net.corda.nodeapi.internal.network.CertificateRevocationRequest
import java.math.BigInteger
import java.security.cert.CRLReason
import java.time.Instant
/**
* This data class is intended to be used internally certificate revocation request service.
*/
@CordaSerializable
data class CertificateRevocationRequestData(val requestId: String, // This is a uniquely generated string
val certificateSerialNumber: BigInteger,
val revocationTime: Instant?,
val legalName: String,
val modifiedAt: Instant,
val legalName: CordaX500Name,
val status: RequestStatus,
val reason: CRLReason,
val reporter: String) // Username of the reporter
@ -24,13 +31,11 @@ interface CertificateRevocationRequestStorage {
* [RequestStatus.NEW], [RequestStatus.APPROVED] or [RequestStatus.REVOKED]
* then nothing is persisted and the existing revocation request identifier is returned.
*
* @param certificateSerialNumber serial number of the certificate to be revoked.
* @param reason reason for revocation. See [java.security.cert.CRLReason]
* @param reporter who is requesting this revocation
* @param request certificate revocation request to be stored.
*
* @return identifier of the newly created (or existing) revocation request.
*/
fun saveRevocationRequest(certificateSerialNumber: BigInteger, reason: CRLReason, reporter: String): String
fun saveRevocationRequest(request: CertificateRevocationRequest): String
/**
* Retrieves the revocation request with the given [requestId]

View File

@ -11,6 +11,7 @@
package com.r3.corda.networkmanage.common.persistence
import net.corda.core.crypto.SecureHash
import net.corda.core.serialization.CordaSerializable
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import java.security.cert.CertPath
@ -89,6 +90,7 @@ sealed class CertificateResponse {
data class Unauthorised(val message: String) : CertificateResponse()
}
@CordaSerializable
enum class RequestStatus {
/**
* The request has been received, this is the initial state in which a request has been created.

View File

@ -11,7 +11,7 @@ import java.security.cert.X509CRL
import java.time.Instant
class PersistentCertificateRevocationListStorage(private val database: CordaPersistence) : CertificateRevocationListStorage {
override fun getCertificateRevocationList(crlIssuer: CrlIssuer): X509CRL {
override fun getCertificateRevocationList(crlIssuer: CrlIssuer): X509CRL? {
return database.transaction {
val builder = session.criteriaBuilder
val query = builder.createQuery(CertificateRevocationListEntity::class.java).run {
@ -21,7 +21,10 @@ class PersistentCertificateRevocationListStorage(private val database: CordaPers
}
}
// We just want the last signed entry
X509CertificateFactory().delegate.generateCRL(session.createQuery(query).setMaxResults(1).singleResult.crlBytes.inputStream()) as X509CRL
val crlEntity = session.createQuery(query).setMaxResults(1).uniqueResult()
crlEntity?.let {
X509CertificateFactory().delegate.generateCRL(crlEntity.crlBytes.inputStream()) as X509CRL
}
}
}
@ -41,7 +44,10 @@ class PersistentCertificateRevocationListStorage(private val database: CordaPers
private fun revokeCertificate(certificateSerialNumber: BigInteger, time: Instant, transaction: DatabaseTransaction) {
val revocation = transaction.uniqueEntityWhere<CertificateRevocationRequestEntity> { builder, path ->
builder.equal(path.get<BigInteger>(CertificateRevocationRequestEntity::certificateSerialNumber.name), certificateSerialNumber)
builder.and(
builder.equal(path.get<BigInteger>(CertificateRevocationRequestEntity::certificateSerialNumber.name), certificateSerialNumber),
builder.notEqual(path.get<RequestStatus>(CertificateRevocationRequestEntity::status.name), RequestStatus.REJECTED)
)
}
revocation ?: throw IllegalStateException("The certificate revocation request for $certificateSerialNumber does not exist")
check(revocation.status in arrayOf(RequestStatus.APPROVED, RequestStatus.DONE)) {

View File

@ -2,39 +2,41 @@ package com.r3.corda.networkmanage.common.persistence
import com.r3.corda.networkmanage.common.persistence.entity.CertificateDataEntity
import com.r3.corda.networkmanage.common.persistence.entity.CertificateRevocationRequestEntity
import com.r3.corda.networkmanage.common.persistence.entity.CertificateSigningRequestEntity
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name
import net.corda.nodeapi.internal.network.CertificateRevocationRequest
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.CRLReason
import java.time.Instant
class PersistentCertificateRevocationRequestStorage(private val database: CordaPersistence) : CertificateRevocationRequestStorage {
override fun saveRevocationRequest(certificateSerialNumber: BigInteger, reason: CRLReason, reporter: String): String {
override fun saveRevocationRequest(request: CertificateRevocationRequest): String {
return database.transaction(TransactionIsolationLevel.SERIALIZABLE) {
// Get matching CSR
val csr = retrieveCsr(request.certificateSerialNumber, request.csrRequestId, request.legalName)
csr ?: throw IllegalArgumentException("No CSR matching the given criteria was found")
// Check if there is an entry for the given certificate serial number
val revocation = uniqueEntityWhere<CertificateRevocationRequestEntity> { builder, path ->
val serialNumberEqual = builder.equal(path.get<BigInteger>(CertificateRevocationRequestEntity::certificateSerialNumber.name), certificateSerialNumber)
val serialNumberEqual = builder.equal(path.get<BigInteger>(CertificateRevocationRequestEntity::certificateSerialNumber.name), request.certificateSerialNumber)
val statusNotEqualRejected = builder.notEqual(path.get<RequestStatus>(CertificateRevocationRequestEntity::status.name), RequestStatus.REJECTED)
builder.and(serialNumberEqual, statusNotEqualRejected)
}
if (revocation != null) {
revocation.requestId
} else {
val certificateData = uniqueEntityWhere<CertificateDataEntity> { builder, path ->
val serialNumberEqual = builder.equal(path.get<BigInteger>(CertificateDataEntity::certificateSerialNumber.name), certificateSerialNumber)
val statusEqualValid = builder.equal(path.get<CertificateStatus>(CertificateDataEntity::certificateStatus.name), CertificateStatus.VALID)
builder.and(serialNumberEqual, statusEqualValid)
}
val certificateData = csr.certificateData!!
requireNotNull(certificateData) { "The certificate with the given serial number cannot be found." }
val requestId = SecureHash.randomSHA256().toString()
session.save(CertificateRevocationRequestEntity(
certificateSerialNumber = certificateSerialNumber,
revocationReason = reason,
certificateSerialNumber = certificateData.certificateSerialNumber,
revocationReason = request.reason,
requestId = requestId,
modifiedBy = reporter,
certificateData = certificateData!!,
reporter = reporter,
modifiedBy = request.reporter,
certificateData = certificateData,
reporter = request.reporter,
legalName = certificateData.legalName()
))
requestId
@ -42,8 +44,62 @@ class PersistentCertificateRevocationRequestStorage(private val database: CordaP
}
}
override fun getRevocationRequest(requestId: String): CertificateRevocationRequestData? = database.transaction {
getRevocationRequestEntity(requestId)?.toCertificateRevocationRequest()
private fun DatabaseTransaction.retrieveCsr(certificateSerialNumber: BigInteger?, csrRequestId: String?, legalName: CordaX500Name?): CertificateSigningRequestEntity? {
val csr = if (csrRequestId != null) {
uniqueEntityWhere<CertificateSigningRequestEntity> { builder, path ->
builder.and(
builder.equal(path.get<String>(CertificateSigningRequestEntity::requestId.name), csrRequestId),
builder.notEqual(path.get<RequestStatus>(CertificateSigningRequestEntity::status.name), RequestStatus.REJECTED),
builder.equal(path
.get<CertificateDataEntity>(CertificateSigningRequestEntity::certificateData.name)
.get<CertificateStatus>(CertificateDataEntity::certificateStatus.name), CertificateStatus.VALID)
)
}
} else if (legalName != null) {
uniqueEntityWhere<CertificateSigningRequestEntity> { builder, path ->
builder.and(
builder.equal(path.get<String>(CertificateSigningRequestEntity::legalName.name), legalName.toString()),
builder.notEqual(path.get<RequestStatus>(CertificateSigningRequestEntity::status.name), RequestStatus.REJECTED),
builder.equal(path
.get<CertificateDataEntity>(CertificateSigningRequestEntity::certificateData.name)
.get<CertificateStatus>(CertificateDataEntity::certificateStatus.name), CertificateStatus.VALID)
)
}
} else {
val certificateData = uniqueEntityWhere<CertificateDataEntity> { builder, path ->
builder.and(
builder.equal(path.get<CertificateStatus>(CertificateDataEntity::certificateStatus.name), CertificateStatus.VALID),
builder.equal(path.get<String>(CertificateDataEntity::certificateSerialNumber.name), certificateSerialNumber)
)
}
certificateData?.certificateSigningRequest
}
return csr?.let {
validateCsrConsistency(certificateSerialNumber, csrRequestId, legalName, it)
}
}
private fun validateCsrConsistency(certificateSerialNumber: BigInteger?,
csrRequestId: String?,
legalName: CordaX500Name?,
result: CertificateSigningRequestEntity): CertificateSigningRequestEntity {
val certData = result.certificateData!!
require(legalName == null || result.legalName == legalName.toString()) {
"The legal name does not match."
}
require(csrRequestId == null || result.requestId == csrRequestId) {
"The CSR request ID does not match."
}
require(certificateSerialNumber == null || certData.certificateSerialNumber == certificateSerialNumber) {
"The certificate serial number does not match."
}
return result
}
override fun getRevocationRequest(requestId: String): CertificateRevocationRequestData? {
return database.transaction {
getRevocationRequestEntity(requestId)?.toCertificateRevocationRequest()
}
}
override fun getRevocationRequests(revocationStatus: RequestStatus): List<CertificateRevocationRequestData> {
@ -89,9 +145,11 @@ class PersistentCertificateRevocationRequestStorage(private val database: CordaP
}
}
private fun getRevocationRequestEntity(requestId: String): CertificateRevocationRequestEntity? = database.transaction {
uniqueEntityWhere { builder, path ->
builder.equal(path.get<String>(CertificateRevocationRequestEntity::requestId.name), requestId)
private fun getRevocationRequestEntity(requestId: String): CertificateRevocationRequestEntity? {
return database.transaction {
uniqueEntityWhere { builder, path ->
builder.equal(path.get<String>(CertificateRevocationRequestEntity::requestId.name), requestId)
}
}
}
@ -99,8 +157,8 @@ class PersistentCertificateRevocationRequestStorage(private val database: CordaP
return CertificateRevocationRequestData(
requestId,
certificateSerialNumber,
if (status == RequestStatus.DONE) modifiedAt else null,
legalName,
modifiedAt,
CordaX500Name.parse(legalName),
status,
revocationReason,
reporter)

View File

@ -1,7 +1,6 @@
package com.r3.corda.networkmanage.common.persistence.entity
import com.r3.corda.networkmanage.common.persistence.CrlIssuer
import org.hibernate.envers.Audited
import java.time.Instant
import javax.persistence.*
@ -19,11 +18,9 @@ class CertificateRevocationListEntity(
@Column(name = "crl_bytes", nullable = false)
val crlBytes: ByteArray,
@Audited
@Column(name = "signed_by", length = 512)
val signedBy: String,
@Audited
@Column(name = "modified_at", nullable = false)
val modifiedAt: Instant = Instant.now()
)

View File

@ -0,0 +1,57 @@
package com.r3.corda.networkmanage.common.signer
import com.r3.corda.networkmanage.common.persistence.CertificateRevocationListStorage
import com.r3.corda.networkmanage.common.persistence.CertificateRevocationRequestData
import com.r3.corda.networkmanage.common.persistence.CrlIssuer
import com.r3.corda.networkmanage.common.persistence.RequestStatus
import com.r3.corda.networkmanage.common.utils.createSignedCrl
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.core.utilities.trace
import java.net.URL
import java.security.cert.X509CRL
import java.security.cert.X509Certificate
import java.time.Duration
import java.time.Instant
class CertificateRevocationListSigner(
private val revocationListStorage: CertificateRevocationListStorage,
private val issuerCertificate: X509Certificate,
private val updateInterval: Duration,
private val endpoint: URL,
private val signer: Signer) {
private companion object {
private val logger = contextLogger()
}
/**
* Builds, signs and persists a new certificate revocation list.
* It happens only if there are new entries to be added to the current list.
*
* @param newCRRs list of approved certificate revocation requests. An approved certificate revocation request
* is a request that has status [RequestStatus.APPROVED] - i.e. it has not yet been included in any CRL.
* @param existingCRRs list of revoked certificate revocation requests. A revoked certificate revocation request
* is a request that has status [RequestStatus.DONE] - i.e. it has already been included in another CRL.
* @param signedBy who signs this CRL.
*
* @return A new signed CRL containing both revoked and approved certificate revocation requests.
*/
fun createSignedCRL(newCRRs: List<CertificateRevocationRequestData>,
existingCRRs: List<CertificateRevocationRequestData>,
signedBy: String): X509CRL {
require(existingCRRs.all { it.status == RequestStatus.DONE }) { "All existing CRRs need to be in the ${RequestStatus.DONE} state" }
require(newCRRs.all { it.status == RequestStatus.APPROVED }) { "All newly included CRRs need to be in the ${RequestStatus.APPROVED} state" }
logger.info("Signing a new Certificate Revocation List...")
logger.debug("Retrieving approved Certificate Revocation Requests...")
val revocationTime = Instant.now()
val approvedWithTimestamp = newCRRs.map { it.copy(modifiedAt = revocationTime) }
logger.trace { "Approved Certificate Revocation Requests to be included in the new Certificate Revocation List: $approvedWithTimestamp" }
logger.debug("Retrieving revoked Certificate Revocation Requests...")
logger.trace { "Revoked Certificate Revocation Requests to be included in the new Certificate Revocation List: $existingCRRs" }
val crl = createSignedCrl(issuerCertificate, endpoint, updateInterval, signer, existingCRRs + approvedWithTimestamp)
logger.debug { "Created a new Certificate Revocation List $crl" }
revocationListStorage.saveCertificateRevocationList(crl, CrlIssuer.DOORMAN, signedBy, revocationTime)
logger.info("A new Certificate Revocation List has been persisted.")
return crl
}
}

View File

@ -0,0 +1,48 @@
package com.r3.corda.networkmanage.common.utils
import com.r3.corda.networkmanage.common.persistence.CertificateRevocationRequestData
import com.r3.corda.networkmanage.common.signer.Signer
import net.corda.nodeapi.internal.crypto.X509Utilities
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.*
import org.bouncycastle.cert.X509v2CRLBuilder
import org.bouncycastle.cert.jcajce.JcaX509CRLConverter
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.operator.ContentSigner
import java.io.ByteArrayOutputStream
import java.io.OutputStream
import java.net.URL
import java.security.cert.X509CRL
import java.security.cert.X509Certificate
import java.time.Duration
import java.time.Instant
import java.util.*
fun createSignedCrl(issuerCertificate: X509Certificate,
endpointUrl: URL,
nextUpdateInterval: Duration,
signer: Signer,
includeInCrl: List<CertificateRevocationRequestData>): X509CRL {
val extensionUtils = JcaX509ExtensionUtils()
val builder = X509v2CRLBuilder(X500Name.getInstance(issuerCertificate.issuerX500Principal.encoded), Date())
builder.addExtension(Extension.authorityKeyIdentifier, false, extensionUtils.createAuthorityKeyIdentifier(issuerCertificate))
val issuingDistributionPointName = GeneralName(GeneralName.uniformResourceIdentifier, endpointUrl.toString())
val issuingDistributionPoint = IssuingDistributionPoint(DistributionPointName(GeneralNames(issuingDistributionPointName)), false, false)
builder.addExtension(Extension.issuingDistributionPoint, true, issuingDistributionPoint)
builder.setNextUpdate(Date((Instant.now() + nextUpdateInterval).toEpochMilli()))
includeInCrl.forEach {
builder.addCRLEntry(it.certificateSerialNumber, Date(it.modifiedAt.toEpochMilli()), it.reason.ordinal)
}
val crlHolder = builder.build(CrlContentSigner(signer))
return JcaX509CRLConverter().setProvider(BouncyCastleProvider()).getCRL(crlHolder)
}
private class CrlContentSigner(private val signer: Signer) : ContentSigner {
private val outputStream = ByteArrayOutputStream()
override fun getAlgorithmIdentifier(): AlgorithmIdentifier = X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME.signatureOID
override fun getOutputStream(): OutputStream = outputStream
override fun getSignature(): ByteArray = signer.signBytes(outputStream.toByteArray()).bytes
}

View File

@ -0,0 +1,62 @@
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.CertificateRevocationListStorage
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.util.concurrent.ExecutionException
import java.util.concurrent.TimeUnit
import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.Produces
import javax.ws.rs.core.Response
import javax.ws.rs.core.Response.ok
import javax.ws.rs.core.Response.status
@Path(CRL_PATH)
class CertificateRevocationListWebService(private val revocationListStorage: CertificateRevocationListStorage,
cacheTimeout: Long) {
companion object {
private val logger = contextLogger()
const val CRL_PATH = "certificate-revocation-list"
const val CRL_DATA_TYPE = "application/pkcs7-crl"
const val DOORMAN = "doorman"
const val ROOT = "root"
}
private val crlCache: LoadingCache<CrlIssuer, ByteArray> = Caffeine.newBuilder()
.expireAfterWrite(cacheTimeout, TimeUnit.MILLISECONDS)
.build({ key ->
revocationListStorage.getCertificateRevocationList(key)?.encoded
})
@GET
@Path(DOORMAN)
@Produces(CRL_DATA_TYPE)
fun getDoormanRevocationList(): Response {
return getCrlResponse(CrlIssuer.DOORMAN)
}
@GET
@Path(ROOT)
@Produces(CRL_DATA_TYPE)
fun getRootRevocationList(): Response {
return getCrlResponse(CrlIssuer.ROOT)
}
private fun getCrlResponse(issuer: CrlIssuer): Response {
return try {
val crlBytes = crlCache.get(issuer)
if (crlBytes != null) {
ok(crlBytes).build()
} else {
status(Response.Status.NOT_FOUND).build()
}
} catch (e: Exception) {
logger.error("Error when retrieving the ${issuer.name} crl.", e)
status(Response.Status.INTERNAL_SERVER_ERROR).build()
}
}
}

View File

@ -0,0 +1,31 @@
package com.r3.corda.networkmanage.doorman.webservice
import com.r3.corda.networkmanage.common.persistence.CertificateRevocationRequestStorage
import com.r3.corda.networkmanage.doorman.webservice.CertificateRevocationRequestWebService.Companion.CRR_PATH
import net.corda.core.serialization.deserialize
import net.corda.nodeapi.internal.network.CertificateRevocationRequest
import java.io.InputStream
import javax.ws.rs.Consumes
import javax.ws.rs.POST
import javax.ws.rs.Path
import javax.ws.rs.Produces
import javax.ws.rs.core.MediaType
import javax.ws.rs.core.Response
import javax.ws.rs.core.Response.ok
@Path(CRR_PATH)
class CertificateRevocationRequestWebService(private val revocationRequestStorage: CertificateRevocationRequestStorage) {
companion object {
const val CRR_PATH = "certificate-revocation-request"
}
@POST
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
@Produces(MediaType.TEXT_PLAIN)
fun submitRequest(input: InputStream): Response {
val request = input.readBytes().deserialize<CertificateRevocationRequest>()
val requestId = revocationRequestStorage.saveRevocationRequest(request)
return ok(requestId).build()
}
}

View File

@ -17,6 +17,7 @@ import com.r3.corda.networkmanage.common.utils.initialiseSerialization
import com.r3.corda.networkmanage.common.utils.parseConfig
import com.r3.corda.networkmanage.hsm.configuration.SigningServiceArgsParser
import com.r3.corda.networkmanage.hsm.configuration.SigningServiceConfig
import com.r3.corda.networkmanage.hsm.processor.CrrProcessor
import com.r3.corda.networkmanage.hsm.processor.CsrProcessor
import com.r3.corda.networkmanage.hsm.processor.NetworkMapProcessor
import org.apache.logging.log4j.LogManager
@ -54,12 +55,13 @@ fun main(args: Array<String>) {
}
initialiseSerialization()
// Create DB connection.
val persistence = configureDatabase(config.dataSourceProperties, config.database)
if (config.networkMap != null) {
NetworkMapProcessor(config.networkMap, config.device, config.keySpecifier, persistence).run()
} else {
CsrProcessor(config.doorman!!, config.device, config.keySpecifier, persistence).showMenu()
} else if (config.doorman != null) {
CsrProcessor(config.doorman, config.device, config.keySpecifier, persistence).showMenu()
} else if (config.doorman != null) {
CrrProcessor(config.doorman, config.device, config.keySpecifier, persistence).showMenu()
}
}

View File

@ -23,6 +23,7 @@ import net.corda.core.internal.div
import net.corda.nodeapi.internal.config.parseAs
import net.corda.nodeapi.internal.crypto.X509KeyStore
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import java.net.URL
import java.nio.file.Path
import java.nio.file.Paths
import java.util.*
@ -54,7 +55,9 @@ data class NetworkMapCertificateConfig(val username: String,
/**
* Certificate signing requests process specific parameters.
*/
data class DoormanCertificateConfig(val crlDistributionPoint: String,
data class DoormanCertificateConfig(val crlDistributionPoint: URL,
val compatibilityZoneURL: URL,
val crlUpdatePeriod: Long,
val keyGroup:String,
val validDays: Int,
val rootKeyStoreFile: Path,

View File

@ -19,7 +19,7 @@ import java.security.cert.CertPath
data class ApprovedCertificateRequestData(val requestId: String, val request: PKCS10CertificationRequest, var certPath: CertPath? = null)
class DBSignedCertificateRequestStorage(database: CordaPersistence) : SignedCertificateRequestStorage {
class DBSignedCertificateRequestStorage(database: CordaPersistence) : SignedCertificateSigningRequestStorage {
private val storage = PersistentCertificateSigningRequestStorage(database)

View File

@ -13,7 +13,7 @@ package com.r3.corda.networkmanage.hsm.persistence
/**
* Provides an API for storing signed CSRs (Certificate Signing Requests).
*/
interface SignedCertificateRequestStorage {
interface SignedCertificateSigningRequestStorage {
/**
* Returns all certificate signing requests that have been approved for signing.

View File

@ -0,0 +1,74 @@
package com.r3.corda.networkmanage.hsm.processor
import com.google.common.net.HostAndPort
import com.r3.corda.networkmanage.common.persistence.CrlIssuer
import com.r3.corda.networkmanage.common.persistence.PersistentCertificateRevocationListStorage
import com.r3.corda.networkmanage.common.persistence.PersistentCertificateRevocationRequestStorage
import com.r3.corda.networkmanage.common.persistence.RequestStatus
import com.r3.corda.networkmanage.common.signer.CertificateRevocationListSigner
import com.r3.corda.networkmanage.hsm.authentication.Authenticator
import com.r3.corda.networkmanage.hsm.authentication.createProvider
import com.r3.corda.networkmanage.hsm.configuration.DoormanCertificateConfig
import com.r3.corda.networkmanage.hsm.menu.Menu
import com.r3.corda.networkmanage.hsm.signer.HsmSigner
import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.getAndInitializeKeyStore
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_INTERMEDIATE_CA
import net.corda.nodeapi.internal.crypto.getX509Certificate
import net.corda.nodeapi.internal.persistence.CordaPersistence
import java.time.Duration
class CrrProcessor(private val config: DoormanCertificateConfig,
private val device: String,
private val keySpecifier: Int,
private val persistance: CordaPersistence) : Processor() {
private companion object {
private const val RESET = "\u001B[0m"
private const val BLACK = "\u001B[30m"
private const val RED = "\u001B[31m"
private const val GREEN = "\u001B[32m"
private const val YELLOW = "\u001B[33m"
private const val BLUE = "\u001B[34m"
private const val CYAN = "\u001B[36m"
private const val WHITE = "\u001B[37m"
}
private val auth = config.authParameters
fun showMenu() {
val authenticator = Authenticator(
provider = createProvider(config.keyGroup, keySpecifier, device),
mode = auth.mode,
authStrengthThreshold = auth.threshold)
Menu().withExceptionHandler(this::processError).setExitOption("2", "Quit").addItem("1", "View current and sign a new certificate revocation list", {
val crlStorage = PersistentCertificateRevocationListStorage(persistance)
val currentCrl = crlStorage.getCertificateRevocationList(CrlIssuer.DOORMAN)
printlnColor("Current CRL:")
printlnColor(currentCrl.toString(), YELLOW)
val crrStorage = PersistentCertificateRevocationRequestStorage(persistance)
val approvedRequests = crrStorage.getRevocationRequests(RequestStatus.APPROVED)
if (approvedRequests.isEmpty()) {
printlnColor("There are no approved Certificate Revocation Requests.", GREEN)
} else {
printlnColor("Following are the approved Certificate Revocation Requests which will be added to the CRL:")
approvedRequests.forEach {
printlnColor("Certificate DN: ${it.legalName}, Certificate serial number: ${it.certificateSerialNumber}", GREEN)
}
}
Menu().withExceptionHandler(this::processError).setExitOption("2", "Go back").
addItem("1", "Create and sign a new Certificate Revocation List", {
authenticator.connectAndAuthenticate { provider, signers ->
val keyStore = getAndInitializeKeyStore(provider)
val issuerCertificate = keyStore.getX509Certificate(CORDA_INTERMEDIATE_CA)
val crlSigner = CertificateRevocationListSigner(
revocationListStorage = crlStorage,
issuerCertificate = issuerCertificate,
updateInterval = Duration.ofMillis(config.crlUpdatePeriod),
endpoint = config.crlDistributionPoint,
signer = HsmSigner(provider = provider, keyName = CORDA_INTERMEDIATE_CA))
val currentRequests = crrStorage.getRevocationRequests(RequestStatus.DONE)
crlSigner.createSignedCRL(approvedRequests, currentRequests, signers.toString())
}
})
}).showMenu()
}
}

View File

@ -16,81 +16,84 @@ import com.r3.corda.networkmanage.hsm.configuration.DoormanCertificateConfig
import com.r3.corda.networkmanage.hsm.menu.Menu
import com.r3.corda.networkmanage.hsm.persistence.ApprovedCertificateRequestData
import com.r3.corda.networkmanage.hsm.persistence.DBSignedCertificateRequestStorage
import com.r3.corda.networkmanage.hsm.persistence.SignedCertificateSigningRequestStorage
import com.r3.corda.networkmanage.hsm.signer.HsmCsrSigner
import com.r3.corda.networkmanage.hsm.utils.mapCryptoServerException
import net.corda.core.utilities.contextLogger
import net.corda.nodeapi.internal.persistence.CordaPersistence
class CsrProcessor(private val config: DoormanCertificateConfig,
private val device: String,
private val keySpecifier: Int,
private val database: CordaPersistence) {
companion object {
val logger = contextLogger()
private val database: CordaPersistence) : Processor() {
private companion object {
private val logger = contextLogger()
}
private val auth = config.authParameters
fun showMenu() {
val csrStorage = DBSignedCertificateRequestStorage(database)
val sign: (List<ApprovedCertificateRequestData>) -> Unit = {
val signer = config.run {
val authenticator = Authenticator(
provider = createProvider(config.keyGroup, keySpecifier, device),
mode = auth.mode,
authStrengthThreshold = auth.threshold)
val signCsr: (List<ApprovedCertificateRequestData>) -> Unit = {
val csrSigner = config.run {
HsmCsrSigner(
csrStorage,
loadRootKeyStore(),
crlDistributionPoint,
crlDistributionPoint.toString(),
null,
validDays,
Authenticator(
provider = createProvider(config.keyGroup, keySpecifier, device),
mode = auth.mode,
authStrengthThreshold = auth.threshold))
authenticator)
}
logger.debug("Signing requests: $it")
signer.sign(it)
csrSigner.sign(it)
}
Menu().withExceptionHandler(this::processError).setExitOption("3", "Quit").addItem("1", "Sign all approved and unsigned CSRs",
{
logger.debug("Fetching approved requests...")
val approved = csrStorage.getApprovedRequests()
logger.debug("Approved requests fetched: $approved")
if (approved.isNotEmpty()) {
if (confirmedSign(approved)) {
sign(approved)
}
} else {
println("There is no approved CSR")
}
}).addItem("2", "List all approved and unsigned CSRs",
{
logger.debug("Fetching approved requests...")
val approved = csrStorage.getApprovedRequests()
logger.debug("Approved requests fetched: $approved")
if (approved.isNotEmpty()) {
println("Approved CSRs:")
approved.forEachIndexed { index, item -> println("${index + 1}. ${item.request.subject}") }
Menu().withExceptionHandler(this::processError).setExitOption("3", "Go back").
addItem("1", "Sign all listed CSRs", {
if (confirmedSign(approved)) {
sign(approved)
}
}, isTerminating = true).
addItem("2", "Select and sign CSRs", {
val selectedItems = getSelection(approved)
if (confirmedSign(selectedItems)) {
sign(selectedItems)
}
}, isTerminating = true).showMenu()
} else {
println("There is no approved and unsigned CSR")
}
}).showMenu()
showMenu(csrStorage, signCsr)
}
private fun showMenu(csrStorage: SignedCertificateSigningRequestStorage, signCsr: (List<ApprovedCertificateRequestData>) -> Unit) {
Menu().withExceptionHandler(this::processError).setExitOption("3", "Quit").addItem("1", "Sign all approved and unsigned CSRs", {
logger.debug("Fetching approved requests...")
val approved = csrStorage.getApprovedRequests()
logger.debug("Approved requests fetched: $approved")
if (approved.isNotEmpty()) {
if (confirmedSign(approved)) {
signCsr(approved)
}
} else {
printlnColor("There are no approved CSRs")
}
}).addItem("2", "List all approved and unsigned CSRs", {
logger.debug("Fetching approved requests...")
val approved = csrStorage.getApprovedRequests()
logger.debug("Approved requests fetched: $approved")
if (approved.isNotEmpty()) {
printlnColor("Approved CSRs:")
approved.forEachIndexed { index, item -> printlnColor("${index + 1}. ${item.request.subject}") }
Menu().withExceptionHandler(this::processError).setExitOption("3", "Go back").
addItem("1", "Sign all listed CSRs", {
if (confirmedSign(approved)) {
signCsr(approved)
}
}, isTerminating = true).
addItem("2", "Select and sign CSRs", {
val selectedItems = getSelection(approved)
if (confirmedSign(selectedItems)) {
signCsr(selectedItems)
}
}, isTerminating = true).showMenu()
} else {
printlnColor("There is no approved and unsigned CSR")
}
}).showMenu()
}
private fun confirmedSign(selectedItems: List<ApprovedCertificateRequestData>): Boolean {
println("Are you sure you want to sign the following requests:")
printlnColor("Are you sure you want to sign the following requests:")
selectedItems.forEachIndexed { index, data ->
println("${index + 1} ${data.request.subject}")
printlnColor("${index + 1} ${data.request.subject}")
}
var result = false
Menu().addItem("Y", "Yes", { result = true }, true).setExitOption("N", "No").showMenu()
@ -101,7 +104,7 @@ class CsrProcessor(private val config: DoormanCertificateConfig,
print("CSRs to be signed (comma separated list): ")
val line = readLine()
if (line == null) {
println("EOF reached")
printlnColor("EOF reached")
return emptyList()
}
return try {
@ -114,14 +117,8 @@ class CsrProcessor(private val config: DoormanCertificateConfig,
}
}
} catch (exception: Exception) {
println(exception.message)
printlnColor(exception.message)
emptyList()
}
}
fun processError(exception: Exception) {
val processed = mapCryptoServerException(exception)
System.err.println("An error occurred:")
processed.printStackTrace()
}
}

View File

@ -12,6 +12,7 @@ package com.r3.corda.networkmanage.hsm.processor
import com.r3.corda.networkmanage.common.persistence.PersistentNetworkMapStorage
import com.r3.corda.networkmanage.common.signer.NetworkMapSigner
import com.r3.corda.networkmanage.common.utils.CORDA_NETWORK_MAP
import com.r3.corda.networkmanage.hsm.authentication.AuthMode
import com.r3.corda.networkmanage.hsm.authentication.Authenticator
import com.r3.corda.networkmanage.hsm.authentication.createProvider
@ -49,7 +50,8 @@ class NetworkMapProcessor(private val config: NetworkMapCertificateConfig,
authParameters.keyFilePath,
authParameters.password,
authParameters.threshold,
provider = createProvider(keyGroup, keySpecifier, device)))
provider = createProvider(keyGroup, keySpecifier, device)),
keyName = CORDA_NETWORK_MAP)
val networkMapSigner = NetworkMapSigner(networkMapStorage, signer)
try {
logger.info("Executing network map signing...")

View File

@ -0,0 +1,16 @@
package com.r3.corda.networkmanage.hsm.processor
import com.r3.corda.networkmanage.hsm.utils.mapCryptoServerException
abstract class Processor {
protected fun processError(exception: Exception) {
val processed = mapCryptoServerException(exception)
System.err.println("An error occurred:")
processed.printStackTrace()
}
protected fun printlnColor(line: String?, color: String = "") {
println(color + line)
}
}

View File

@ -13,13 +13,11 @@ package com.r3.corda.networkmanage.hsm.signer
import com.r3.corda.networkmanage.common.utils.getCertRole
import com.r3.corda.networkmanage.hsm.authentication.Authenticator
import com.r3.corda.networkmanage.hsm.persistence.ApprovedCertificateRequestData
import com.r3.corda.networkmanage.hsm.persistence.SignedCertificateRequestStorage
import com.r3.corda.networkmanage.hsm.persistence.SignedCertificateSigningRequestStorage
import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.createClientCertificate
import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.getAndInitializeKeyStore
import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.retrieveCertAndKeyPair
import net.corda.core.internal.CertRole
import net.corda.core.utilities.contextLogger
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509KeyStore
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_INTERMEDIATE_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
@ -31,7 +29,7 @@ import java.io.PrintStream
/**
* Encapsulates certificate signing logic
*/
class HsmCsrSigner(private val storage: SignedCertificateRequestStorage,
class HsmCsrSigner(private val storage: SignedCertificateSigningRequestStorage,
private val rootKeyStore: X509KeyStore,
private val csrCertCrlDistPoint: String,
private val csrCertCrlIssuer: String?,

View File

@ -10,8 +10,9 @@
package com.r3.corda.networkmanage.hsm.signer
import CryptoServerJCE.CryptoServerProvider
import com.google.common.primitives.Booleans
import com.r3.corda.networkmanage.common.signer.Signer
import com.r3.corda.networkmanage.common.utils.CORDA_NETWORK_MAP
import com.r3.corda.networkmanage.hsm.authentication.Authenticator
import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities
import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.getAndInitializeKeyStore
@ -22,26 +23,42 @@ import java.security.PrivateKey
import java.security.Signature
/**
* Signer which connects to a HSM using the given [authenticator] to sign bytes.
* Signer which connects to a HSM using the given [authenticator] and provider to sign bytes.
*/
class HsmSigner(private val authenticator: Authenticator) : Signer {
class HsmSigner(private val authenticator: Authenticator? = null,
private val provider: CryptoServerProvider? = null,
private val keyName: String) : 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 certificate = keyStore.getX509Certificate(CORDA_NETWORK_MAP)
// 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(CORDA_NETWORK_MAP, null) as PrivateKey
val signature = Signature.getInstance(HsmX509Utilities.SIGNATURE_ALGORITHM, provider).run {
initSign(key)
update(data)
sign()
}
verify(data, signature, certificate.publicKey)
DigitalSignatureWithCert(certificate, signature)
init {
require(Booleans.countTrue(authenticator != null, provider != null) == 1) {
"Either authenticator or provider needs to be non-null."
}
}
}
override fun signBytes(data: ByteArray): DigitalSignatureWithCert {
if (provider == null) {
return authenticator!!.connectAndAuthenticate { provider, _ ->
signBytes(data, provider)
}
} else {
return signBytes(data, provider)
}
}
private fun signBytes(data: ByteArray, provider: CryptoServerProvider): DigitalSignatureWithCert {
val keyStore = getAndInitializeKeyStore(provider)
val certificate = keyStore.getX509Certificate(keyName)
// 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(keyName, null) as PrivateKey
val signature = Signature.getInstance(HsmX509Utilities.SIGNATURE_ALGORITHM, provider).run {
initSign(key)
update(data)
sign()
}
verify(data, signature, certificate.publicKey)
return DigitalSignatureWithCert(certificate, signature)
}
}

View File

@ -2,6 +2,7 @@ package com.r3.corda.networkmanage.common.persistence
import com.r3.corda.networkmanage.TestBase
import net.corda.core.utilities.minutes
import net.corda.nodeapi.internal.network.CertificateRevocationRequest
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.internal.DEV_INTERMEDIATE_CA
@ -52,7 +53,10 @@ class PersistentCertificateRevocationListStorageTest : TestBase() {
fun `Saving CRL persists it in the DB and changes the status of the certificate revocation requests to DONE`() {
// given
val certificate = createNodeCertificate(csrStorage)
val requestId = crrStorage.saveRevocationRequest(certificate.serialNumber, REVOCATION_REASON, REPORTER)
val requestId = crrStorage.saveRevocationRequest(CertificateRevocationRequest(
certificateSerialNumber = certificate.serialNumber,
reason = REVOCATION_REASON,
reporter = REPORTER))
crrStorage.approveRevocationRequest(requestId, "Approver")
val revocationRequest = crrStorage.getRevocationRequest(requestId)!!
val crl = createDummyCertificateRevocationList(listOf(revocationRequest.certificateSerialNumber))
@ -72,16 +76,25 @@ class PersistentCertificateRevocationListStorageTest : TestBase() {
@Test
fun `Saving CRL does not change the status of other requests`() {
// given
val done = crrStorage.saveRevocationRequest(createNodeCertificate(csrStorage, legalName = "Bank A").serialNumber, REVOCATION_REASON, REPORTER)
val done = crrStorage.saveRevocationRequest(CertificateRevocationRequest(
certificateSerialNumber = createNodeCertificate(csrStorage, legalName = "Bank A").serialNumber,
reason = REVOCATION_REASON,
reporter = REPORTER))
crrStorage.approveRevocationRequest(done, "Approver")
val doneRevocationRequest = crrStorage.getRevocationRequest(done)!!
val new = crrStorage.saveRevocationRequest(createNodeCertificate(csrStorage, legalName = "Bank B").serialNumber, REVOCATION_REASON, REPORTER)
val new = crrStorage.saveRevocationRequest(CertificateRevocationRequest(
certificateSerialNumber = createNodeCertificate(csrStorage, legalName = "Bank B").serialNumber,
reason = REVOCATION_REASON,
reporter = REPORTER))
val crl = createDummyCertificateRevocationList(listOf(doneRevocationRequest.certificateSerialNumber))
crlStorage.saveCertificateRevocationList(crl, CrlIssuer.DOORMAN, "TestSigner", Instant.now())
val approved = crrStorage.saveRevocationRequest(createNodeCertificate(csrStorage, legalName = "Bank C").serialNumber, REVOCATION_REASON, REPORTER)
val approved = crrStorage.saveRevocationRequest(CertificateRevocationRequest(
certificateSerialNumber = createNodeCertificate(csrStorage, legalName = "Bank C").serialNumber,
reason = REVOCATION_REASON,
reporter = REPORTER))
crrStorage.approveRevocationRequest(approved, "Approver")
val approvedRevocationRequest = crrStorage.getRevocationRequest(approved)!!

View File

@ -1,6 +1,7 @@
package com.r3.corda.networkmanage.common.persistence
import com.r3.corda.networkmanage.TestBase
import net.corda.nodeapi.internal.network.CertificateRevocationRequest
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.node.MockServices
@ -41,7 +42,10 @@ class PersistentCertificateRevocationRequestStorageTest : TestBase() {
val certificate = createNodeCertificate(csrStorage)
// when
val requestId = crrStorage.saveRevocationRequest(certificate.serialNumber, REVOCATION_REASON, REPORTER)
val requestId = crrStorage.saveRevocationRequest(CertificateRevocationRequest(
certificateSerialNumber = certificate.serialNumber,
reason = REVOCATION_REASON,
reporter = REPORTER))
// then
assertNotNull(crrStorage.getRevocationRequest(requestId)).apply {
@ -55,7 +59,10 @@ class PersistentCertificateRevocationRequestStorageTest : TestBase() {
fun `Retrieving a certificate revocation request succeeds`() {
// given
val certificate = createNodeCertificate(csrStorage)
val requestId = crrStorage.saveRevocationRequest(certificate.serialNumber, REVOCATION_REASON, REPORTER)
val requestId = crrStorage.saveRevocationRequest(CertificateRevocationRequest(
certificateSerialNumber = certificate.serialNumber,
reason = REVOCATION_REASON,
reporter = REPORTER))
// when
val request = crrStorage.getRevocationRequest(requestId)
@ -68,10 +75,16 @@ class PersistentCertificateRevocationRequestStorageTest : TestBase() {
fun `Retrieving a certificate revocation requests by status returns correct data`() {
// given
(1..10).forEach {
crrStorage.saveRevocationRequest(createNodeCertificate(csrStorage, "LegalName" + it.toString()).serialNumber, REVOCATION_REASON, REPORTER)
crrStorage.saveRevocationRequest(CertificateRevocationRequest(
certificateSerialNumber = createNodeCertificate(csrStorage, "LegalName" + it.toString()).serialNumber,
reason = REVOCATION_REASON,
reporter = REPORTER))
}
(11..15).forEach {
val requestId = crrStorage.saveRevocationRequest(createNodeCertificate(csrStorage, "LegalName" + it.toString()).serialNumber, REVOCATION_REASON, REPORTER)
val requestId = crrStorage.saveRevocationRequest(CertificateRevocationRequest(
certificateSerialNumber = createNodeCertificate(csrStorage, "LegalName" + it.toString()).serialNumber,
reason = REVOCATION_REASON,
reporter = REPORTER))
crrStorage.approveRevocationRequest(requestId, "Approver")
}
@ -89,7 +102,10 @@ class PersistentCertificateRevocationRequestStorageTest : TestBase() {
// then
assertFailsWith(IllegalArgumentException::class) {
// when
crrStorage.saveRevocationRequest(BigInteger.TEN, REVOCATION_REASON, REPORTER)
crrStorage.saveRevocationRequest(CertificateRevocationRequest(
certificateSerialNumber = BigInteger.TEN,
reason = REVOCATION_REASON,
reporter = REPORTER))
}
}
@ -97,7 +113,10 @@ class PersistentCertificateRevocationRequestStorageTest : TestBase() {
fun `Approving a certificate revocation request changes its status`() {
// given
val certificate = createNodeCertificate(csrStorage)
val requestId = crrStorage.saveRevocationRequest(certificate.serialNumber, REVOCATION_REASON, REPORTER)
val requestId = crrStorage.saveRevocationRequest(CertificateRevocationRequest(
certificateSerialNumber = certificate.serialNumber,
reason = REVOCATION_REASON,
reporter = REPORTER))
// when
crrStorage.approveRevocationRequest(requestId, "Approver")
@ -112,7 +131,10 @@ class PersistentCertificateRevocationRequestStorageTest : TestBase() {
fun `Rejecting a certificate revocation request changes its status`() {
// given
val certificate = createNodeCertificate(csrStorage)
val requestId = crrStorage.saveRevocationRequest(certificate.serialNumber, REVOCATION_REASON, REPORTER)
val requestId = crrStorage.saveRevocationRequest(CertificateRevocationRequest(
certificateSerialNumber = certificate.serialNumber,
reason = REVOCATION_REASON,
reporter = REPORTER))
// when
crrStorage.rejectRevocationRequest(requestId, "Rejector", "No reason")

View File

@ -0,0 +1,73 @@
package com.r3.corda.networkmanage.common.signer
import com.nhaarman.mockito_kotlin.*
import com.r3.corda.networkmanage.TestBase
import com.r3.corda.networkmanage.common.persistence.CertificateRevocationListStorage
import com.r3.corda.networkmanage.common.persistence.CertificateRevocationRequestData
import com.r3.corda.networkmanage.common.persistence.CrlIssuer
import com.r3.corda.networkmanage.common.persistence.RequestStatus
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.random63BitValue
import net.corda.core.crypto.sign
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.DigitalSignatureWithCert
import net.corda.core.utilities.millis
import net.corda.nodeapi.internal.DEV_INTERMEDIATE_CA
import org.junit.Before
import org.junit.Test
import java.math.BigInteger
import java.net.URL
import java.security.cert.CRLReason
import java.security.cert.X509CRL
import java.time.Instant
import kotlin.test.assertNotNull
class CertificateRevocationListSignerTest : TestBase() {
private val signer = TestSigner()
private lateinit var crlStorage: CertificateRevocationListStorage
private lateinit var crlSigner: CertificateRevocationListSigner
@Before
fun setUp() {
crlStorage = mock()
crlSigner = CertificateRevocationListSigner(crlStorage, DEV_INTERMEDIATE_CA.certificate, 600.millis, URL("http://dummy.com"), signer)
}
@Test
fun `signCertificateRevocationList creates correct CRL and saves c`() {
// given
val approvedReq = givenCertificateRevocationRequest(RequestStatus.APPROVED)
val revokedReq = givenCertificateRevocationRequest(RequestStatus.DONE)
val signedBy = "Signer"
// when
crlSigner.createSignedCRL(listOf(approvedReq), listOf(revokedReq), signedBy)
// then
argumentCaptor<X509CRL>().apply {
verify(crlStorage).saveCertificateRevocationList(capture(), eq(CrlIssuer.DOORMAN), eq(signedBy), any())
val crl = firstValue
crl.verify(DEV_INTERMEDIATE_CA.keyPair.public)
assertNotNull(crl.getRevokedCertificate(approvedReq.certificateSerialNumber))
assertNotNull(crl.getRevokedCertificate(revokedReq.certificateSerialNumber))
}
}
private class TestSigner : Signer {
override fun signBytes(data: ByteArray): DigitalSignatureWithCert {
return DigitalSignatureWithCert(DEV_INTERMEDIATE_CA.certificate, DEV_INTERMEDIATE_CA.keyPair.private.sign(data).bytes)
}
}
private fun givenCertificateRevocationRequest(status: RequestStatus): CertificateRevocationRequestData {
return CertificateRevocationRequestData(
SecureHash.randomSHA256().toString(),
BigInteger.valueOf(random63BitValue()),
Instant.now(),
CordaX500Name.parse("CN=Bank A, O=$status, L=London, C=GB"),
status,
CRLReason.KEY_COMPROMISE,
"Reporter")
}
}

View File

@ -0,0 +1,23 @@
package net.corda.nodeapi.internal.network
import net.corda.core.identity.CordaX500Name
import net.corda.core.serialization.CordaSerializable
import java.math.BigInteger
import java.security.cert.CRLReason
/**
* This data class is intended to be used by the certificate revocation request (CRR) service client to create a new
* CRR submission.
*/
@CordaSerializable
data class CertificateRevocationRequest(val certificateSerialNumber: BigInteger? = null,
val csrRequestId: String? = null,
val legalName: CordaX500Name? = null,
val reason: CRLReason,
val reporter: String) {
init {
require(certificateSerialNumber != null || csrRequestId != null || legalName != null) {
"At least one of the following needs to be specified: certificateSerialNumber, csrRequestId, legalName."
}
}
}

View File

@ -1,3 +1,13 @@
/*
* 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 net.corda.nodeapi.internal.serialization.amqp.custom
import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer