ENT-1883 Adding the CRL generator (#857)

* Adding the empty CRL generation functionality

* Addressing review comments
This commit is contained in:
Michal Kit 2018-05-23 07:48:53 +01:00 committed by GitHub
parent c4f9f1cb68
commit d9510d9a22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 634 additions and 67 deletions

View File

@ -0,0 +1,18 @@
HSM Certificate Generation Tool
===============================
The purpose of the HSM Certificate Revocation List (CRL) Generation Tool is to provide means for the ROOT signed CRL creation.
Currently, only the NODE-level CRL creation is automated. Other levels (i.e. INTERMEDIATE and TLS) need to be addressed as well.
Since we do not presume to update the INTERMEDIATE-level CRL often, the automation in this case is not required.
With respect to the TLS certificates, we (from the perspective of R3) are not the maintainers of those CRLs.
It is a customer responsibility to maintain those lists. However, in order to ensure correct CRL checking procedure in case of the
SSL communication we need to provide the endpoint serving an empty CRL in case the customer is not able to provide for a CRL infrastructure.
Thus necessity for an empty CRL creation.
The HSM CRL Generation Tool allows for both empty and non-empty CRL creation. It can be configured to generate direct and indirect CRLs.
A direct CRL is a CRL issued by the certificate issuer, which applies to the INTERMEDIATE certificates.
However, sometimes there is a need for creating an indirect CRL - i.e. issued by another authority different than the certificate issuer. This is the case in the TLS certificates.
The tool is implemented in such a way that the ROOT CA is always the issuing authority. Depending on the configuration, the generated
CRL can be flagged as direct or indirect.
The output of the tool is a file containing ASN.1 DER-encoded bytes of the generated CRL.

View File

@ -13,7 +13,7 @@ Configuration file
At startup, the HSM Certificate Generation Tool reads a configuration file, passed with ``--config-file`` on the command line.
This is an example of what a tool configuration file might look like:
.. literalinclude:: ../../network-management/generator.conf
.. literalinclude:: ../../network-management/cert-generator.conf
General configuration parameters
--------------------------------
@ -61,9 +61,9 @@ Certificate Configuration
:keyOverride: Whether to override the key if already exists or not. 1 for override and 0 for NOT override.
:keySpecifier: This is an HSM specific parameter that corresponds to key name spacing. See Utimaco documentation for more details.
:keySpecifier: This is an HSM specific parameter that corresponds to key name spacing of the generated key. See Utimaco documentation for more details.
:keyGroup: This is an HSM specific parameter that corresponds to key name spacing for the generated key. See Utimaco documentation for more details.
:keyGroup: This is an HSM specific parameter that corresponds to key name grouping of the generated key. See Utimaco documentation for more details.
User Authentication Configuration
@ -72,7 +72,7 @@ Allowed parameters are:
:username: HSM username. This user needs to be allowed to generate keys/certificates and store them in HSM.
:authMode: One of the 2 possible authentication modes:
:authMode: One of the 3 possible authentication modes:
PASSWORD - User's password as set-up in the HSM
CARD_READER - Smart card reader authentication
KEY_FILE - Key file based authentication.

View File

@ -0,0 +1,76 @@
Running the HSM Certificate Generation tool
===========================================
The purpose of this tool is to facilitate the process of CRL generation using the ROOT certificate stored on the HSM infrastructure.
See :doc:`hsm-crl-generator` for more details.
See the Readme under ``network-management`` for detailed building instructions.
Configuration file
------------------
At startup, the HSM CRL Generation Tool reads a configuration file, passed with ``--config-file`` on the command line.
This is an example of what a tool configuration file might look like:
.. literalinclude:: ../../network-management/crl-generator.conf
General configuration parameters
--------------------------------
Allowed parameters are:
:hsmHost: IP address of the HSM device.
:hsmPort: Port number of the HSM device.
:userConfigs: List of user authentication configurations. See below section on User Authentication Configuration.
:crl: CRL specific configuration. See below section on CRL Configuration.
:trustStoreFile: Path to the trust store file containing the ROOT certificate.
:trustStorePassword: Password for the trust store.
CRL Configuration
-----------------
:keySpecifier: This is an HSM specific parameter that corresponds to ROOT key name spacing. See Utimaco documentation for more details.
:keyGroup: This is an HSM specific parameter that corresponds to ROOT key name grouping. See Utimaco documentation for more details.
:validDays: Validity period of this CRL expressed in days.
:crlEndpoint: URL pointing to the endpoint where this CRL can be obtained from. It is embedded in the generated CRL.
:indirectIssuer: A boolean flag noting whether this CRL was issued by the certificate issuer (false) or another issuer (true).
:filePath: Path to the generated file.
:revocations: A list of revoked certificate data that is to be included in the generated CRL. Default value is the empty list.
See below for more details on the revoked certificate data.
Revoked Certificate Data
------------------------
:certificateSerialNumber: Serial number of the revoked certificate.
:dateInMillis: Certificate revocation time.
:reason: Reason for the certificate revocation. The allowed value is one of the following:
UNSPECIFIED, KEY_COMPROMISE, CA_COMPROMISE, AFFILIATION_CHANGED, SUPERSEDED, CESSATION_OF_OPERATION, PRIVILEGE_WITHDRAWN
User Authentication Configuration
---------------------------------
Allowed parameters are:
:username: HSM username. This user needs to be allowed to generate keys/certificates and store them in HSM.
:authMode: One of the 3 possible authentication modes:
PASSWORD - User's password as set-up in the HSM
CARD_READER - Smart card reader authentication
KEY_FILE - Key file based authentication.
:authToken: Depending on the authMode it is either user's password or path to the authentication key file. In case of the CARD_READER authMode value, this can be omitted.
:keyFilePassword: Only relevant, if authMode == KEY_FILE. It is the key file password.

View File

@ -48,6 +48,18 @@ The built file will appear in
network-management/capsule-hsm-cert-generator/build/libs/hsm-cert-generator-<VERSION>.jar
```
## HSM CRL Generator
To build a fat jar containing all the hsm CRL generator code you can simply invoke
```
./gradlew network-management:capsule-hsm-crl-generator:buildHsmCrlGeneratorJAR
```
The built file will appear in
```
network-management/capsule-hsm-crl-generator/build/libs/hsm-crl-generator-<VERSION>.jar
```
## Certificate Revocation Request Submission Tool
To build a fat jar containing all the CRR submission tool code you can simply invoke

View File

@ -20,7 +20,7 @@ configurations {
}
task buildHsmCertGeneratorJAR(type: FatCapsule, dependsOn: 'jar') {
applicationClass 'com.r3.corda.networkmanage.hsm.generator.MainKt'
applicationClass 'com.r3.corda.networkmanage.hsm.generator.certificate.MainKt'
archiveName "hsm-cert-generator-${version}.jar"
capsuleManifest {
applicationVersion = corda_release_version

View File

@ -0,0 +1,49 @@
/*
* 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.
*/
apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'us.kirchmeier.capsule'
description 'HSM Certificate Generator'
version project(':network-management').version
configurations {
runtimeArtifacts.extendsFrom runtime
}
task buildHsmCrlGeneratorJAR(type: FatCapsule, dependsOn: 'jar') {
applicationClass 'com.r3.corda.networkmanage.hsm.generator.crl.MainKt'
archiveName "hsm-crl-generator-${version}.jar"
capsuleManifest {
applicationVersion = corda_release_version
systemProperties['visualvm.display.name'] = 'HSM CRL Generator'
minJavaVersion = '1.8.0'
jvmArgs = ['-XX:+UseG1GC']
}
applicationSource = files(
project(':network-management').configurations.runtime,
project(':network-management').jar
)
}
artifacts {
runtimeArtifacts buildHsmCrlGeneratorJAR
publish buildHsmCrlGeneratorJAR
}
jar {
classifier "ignore"
}
publish {
name 'hsm-crl-generator'
disableDefaultJar = true
}

View File

@ -0,0 +1,33 @@
hsmHost = 127.0.0.1
hsmPort = 3001
trustStoreFile = "./truststore.jks"
trustStorePassword = "trustpass"
crl {
keyGroup = "TEST.CORDACONNECT.ROOT"
keySpecifier = 1
validDays = 3650
crlEndpoint = "http://test.com/crl"
indirectIssuer = true
filePath = "./bytes.crl"
revocations = [
{
certificateSerialNumber = "12345"
dateInMillis = 1526643707290
reason = "KEY_COMPROMISE"
},
{
certificateSerialNumber = "6789012"
dateInMillis = 1526643712345
reason = "KEY_COMPROMISE"
}
]
}
userConfigs = [
{
username = "INTEGRATION_TEST"
authMode = PASSWORD
authToken = "INTEGRATION_TEST"
}
]

View File

@ -14,11 +14,12 @@ import com.nhaarman.mockito_kotlin.any
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import com.r3.corda.networkmanage.HsmSimulator
import com.r3.corda.networkmanage.hsm.authentication.CryptoServerProviderConfig
import com.r3.corda.networkmanage.hsm.authentication.InputReader
import com.r3.corda.networkmanage.hsm.configuration.*
import com.r3.corda.networkmanage.hsm.generator.CertificateConfiguration
import com.r3.corda.networkmanage.hsm.generator.GeneratorParameters
import com.r3.corda.networkmanage.hsm.generator.UserAuthenticationParameters
import com.r3.corda.networkmanage.hsm.generator.certificate.CertificateConfiguration
import com.r3.corda.networkmanage.hsm.generator.certificate.GeneratorParameters
import net.corda.core.crypto.random63BitValue
import net.corda.core.internal.div
import net.corda.core.utilities.NetworkHostAndPort
@ -43,9 +44,9 @@ abstract class HsmBaseTest : IntegrationTest() {
const val ROOT_CERT_KEY_GROUP = "TEST.CORDACONNECT.ROOT"
const val NETWORK_MAP_CERT_KEY_GROUP = "TEST.CORDACONNECT.OPS.NETMAP"
const val DOORMAN_CERT_KEY_GROUP = "TEST.CORDACONNECT.OPS.CERT"
const val ROOT_CERT_SUBJECT = "CN=Corda Root CA, O=R3 Ltd, OU=Corda, L=London, C=GB"
const val NETWORK_MAP_CERT_SUBJECT = "CN=Corda Network Map, O=R3 Ltd, OU=Corda, L=London, C=GB"
const val DOORMAN_CERT_SUBJECT = "CN=Corda Doorman CA, O=R3 Ltd, OU=Corda, L=London, C=GB"
const val ROOT_CERT_SUBJECT = "CN=Corda Root CA, OU=Corda, O=R3 Ltd, L=London, C=GB"
const val NETWORK_MAP_CERT_SUBJECT = "CN=Corda Network Map, OU=Corda, O=R3 Ltd, L=London, C=GB"
const val DOORMAN_CERT_SUBJECT = "CN=Corda Doorman CA, OU=Corda, O=R3 Ltd, L=London, C=GB"
const val TRUSTSTORE_PASSWORD: String = "trustpass"
const val HSM_USERNAME = "INTEGRATION_TEST"
const val HSM_PASSWORD = "INTEGRATION_TEST"
@ -191,4 +192,12 @@ abstract class HsmBaseTest : IntegrationTest() {
fun makeTestDatabaseProperties(): DatabaseConfig {
return makeTestDatabaseProperties(DOORMAN_DB_NAME, configSupplier = configSupplierForSupportedDatabases())
}
protected fun createProviderConfig(keyGroup: String): CryptoServerProviderConfig {
return CryptoServerProviderConfig(
Device = "${hsmSimulator.port}@${hsmSimulator.host}",
KeySpecifier = 1,
KeyGroup = keyGroup,
StoreKeysExternal = false)
}
}

View File

@ -0,0 +1,137 @@
/*
* 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.hsm
import com.nhaarman.mockito_kotlin.any
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import com.r3.corda.networkmanage.common.HsmBaseTest
import com.r3.corda.networkmanage.hsm.authentication.InputReader
import com.r3.corda.networkmanage.hsm.generator.AutoAuthenticator
import com.r3.corda.networkmanage.hsm.generator.UserAuthenticationParameters
import com.r3.corda.networkmanage.hsm.generator.crl.CrlConfig
import com.r3.corda.networkmanage.hsm.generator.crl.GeneratorConfig
import com.r3.corda.networkmanage.hsm.generator.crl.RevocationConfig
import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
import org.apache.commons.io.FileUtils
import org.junit.Before
import org.junit.Test
import java.net.URL
import java.security.cert.CertificateFactory
import java.security.cert.X509CRL
import java.security.cert.X509Certificate
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
import com.r3.corda.networkmanage.hsm.generator.certificate.run as runCertificateGeneration
import com.r3.corda.networkmanage.hsm.generator.crl.run as runCrlGeneration
class HsmEmptyCrlGenerationTest : HsmBaseTest() {
private lateinit var inputReader: InputReader
@Before
override fun setUp() {
super.setUp()
inputReader = mock()
whenever(inputReader.readLine()).thenReturn(hsmSimulator.cryptoUserCredentials().username)
whenever(inputReader.readPassword(any())).thenReturn(hsmSimulator.cryptoUserCredentials().password)
}
@Test
fun `An empty CRL is generated`() {
// when root cert is created
runCertificateGeneration(createGeneratorParameters(
keyGroup = ROOT_CERT_KEY_GROUP,
rootKeyGroup = null,
certificateType = CertificateType.ROOT_CA,
subject = ROOT_CERT_SUBJECT
))
// then root cert is persisted in the HSM
AutoAuthenticator(createProviderConfig(ROOT_CERT_KEY_GROUP), HSM_USER_CONFIGS).connectAndAuthenticate { provider ->
val keyStore = HsmX509Utilities.getAndInitializeKeyStore(provider)
val rootCert = keyStore.getCertificate(CORDA_ROOT_CA) as X509Certificate
assertEquals(rootCert.issuerX500Principal, rootCert.subjectX500Principal)
}
val generatedFile = tempFolder.newFile()
runCrlGeneration(createCrlGeneratorParameters(CrlConfig(
crlEndpoint = URL("http://test.com/crl"),
filePath = generatedFile.toPath(),
keyGroup = ROOT_CERT_KEY_GROUP,
keySpecifier = 1,
validDays = 1000,
indirectIssuer = true,
revocations = emptyList()), HSM_ROOT_USER_CONFIGS))
val crl = CertificateFactory.getInstance("X.509")
.generateCRL(FileUtils.readFileToByteArray(generatedFile).inputStream()) as X509CRL
assertNotNull(crl)
assertEquals(ROOT_CERT_SUBJECT, crl.issuerDN.name)
assertTrue { crl.revokedCertificates.isEmpty() }
}
@Test
fun `A non-empty CRL is generated`() {
// when root cert is created
runCertificateGeneration(createGeneratorParameters(
keyGroup = ROOT_CERT_KEY_GROUP,
rootKeyGroup = null,
certificateType = CertificateType.ROOT_CA,
subject = ROOT_CERT_SUBJECT
))
// then root cert is persisted in the HSM
AutoAuthenticator(createProviderConfig(ROOT_CERT_KEY_GROUP), HSM_USER_CONFIGS).connectAndAuthenticate { provider ->
val keyStore = HsmX509Utilities.getAndInitializeKeyStore(provider)
val rootCert = keyStore.getCertificate(CORDA_ROOT_CA) as X509Certificate
assertEquals(rootCert.issuerX500Principal, rootCert.subjectX500Principal)
}
val generatedFile = tempFolder.newFile()
val revokedSerialNumber = "1234567890"
runCrlGeneration(createCrlGeneratorParameters(CrlConfig(
crlEndpoint = URL("http://test.com/crl"),
filePath = generatedFile.toPath(),
keyGroup = ROOT_CERT_KEY_GROUP,
keySpecifier = 1,
validDays = 1000,
indirectIssuer = false,
revocations = listOf(
RevocationConfig(
certificateSerialNumber = "1234567890",
dateInMillis = 0,
reason = "KEY_COMPROMISE"
)
)), HSM_ROOT_USER_CONFIGS))
val crl = CertificateFactory.getInstance("X.509")
.generateCRL(FileUtils.readFileToByteArray(generatedFile).inputStream()) as X509CRL
assertNotNull(crl)
assertEquals(ROOT_CERT_SUBJECT, crl.issuerDN.name)
assertEquals(1, crl.revokedCertificates.size)
val revoked = crl.revokedCertificates.first()
assertEquals(revoked.serialNumber.toString(), revokedSerialNumber)
}
private fun createCrlGeneratorParameters(crlConfg: CrlConfig,
userConfigs: List<UserAuthenticationParameters>): GeneratorConfig {
return GeneratorConfig(
hsmHost = hsmSimulator.host,
hsmPort = hsmSimulator.port,
trustStoreFile = rootKeyStoreFile,
trustStorePassword = TRUSTSTORE_PASSWORD,
userConfigs = userConfigs,
crl = crlConfg
)
}
}

View File

@ -15,10 +15,9 @@ import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import com.r3.corda.networkmanage.common.HsmBaseTest
import com.r3.corda.networkmanage.common.utils.CORDA_NETWORK_MAP
import com.r3.corda.networkmanage.hsm.authentication.CryptoServerProviderConfig
import com.r3.corda.networkmanage.hsm.authentication.InputReader
import com.r3.corda.networkmanage.hsm.generator.AutoAuthenticator
import com.r3.corda.networkmanage.hsm.generator.run
import com.r3.corda.networkmanage.hsm.generator.certificate.run
import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities
import net.corda.core.identity.CordaX500Name
import net.corda.nodeapi.internal.crypto.CertificateType
@ -93,12 +92,4 @@ class HsmKeyGenerationTest : HsmBaseTest() {
assertEquals(CordaX500Name.parse(ROOT_CERT_SUBJECT).x500Principal, networkMapCert.issuerX500Principal)
}
}
private fun createProviderConfig(keyGroup: String): CryptoServerProviderConfig {
return CryptoServerProviderConfig(
Device = "${hsmSimulator.port}@${hsmSimulator.host}",
KeySpecifier = 1,
KeyGroup = keyGroup,
StoreKeysExternal = false)
}
}

View File

@ -15,7 +15,7 @@ import com.r3.corda.networkmanage.common.HsmBaseTest
import com.r3.corda.networkmanage.hsm.authentication.Authenticator
import com.r3.corda.networkmanage.hsm.authentication.createProvider
import com.r3.corda.networkmanage.hsm.generator.UserAuthenticationParameters
import com.r3.corda.networkmanage.hsm.generator.run
import com.r3.corda.networkmanage.hsm.generator.certificate.run
import com.r3.corda.networkmanage.hsm.persistence.ApprovedCertificateRequestData
import com.r3.corda.networkmanage.hsm.signer.HsmCsrSigner
import net.corda.core.crypto.Crypto.generateKeyPair
@ -149,25 +149,25 @@ class HsmPermissionTest : HsmBaseTest() {
netMapCertUserConfigs: List<UserAuthenticationParameters>) {
// when root cert is created
run(createGeneratorParameters(
keyGroup = HsmBaseTest.ROOT_CERT_KEY_GROUP,
keyGroup = ROOT_CERT_KEY_GROUP,
rootKeyGroup = null,
certificateType = CertificateType.ROOT_CA,
subject = HsmBaseTest.ROOT_CERT_SUBJECT,
subject = ROOT_CERT_SUBJECT,
hsmUserConfigs = rootCertUserConfigs))
// when network map cert is created
run(createGeneratorParameters(
keyGroup = HsmBaseTest.NETWORK_MAP_CERT_KEY_GROUP,
rootKeyGroup = HsmBaseTest.ROOT_CERT_KEY_GROUP,
keyGroup = NETWORK_MAP_CERT_KEY_GROUP,
rootKeyGroup = ROOT_CERT_KEY_GROUP,
certificateType = CertificateType.NETWORK_MAP,
subject = HsmBaseTest.NETWORK_MAP_CERT_SUBJECT,
subject = NETWORK_MAP_CERT_SUBJECT,
hsmUserConfigs = netMapCertUserConfigs
))
// when doorman cert is created
run(createGeneratorParameters(
keyGroup = HsmBaseTest.DOORMAN_CERT_KEY_GROUP,
rootKeyGroup = HsmBaseTest.ROOT_CERT_KEY_GROUP,
keyGroup = DOORMAN_CERT_KEY_GROUP,
rootKeyGroup = ROOT_CERT_KEY_GROUP,
certificateType = CertificateType.INTERMEDIATE_CA,
subject = HsmBaseTest.DOORMAN_CERT_SUBJECT,
subject = DOORMAN_CERT_SUBJECT,
hsmUserConfigs = doormanCertUserConfigs
))
}

View File

@ -19,7 +19,7 @@ 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
import com.r3.corda.networkmanage.hsm.generator.run
import com.r3.corda.networkmanage.hsm.generator.certificate.run
import com.r3.corda.networkmanage.hsm.persistence.ApprovedCertificateRequestData
import com.r3.corda.networkmanage.hsm.signer.HsmCsrSigner
import com.r3.corda.networkmanage.hsm.signer.HsmSigner

View File

@ -4,6 +4,7 @@ import com.r3.corda.networkmanage.common.persistence.CertificateRevocationListSt
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.Revocation
import com.r3.corda.networkmanage.common.utils.createSignedCrl
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
@ -13,6 +14,7 @@ import java.security.cert.X509CRL
import java.security.cert.X509Certificate
import java.time.Duration
import java.time.Instant
import java.util.*
class CertificateRevocationListSigner(
private val revocationListStorage: CertificateRevocationListStorage,
@ -48,7 +50,10 @@ class CertificateRevocationListSigner(
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)
val revocations = (existingCRRs + approvedWithTimestamp).map {
Revocation(it.certificateSerialNumber, Date(it.modifiedAt.toEpochMilli()), it.reason)
}
val crl = createSignedCrl(issuerCertificate, endpoint, updateInterval, signer, revocations)
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.")

View File

@ -1,6 +1,5 @@
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
@ -12,7 +11,9 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.operator.ContentSigner
import java.io.ByteArrayOutputStream
import java.io.OutputStream
import java.math.BigInteger
import java.net.URL
import java.security.cert.CRLReason
import java.security.cert.X509CRL
import java.security.cert.X509Certificate
import java.time.Duration
@ -23,7 +24,7 @@ fun createSignedCrl(issuerCertificate: X509Certificate,
endpointUrl: URL,
nextUpdateInterval: Duration,
signer: Signer,
includeInCrl: List<CertificateRevocationRequestData>,
includeInCrl: List<Revocation>,
indirectIssuingPoint: Boolean = false): X509CRL {
val extensionUtils = JcaX509ExtensionUtils()
val builder = X509v2CRLBuilder(X500Name.getInstance(issuerCertificate.subjectX500Principal.encoded), Date())
@ -33,12 +34,14 @@ fun createSignedCrl(issuerCertificate: X509Certificate,
builder.addExtension(Extension.issuingDistributionPoint, true, issuingDistributionPoint)
builder.setNextUpdate(Date(Instant.now().toEpochMilli() + nextUpdateInterval.toMillis()))
includeInCrl.forEach {
builder.addCRLEntry(it.certificateSerialNumber, Date(it.modifiedAt.toEpochMilli()), it.reason.ordinal)
builder.addCRLEntry(it.certificateSerialNumber, it.date, it.reason.ordinal)
}
val crlHolder = builder.build(CrlContentSigner(signer))
return JcaX509CRLConverter().setProvider(BouncyCastleProvider()).getCRL(crlHolder)
}
data class Revocation(val certificateSerialNumber: BigInteger, val date: Date, val reason: CRLReason)
private class CrlContentSigner(private val signer: Signer) : ContentSigner {
private val outputStream = ByteArrayOutputStream()
@ -46,4 +49,14 @@ private class CrlContentSigner(private val signer: Signer) : ContentSigner {
override fun getAlgorithmIdentifier(): AlgorithmIdentifier = X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME.signatureOID
override fun getOutputStream(): OutputStream = outputStream
override fun getSignature(): ByteArray = signer.signBytes(outputStream.toByteArray()).bytes
}
enum class SupportedCrlReasons {
UNSPECIFIED,
KEY_COMPROMISE,
CA_COMPROMISE,
AFFILIATION_CHANGED,
SUPERSEDED,
CESSATION_OF_OPERATION,
PRIVILEGE_WITHDRAWN
}

View File

@ -14,6 +14,27 @@ import CryptoServerJCE.CryptoServerProvider
import com.r3.corda.networkmanage.hsm.authentication.CryptoServerProviderConfig
import com.r3.corda.networkmanage.hsm.authentication.createProvider
/**
* Holds configuration necessary for user's authentication against HSM.
*/
data class UserAuthenticationParameters(val username: String,
val authMode: AuthMode,
val authToken: String?, // password or path to the key file, depending on the [authMode]
val keyFilePassword: String?) { // used only if authMode == [AuthMode.KEY_FILE]
init {
require(keyFilePassword == null || authMode == AuthMode.KEY_FILE) {
"keyFilePassword can only be specified if the authMode is set to KEY_FILE"
}
}
}
/**
* Supported authentication modes.
*/
enum class AuthMode {
PASSWORD, CARD_READER, KEY_FILE
}
/**
* Performs user authentication against the HSM
*/

View File

@ -8,8 +8,9 @@
* 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.hsm.generator
package com.r3.corda.networkmanage.hsm.generator.certificate
import com.r3.corda.networkmanage.hsm.generator.UserAuthenticationParameters
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy
@ -17,21 +18,6 @@ import net.corda.nodeapi.internal.config.parseAs
import net.corda.nodeapi.internal.crypto.CertificateType
import java.nio.file.Path
/**
* Holds configuration necessary for user's authentication against HSM.
*/
data class UserAuthenticationParameters(val username: String,
val authMode: AuthMode,
val authToken: String?, // password or path to the key file, depending on the [authMode]
val keyFilePassword: String?) // used only if authMode == [AuthMode.KEY_FILE]
/**
* Supported authentication modes.
*/
enum class AuthMode {
PASSWORD, CARD_READER, KEY_FILE
}
/**
* Holds generator parameters.
*/

View File

@ -8,7 +8,7 @@
* 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.hsm.generator
package com.r3.corda.networkmanage.hsm.generator.certificate
import CryptoServerCXI.CryptoServerCXI.KEY_ALGO_ECDSA
import CryptoServerCXI.CryptoServerCXI.KeyAttributes

View File

@ -8,15 +8,16 @@
* 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.hsm.generator
package com.r3.corda.networkmanage.hsm.generator.certificate
import com.r3.corda.networkmanage.common.configuration.ConfigFilePathArgsParser
import com.r3.corda.networkmanage.hsm.authentication.CryptoServerProviderConfig
import com.r3.corda.networkmanage.hsm.generator.AutoAuthenticator
import com.r3.corda.networkmanage.hsm.utils.mapCryptoServerException
import net.corda.nodeapi.internal.crypto.CertificateType.ROOT_CA
import org.apache.logging.log4j.LogManager
private val logger = LogManager.getLogger("com.r3.corda.networkmanage.hsm.generator.Main")
private val logger = LogManager.getLogger("com.r3.corda.networkmanage.hsm.generator.certificate.Main")
fun main(args: Array<String>) {
run(parseParameters(ConfigFilePathArgsParser().parseOrExit(*args)))

View File

@ -0,0 +1,81 @@
/*
* 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.hsm.generator.crl
import com.r3.corda.networkmanage.common.utils.SupportedCrlReasons
import com.r3.corda.networkmanage.hsm.generator.UserAuthenticationParameters
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy
import net.corda.nodeapi.internal.config.parseAs
import net.corda.nodeapi.internal.crypto.X509KeyStore
import java.net.URL
import java.nio.file.Path
/**
* Holds generator parameters.
*/
data class GeneratorConfig(val hsmHost: String,
val hsmPort: Int,
val userConfigs: List<UserAuthenticationParameters>,
val trustStoreFile: Path,
val trustStorePassword: String,
val crl: CrlConfig) {
fun loadTrustStore(): X509KeyStore {
return X509KeyStore.fromFile(trustStoreFile, trustStorePassword, false)
}
}
/**
* Holds CRL specific configuration.
*/
data class CrlConfig(val keyGroup: String,
val keySpecifier: Int,
val validDays: Long,
val crlEndpoint: URL,
val indirectIssuer: Boolean,
val filePath: Path,
val revocations: List<RevocationConfig> = emptyList())
/**
* Supported revocation reasons:
* UNSPECIFIED,
* KEY_COMPROMISE,
* CA_COMPROMISE,
* AFFILIATION_CHANGED,
* SUPERSEDED,
* CESSATION_OF_OPERATION,
* PRIVILEGE_WITHDRAWN
*/
data class RevocationConfig(val certificateSerialNumber: String, val dateInMillis: Long, val reason: String) {
companion object {
val reasonErrorMessage = "Error when parsing the revocation reason. Allowed values: ${SupportedCrlReasons.values()}"
}
init {
try {
SupportedCrlReasons.valueOf(reason)
} catch (e: Exception) {
throw IllegalArgumentException(reasonErrorMessage)
}
}
}
/**
* Parses a configuration file, which contains all the configuration - i.e. for user and certificate parameters.
*/
fun parseParameters(configFile: Path): GeneratorConfig {
return ConfigFactory
.parseFile(configFile.toFile(), ConfigParseOptions.defaults().setAllowMissing(true))
.resolve()
.parseAs(UnknownConfigKeysPolicy.IGNORE::handle)
}

View File

@ -0,0 +1,61 @@
/*
* 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.hsm.generator.crl
import com.r3.corda.networkmanage.common.configuration.ConfigFilePathArgsParser
import com.r3.corda.networkmanage.common.utils.Revocation
import com.r3.corda.networkmanage.common.utils.createSignedCrl
import com.r3.corda.networkmanage.hsm.authentication.CryptoServerProviderConfig
import com.r3.corda.networkmanage.hsm.generator.AutoAuthenticator
import com.r3.corda.networkmanage.hsm.signer.HsmSigner
import com.r3.corda.networkmanage.hsm.utils.mapCryptoServerException
import net.corda.nodeapi.internal.crypto.X509Utilities
import org.apache.commons.io.FileUtils
import org.apache.logging.log4j.LogManager
import java.math.BigInteger
import java.security.cert.CRLReason
import java.time.Duration
import java.util.*
private val logger = LogManager.getLogger("com.r3.corda.networkmanage.hsm.generator.crl.Main")
fun main(args: Array<String>) {
run(parseParameters(ConfigFilePathArgsParser().parseOrExit(*args)))
}
fun run(parameters: GeneratorConfig) {
parameters.run {
val providerConfig = CryptoServerProviderConfig(
Device = "$hsmPort@$hsmHost",
KeySpecifier = crl.keySpecifier,
KeyGroup = crl.keyGroup)
try {
AutoAuthenticator(providerConfig, userConfigs).connectAndAuthenticate { provider ->
logger.info("Generating an empty CRL...")
val issuerCertificate = loadTrustStore().getCertificate(X509Utilities.CORDA_ROOT_CA)
val generatedCrl = createSignedCrl(issuerCertificate,
crl.crlEndpoint,
Duration.ofDays(crl.validDays),
HsmSigner(provider = provider, keyName = X509Utilities.CORDA_ROOT_CA),
crl.revocations.map { Revocation(
BigInteger(it.certificateSerialNumber),
Date(it.dateInMillis),
CRLReason.valueOf(it.reason)
) },
crl.indirectIssuer)
FileUtils.writeByteArrayToFile(crl.filePath.toFile(), generatedCrl.encoded)
provider.logoff()
}
} catch (e: Exception) {
logger.error("HSM CRL generation error.", mapCryptoServerException(e))
}
}
}

View File

@ -1,5 +1,6 @@
package com.r3.corda.networkmanage.tools.crr.submission
import com.r3.corda.networkmanage.common.utils.SupportedCrlReasons
import com.r3.corda.networkmanage.common.utils.initialiseSerialization
import com.r3.corda.networkmanage.hsm.authentication.ConsoleInputReader
import com.r3.corda.networkmanage.hsm.authentication.InputReader
@ -55,16 +56,6 @@ private fun InputReader.getRequiredInput(attributeName: String): String {
}
}
private enum class SupportedCrlReasons {
UNSPECIFIED,
KEY_COMPROMISE,
CA_COMPROMISE,
AFFILIATION_CHANGED,
SUPERSEDED,
CESSATION_OF_OPERATION,
PRIVILEGE_WITHDRAWN
}
private fun getReason(inputReader: InputReader): CRLReason {
while (true) {
SupportedCrlReasons.values().forEachIndexed { index, value ->
@ -75,7 +66,7 @@ private fun getReason(inputReader: InputReader): CRLReason {
if (input < 1 || input > SupportedCrlReasons.values().size) {
println("Incorrect selection. Try again.")
} else {
return CRLReason.valueOf(SupportedCrlReasons.values()[input -1 ].name)
return CRLReason.valueOf(SupportedCrlReasons.values()[input - 1].name)
}
}
}

View File

@ -8,9 +8,12 @@
* 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.hsm.generator
package com.r3.corda.networkmanage.hsm.generator.cert
import com.r3.corda.networkmanage.common.configuration.ConfigFilePathArgsParser
import com.r3.corda.networkmanage.hsm.generator.AuthMode
import com.r3.corda.networkmanage.hsm.generator.certificate.GeneratorParameters
import com.r3.corda.networkmanage.hsm.generator.certificate.parseParameters
import com.typesafe.config.ConfigException
import joptsimple.OptionException
import net.corda.nodeapi.internal.crypto.CertificateType
@ -23,8 +26,8 @@ import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
class GeneratorParametersTest {
private val validConfigPath = File("generator.conf").absolutePath
private val invalidConfigPath = File(javaClass.getResource("/generator_fail.conf").toURI()).absolutePath
private val validConfigPath = File("cert-generator.conf").absolutePath
private val invalidConfigPath = File(javaClass.getResource("/cert-generator_fail.conf").toURI()).absolutePath
private val validArgs = arrayOf("--config-file", validConfigPath)
@Test

View File

@ -0,0 +1,67 @@
/*
* 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.hsm.generator.crl
import com.r3.corda.networkmanage.common.configuration.ConfigFilePathArgsParser
import com.r3.corda.networkmanage.hsm.generator.AuthMode
import com.typesafe.config.ConfigException
import joptsimple.OptionException
import org.assertj.core.api.Assertions
import org.junit.Test
import java.io.File
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
class GeneratorParametersTest {
private val validConfigPath = File("crl-generator.conf").absolutePath
private val invalidConfigPath = File(javaClass.getResource("/crl-generator_fail.conf").toURI()).absolutePath
private val validArgs = arrayOf("--config-file", validConfigPath)
@Test
fun `should fail when config file is missing`() {
val message = assertFailsWith<OptionException> {
ConfigFilePathArgsParser().parseOrExit("--config-file", "not-existing-file", printHelpOn = null)
}.message
Assertions.assertThat(message).contains("not-existing-file")
}
@Test
fun `should fail when config is invalid`() {
assertFailsWith<ConfigException.Missing> {
parseParameters(ConfigFilePathArgsParser().parseOrExit("--config-file", invalidConfigPath))
}
}
@Test
fun `should parse generator config correctly`() {
val parameters = parseCommandLineAndGetParameters()
assertEquals("127.0.0.1", parameters.hsmHost)
assertEquals(3001, parameters.hsmPort)
assertEquals("trustpass", parameters.trustStorePassword)
val crlConfig = parameters.crl
assertEquals(1, crlConfig.keySpecifier)
assertFalse(parameters.userConfigs.isEmpty())
val revocationsConfig = crlConfig.revocations
assertEquals(2, revocationsConfig.size)
val revocationConfig = revocationsConfig.first()
assertEquals(revocationConfig.reason, "KEY_COMPROMISE")
assertEquals(revocationConfig.certificateSerialNumber, "12345")
val userConfig = parameters.userConfigs.first()
assertEquals("INTEGRATION_TEST", userConfig.username)
assertEquals(AuthMode.PASSWORD, userConfig.authMode)
assertEquals("INTEGRATION_TEST", userConfig.authToken)
}
private fun parseCommandLineAndGetParameters(): GeneratorConfig {
return parseParameters(ConfigFilePathArgsParser().parseOrExit(*validArgs))
}
}

View File

@ -0,0 +1,12 @@
hsmHost = 127.0.0.1
hsmPort = 3001
trustStoreFile = "./truststore.jks"
trustStorePassword = "trustpass"
userConfigs = [
{
username = "INTEGRATION_TEST"
authMode = PASSWORD
authToken = "INTEGRATION_TEST"
}
]

View File

@ -51,6 +51,7 @@ include 'network-management'
include 'network-management:capsule'
include 'network-management:capsule-hsm'
include 'network-management:capsule-hsm-cert-generator'
include 'network-management:capsule-hsm-crl-generator'
include 'network-management:capsule-crr-submission'
include 'network-management:registration-tool'
include 'tools:jmeter'