Initial implementation of the certificate generation tool (#148)

* Initial implementation of the certificate generation tool

* Adding trust store persisting

* Addressing review comments

* Adding certificate type to the certificate generation process.

* Addressing review comments

* Fixing typos

* Changing keyOverride to 0 in examples and tests

* Addressing review comments + rebasing

* Adding CRL information to the certificate generation process

* Generation tool refactoring

* Addressing review comments
This commit is contained in:
Michal Kit 2018-01-23 11:50:03 +00:00 committed by GitHub
parent d2795954cb
commit 789ce5d44a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 798 additions and 199 deletions

View File

@ -0,0 +1,28 @@
HSM Certificate Generation Tool
===============================
The purpose of the HSM Certificate Generation Tool is to provide means for corda certificate hierarchy generation on an HSM infrastructure.
The tool is intended to be executed automatically, therefore no human interaction is required. All necessary data is fed
to the tool from the provided configuration file.
In the configuration file, the tool expects 2 main parts to be configured:
* HSM device location
* HSM user(s) credentials, that will be used to gain access to the HSM device and to its functionality related to key/certificate generation.
As such, it is assumed that the relevant privileged users are already set-up on the HSM prior to the script execution.
The tool supports multi-user authentication, so it works with more strict policies, where there is a need for distributed privileges.
* Certificate configuration.
The tool is designed to generate a single certificate in one execution. The type of the generated certificate is driven by the certificateType parameter.
Currently in Corda there is a 2-level certificate hierarchy composed of a root certificate and 2 intermediate certificates (one for CSR and one for network map/parameters signing).
Most attributes for those certificates are taken from the configuration file provided to the tool during the execution time. Some of them (e.g. aliases), however,
are fixed in the code due to compatibility aspects.
In addition to the certificate hierarchy creation on the HSM side, the tool stores the created root CA certificate in a trust store, which is intended for distribution across the nodes.
IMPORTANT NOTE:
===============
Caution is required when the tool is used, especially in case where certificate attributes needs to be altered.
Incorrect usage of the tool may result in existing certificates being overriden and possible network corruption.

View File

@ -0,0 +1,87 @@
Running the HSM Certificate Generation tool
===========================================
The purpose of this tool is to facilitate the process of certificate generation on the HSM infrastructure.
See :doc:`hsm-cert-generator` for more details.
See the Readme under ``network-management`` for detailed building instructions.
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
Invoke doorman with ``-?`` for a full list of supported command-line arguments.
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.
:certConfig: Certificate specific configuration. See below section on Certificate Configuration.
:trustStoreDirectory: Path to the directory where the generated trust store should be placed.
The name of the generated file is "truststore.jks".
If the trust store file does not exist, it will be created.
IMPORTANT - This trust store is intended to be distributed across the nodes.
Nodes are hardcoded to use "truststore.jks" file as the trust store name.
As such, it is required that the file name is as the one expected by nodes.
:trustStorePassword: Password for the generated trust store.
Certificate Configuration
-------------------------
:certificateType: Type of the certificate to be created. Allowed values are:
ROOT_CA, INTERMEDIATE_CA, NETWORK_MAP.
:rootPrivateKeyPassword: Private key of the root certificate.
:privateKeyPassword: Private key password to be used during the key generation process.
:subject: X500Name formatted string to be used as the certificate public key subject.
:validDays: Days number for certificate validity.
:crlDistributionUrl: Url to the certificate revocation list of this certificate. If not defined the CRL information will not be added to the certificate.
:crlIssuer: X500 name of the certificate revocation list issuer - e.g. "L=London, C=GB, OU=Org Unit, CN=Service Name". If the crlDistributionUrl configuration option is specified but this parameter is not, then the certificate issuing authority is considered to be the CRL issuer for this certificate.
:keyCurve: Key algorithm curve type. See Utimaco supported values. "NIST-P256" has been used for experiments.
:keyExport: Enables key exporting. 1 for allow, 0 for deny.
:keyGenMechanism: HSM key generation process specific options. In the experiments the integer value being the logic OR of the two following (MECH_KEYGEN_UNCOMP = 4 or MECH_RND_REAL = 0) has been used. See Utimaco documentation for more details.
: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.
:keyGroup: This is an HSM specific parameter that corresponds to key name spacing. See Utimaco documentation for more details.
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 2 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.
:keyFilePassword: Only relevant, if authMode == KEY_FILE. It is the key file password.

View File

@ -30,15 +30,20 @@ Allowed parameters are:
:keyGroup: HSM key group. This parameter is vendor specific (see Utimaco docs). :keyGroup: HSM key group. This parameter is vendor specific (see Utimaco docs).
:keySpecifier: HSM key specifier. This parameter is vendor specific (see Utimaco docs). Default value: 1 :keySpecifier: HSM key specifier. This parameter is vendor specific (see Utimaco docs). Default value: 1.
:rootPrivateKeyPassword: Private key password for the root certificate. :rootPrivateKeyPassword: Private key password for the root certificate.
:rootCertificateName: Root certificate name. Default value: "cordarootca" :rootCertificateName: Root certificate name. Default value: "cordarootca".
:csrPrivateKeyPassword: Private key password for the intermediate certificate used to sign certficate signing requests. :csrPrivateKeyPassword: Private key password for the intermediate certificate used to sign certficate signing requests.
:csrCertificateName: Certificate signing requests intermediate certificate name. Default value: "cordaintermediateca" :csrCertificateName: Certificate signing requests intermediate certificate name. Default value: "cordaintermediateca".
:csrCertCrlDistPoint: Certificate revocation list location for the node CA certificate.
:csrCertCrlIssuer: Certificate revocation list issuer. The expected value is of the X500 name format - e.g. "L=London, C=GB, OU=Org Unit, CN=Service Name".
If not specified, the node CA certificate issuer is considered also as the CRL issuer.
:databaseProperties: Database properties. :databaseProperties: Database properties.

View File

@ -36,6 +36,18 @@ For a list of options the HSM signing server takes, run with the `--help` option
java -jar capsule-hsm/build/libs/hsm-<version>.jar --help java -jar capsule-hsm/build/libs/hsm-<version>.jar --help
``` ```
## HSM Certificate Generator
To build a fat jar containing all the hsm certificate generator code you can simply invoke
```
./gradlew network-management:capsule-hsm-cert-generator:buildHsmCertGeneratorJAR
```
The built file will appear in
```
network-management/capsule-hsm-cert-generator/build/libs/hsm-cert-generator-<VERSION>.jar
```
#Configuring network management service #Configuring network management service
### Local signing ### Local signing

View File

@ -0,0 +1,39 @@
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 buildHsmCertGeneratorJAR(type: FatCapsule, dependsOn: 'jar') {
applicationClass 'com.r3.corda.networkmanage.hsm.generator.MainKt'
archiveName "hsm-cert-generator-${version}.jar"
capsuleManifest {
applicationVersion = corda_release_version
systemProperties['visualvm.display.name'] = 'HSM Certificate Generator'
minJavaVersion = '1.8.0'
jvmArgs = ['-XX:+UseG1GC']
}
applicationSource = files(
project(':network-management').configurations.runtime,
project(':network-management').jar
)
}
artifacts {
runtimeArtifacts buildHsmCertGeneratorJAR
publish buildHsmCertGeneratorJAR
}
jar {
classifier "ignore"
}
publish {
name 'hsm-cert-generator'
disableDefaultJar = true
}

View File

@ -0,0 +1,30 @@
hsmHost = 127.0.0.1
hsmPort = 3001
trustStoreDirectory = "."
trustStorePassword = "trustpass"
certConfig {
subject = "CN=Corda Root, O=R3Cev, L=London, C=GB"
certificateType = ROOT_CA
privateKeyPassword = "PASSWORD"
rootPrivateKeyPassword = "PASSWORD"
validDays = 3650
keyOverride = 0
keyAlgorithm = 4
keyExport = 0
keyCurve = "NIST-P256"
keyGenMechanism = 4
keyGroup = "DEV.DOORMAN"
keySpecifier = 1
storeKeysExternal = false
crlDistributionUrl = "http://r3.com/revoked.crl"
crlIssuer = "CN=Corda Test, O=R3Cev, L=London, C=GB"
}
userConfigs = [
{
username = "INTEGRATION_TEST"
authMode = PASSWORD
authToken = "INTEGRATION_TEST"
}
]

View File

@ -7,6 +7,7 @@ rootCertificateName = "corda_root_ca"
rootPrivateKeyPassword = "Password" rootPrivateKeyPassword = "Password"
csrPrivateKeyPassword = "Password" csrPrivateKeyPassword = "Password"
csrCertificateName = "intermediate_ca" csrCertificateName = "intermediate_ca"
csrCertCrlDistPoint = "http://test.com/revoked.crl"
networkMapCertificateName = "intermediate_ca" networkMapCertificateName = "intermediate_ca"
networkMapPrivateKeyPassword = "Password" networkMapPrivateKeyPassword = "Password"
validDays = 3650 validDays = 3650
@ -16,7 +17,6 @@ authKeyFilePath = "./Administrator.key"
authKeyFilePassword = "Password" authKeyFilePassword = "Password"
autoUsername = "AUTO_USER" autoUsername = "AUTO_USER"
signInterval = 600 signInterval = 600
h2port = 0 h2port = 0
dataSourceProperties { dataSourceProperties {
"dataSourceClassName" = org.h2.jdbcx.JdbcDataSource "dataSourceClassName" = org.h2.jdbcx.JdbcDataSource

View File

@ -33,6 +33,7 @@ data class CryptoUserCredentials(val username: String, val password: String)
class HsmSimulator(private val serverAddress: String = DEFAULT_SERVER_ADDRESS, class HsmSimulator(private val serverAddress: String = DEFAULT_SERVER_ADDRESS,
private val imageRepoTag: String = DEFAULT_IMAGE_REPO_TAG, private val imageRepoTag: String = DEFAULT_IMAGE_REPO_TAG,
private val imageVersion: String = DEFAULT_IMAGE_VERSION, private val imageVersion: String = DEFAULT_IMAGE_VERSION,
private val pullImage: Boolean = DEFAULT_PULL_IMAGE,
private val registryUser: String? = REGISTRY_USERNAME, private val registryUser: String? = REGISTRY_USERNAME,
private val registryPass: String? = REGISTRY_PASSWORD) : ExternalResource() { private val registryPass: String? = REGISTRY_PASSWORD) : ExternalResource() {
@ -40,6 +41,7 @@ class HsmSimulator(private val serverAddress: String = DEFAULT_SERVER_ADDRESS,
val DEFAULT_SERVER_ADDRESS = "corda.azurecr.io" val DEFAULT_SERVER_ADDRESS = "corda.azurecr.io"
val DEFAULT_IMAGE_REPO_TAG = "corda.azurecr.io/network-management/hsm-simulator" val DEFAULT_IMAGE_REPO_TAG = "corda.azurecr.io/network-management/hsm-simulator"
val DEFAULT_IMAGE_VERSION = "latest" val DEFAULT_IMAGE_VERSION = "latest"
val DEFAULT_PULL_IMAGE = true
val HSM_SIMULATOR_PORT = "3001/tcp" val HSM_SIMULATOR_PORT = "3001/tcp"
val CONTAINER_KILL_TIMEOUT_SECONDS = 10 val CONTAINER_KILL_TIMEOUT_SECONDS = 10
@ -63,7 +65,10 @@ class HsmSimulator(private val serverAddress: String = DEFAULT_SERVER_ADDRESS,
override fun before() { override fun before() {
assumeFalse("Docker registry username is not set!. Skipping the test.", registryUser.isNullOrBlank()) assumeFalse("Docker registry username is not set!. Skipping the test.", registryUser.isNullOrBlank())
assumeFalse("Docker registry password is not set!. Skipping the test.", registryPass.isNullOrBlank()) assumeFalse("Docker registry password is not set!. Skipping the test.", registryPass.isNullOrBlank())
docker = DefaultDockerClient.fromEnv().build().pullHsmSimulatorImageFromRepository() docker = DefaultDockerClient.fromEnv().build()
if (pullImage) {
docker.pullHsmSimulatorImageFromRepository()
}
containerId = docker.createContainer() containerId = docker.createContainer()
docker.startHsmSimulatorContainer() docker.startHsmSimulatorContainer()
} }

View File

@ -0,0 +1,117 @@
package com.r3.corda.networkmanage.hsm
import com.r3.corda.networkmanage.HsmSimulator
import com.r3.corda.networkmanage.common.utils.CORDA_NETWORK_MAP
import com.r3.corda.networkmanage.hsm.authentication.CryptoServerProviderConfig
import com.r3.corda.networkmanage.hsm.generator.*
import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_INTERMEDIATE_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.security.cert.X509Certificate
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
class HsmKeyGenerationTest {
companion object {
val KEY_PASSWORD = "PASSWORD"
}
@Rule
@JvmField
val tempFolder = TemporaryFolder()
@Rule
@JvmField
val hsmSimulator: HsmSimulator = HsmSimulator()
private val rootCertParameters: GeneratorParameters by lazy {
GeneratorParameters(
hsmHost = hsmSimulator.host,
hsmPort = hsmSimulator.port,
trustStoreDirectory = tempFolder.root.toPath(),
trustStorePassword = "",
userConfigs = listOf(UserAuthenticationParameters(
username = "INTEGRATION_TEST",
authMode = AuthMode.PASSWORD,
authToken = "INTEGRATION_TEST",
keyFilePassword = null
)),
certConfig = CertificateConfiguration(
keySpecifier = 1,
keyGroup = "DEV.DOORMAN",
storeKeysExternal = false,
privateKeyPassword = KEY_PASSWORD,
rootPrivateKeyPassword = KEY_PASSWORD,
subject = "CN=Corda Root, O=R3Cev, L=London, C=GB",
validDays = 3650,
keyCurve = "NIST-P256",
certificateType = CertificateType.ROOT_CA,
keyExport = 0,
keyGenMechanism = 4,
keyOverride = 0,
crlIssuer = null,
crlDistributionUrl = null
)
)
}
private val providerConfig: CryptoServerProviderConfig by lazy {
CryptoServerProviderConfig(
Device = "${rootCertParameters.hsmPort}@${rootCertParameters.hsmHost}",
KeySpecifier = rootCertParameters.certConfig.keySpecifier,
KeyGroup = rootCertParameters.certConfig.keyGroup,
StoreKeysExternal = rootCertParameters.certConfig.storeKeysExternal)
}
@Test
fun `Authenticator executes the block once user is successfully authenticated`() {
// given
val authenticator = AutoAuthenticator(providerConfig, rootCertParameters.userConfigs)
val rootCertGenerator = KeyCertificateGenerator(rootCertParameters)
// when root cert is created
authenticator.connectAndAuthenticate { provider ->
rootCertGenerator.generate(provider)
// then
val keyStore = HsmX509Utilities.getAndInitializeKeyStore(provider)
val rootCert = keyStore.getCertificate(CORDA_ROOT_CA) as X509Certificate
assertEquals(rootCert.issuerX500Principal, rootCert.subjectX500Principal)
}
// when network map cert is created
val networkMapCertGenerator = KeyCertificateGenerator(rootCertParameters.copy(
certConfig = rootCertParameters.certConfig.copy(
certificateType = CertificateType.NETWORK_MAP,
subject = "CN=Corda NM, O=R3Cev, L=London, C=GB"
)
))
authenticator.connectAndAuthenticate { provider ->
networkMapCertGenerator.generate(provider)
// then
val keyStore = HsmX509Utilities.getAndInitializeKeyStore(provider)
val rootCert = keyStore.getCertificate(CORDA_ROOT_CA) as X509Certificate
val networkMapCert = keyStore.getCertificate(CORDA_NETWORK_MAP) as X509Certificate
assertNotNull(networkMapCert)
assertEquals(rootCert.subjectX500Principal, networkMapCert.issuerX500Principal)
}
// when csr cert is created
val csrCertGenerator = KeyCertificateGenerator(rootCertParameters.copy(
certConfig = rootCertParameters.certConfig.copy(
certificateType = CertificateType.INTERMEDIATE_CA,
subject = "CN=Corda CSR, O=R3Cev, L=London, C=GB"
)
))
authenticator.connectAndAuthenticate { provider ->
csrCertGenerator.generate(provider)
// then
val keyStore = HsmX509Utilities.getAndInitializeKeyStore(provider)
val rootCert = keyStore.getCertificate(CORDA_ROOT_CA) as X509Certificate
val csrCert = keyStore.getCertificate(CORDA_INTERMEDIATE_CA) as X509Certificate
assertNotNull(csrCert)
assertEquals(rootCert.subjectX500Principal, csrCert.issuerX500Principal)
}
}
}

View File

@ -33,7 +33,8 @@ class HsmTest {
networkMapPrivateKeyPassword = "", networkMapPrivateKeyPassword = "",
rootPrivateKeyPassword = "", rootPrivateKeyPassword = "",
keyGroup = "DEV.DOORMAN", keyGroup = "DEV.DOORMAN",
validDays = 3650 validDays = 3650,
csrCertCrlDistPoint = "http://test.com/revoked.crl"
) )
private lateinit var inputReader: InputReader private lateinit var inputReader: InputReader

View File

@ -16,6 +16,8 @@ import java.security.PublicKey
import java.security.cert.CertPath import java.security.cert.CertPath
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
const val CORDA_NETWORK_MAP = "cordanetworkmap"
// TODO These should be defined in node-api // TODO These should be defined in node-api
typealias SignedNetworkParameters = SignedDataWithCert<NetworkParameters> typealias SignedNetworkParameters = SignedDataWithCert<NetworkParameters>
typealias SignedNetworkMap = SignedDataWithCert<NetworkMap> typealias SignedNetworkMap = SignedDataWithCert<NetworkMap>

View File

@ -3,11 +3,11 @@ package com.r3.corda.networkmanage.doorman
import com.r3.corda.networkmanage.common.persistence.CertificationRequestStorage import com.r3.corda.networkmanage.common.persistence.CertificationRequestStorage
import com.r3.corda.networkmanage.common.persistence.CertificationRequestStorage.Companion.DOORMAN_SIGNATURE import com.r3.corda.networkmanage.common.persistence.CertificationRequestStorage.Companion.DOORMAN_SIGNATURE
import com.r3.corda.networkmanage.common.persistence.configureDatabase import com.r3.corda.networkmanage.common.persistence.configureDatabase
import com.r3.corda.networkmanage.common.utils.CORDA_NETWORK_MAP
import com.r3.corda.networkmanage.common.utils.CertPathAndKey import com.r3.corda.networkmanage.common.utils.CertPathAndKey
import com.r3.corda.networkmanage.common.utils.ShowHelpException import com.r3.corda.networkmanage.common.utils.ShowHelpException
import com.r3.corda.networkmanage.doorman.signer.LocalSigner import com.r3.corda.networkmanage.doorman.signer.LocalSigner
import com.r3.corda.networkmanage.hsm.configuration.Parameters.Companion.DEFAULT_CSR_CERTIFICATE_NAME import com.r3.corda.networkmanage.hsm.configuration.Parameters.Companion.DEFAULT_CSR_CERTIFICATE_NAME
import com.r3.corda.networkmanage.hsm.configuration.Parameters.Companion.DEFAULT_NETWORK_MAP_CERTIFICATE_NAME
import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SignatureScheme import net.corda.core.crypto.SignatureScheme
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
@ -127,7 +127,7 @@ fun generateSigningKeyPairs(keystoreFile: Path, rootStoreFile: Path, rootKeystor
X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
storeCertIfAbsent( storeCertIfAbsent(
DEFAULT_NETWORK_MAP_CERTIFICATE_NAME, CORDA_NETWORK_MAP,
CertificateType.NETWORK_MAP, CertificateType.NETWORK_MAP,
X500Principal("CN=Corda Network Map,OU=Corda,O=R3 Ltd,L=London,C=GB"), X500Principal("CN=Corda Network Map,OU=Corda,O=R3 Ltd,L=London,C=GB"),
Crypto.EDDSA_ED25519_SHA512) Crypto.EDDSA_ED25519_SHA512)
@ -149,7 +149,7 @@ private fun processKeyStore(parameters: NetworkManagementServerParameters): Pair
) )
} }
val networkMapSigner = LocalSigner(keyStore.getCertificateAndKeyPair(DEFAULT_NETWORK_MAP_CERTIFICATE_NAME, privateKeyPassword)) val networkMapSigner = LocalSigner(keyStore.getCertificateAndKeyPair(CORDA_NETWORK_MAP, privateKeyPassword))
return Pair(csrCertPathAndKey, networkMapSigner) return Pair(csrCertPathAndKey, networkMapSigner)
} }

View File

@ -11,8 +11,6 @@ import com.r3.corda.networkmanage.hsm.authentication.Authenticator
import com.r3.corda.networkmanage.hsm.authentication.createProvider import com.r3.corda.networkmanage.hsm.authentication.createProvider
import com.r3.corda.networkmanage.hsm.configuration.Parameters import com.r3.corda.networkmanage.hsm.configuration.Parameters
import com.r3.corda.networkmanage.hsm.configuration.parseParameters import com.r3.corda.networkmanage.hsm.configuration.parseParameters
import com.r3.corda.networkmanage.hsm.generator.CertificateNameAndPass
import com.r3.corda.networkmanage.hsm.generator.KeyCertificateGenerator
import com.r3.corda.networkmanage.hsm.menu.Menu import com.r3.corda.networkmanage.hsm.menu.Menu
import com.r3.corda.networkmanage.hsm.persistence.ApprovedCertificateRequestData import com.r3.corda.networkmanage.hsm.persistence.ApprovedCertificateRequestData
import com.r3.corda.networkmanage.hsm.persistence.DBSignedCertificateRequestStorage import com.r3.corda.networkmanage.hsm.persistence.DBSignedCertificateRequestStorage
@ -59,7 +57,6 @@ fun run(parameters: Parameters) {
val database = configureDatabase(dataSourceProperties, databaseConfig) val database = configureDatabase(dataSourceProperties, databaseConfig)
val csrStorage = DBSignedCertificateRequestStorage(database) val csrStorage = DBSignedCertificateRequestStorage(database)
val hsmSigner = HsmNetworkMapSigner( val hsmSigner = HsmNetworkMapSigner(
networkMapCertificateName,
networkMapPrivateKeyPassword, networkMapPrivateKeyPassword,
Authenticator(createProvider(), AuthMode.KEY_FILE, autoUsername, authKeyFilePath, authKeyFilePassword, signAuthThreshold)) Authenticator(createProvider(), AuthMode.KEY_FILE, autoUsername, authKeyFilePath, authKeyFilePassword, signAuthThreshold))
@ -72,25 +69,14 @@ fun run(parameters: Parameters) {
csrStorage, csrStorage,
csrCertificateName, csrCertificateName,
csrPrivateKeyPassword, csrPrivateKeyPassword,
csrCertCrlDistPoint,
csrCertCrlIssuer,
rootCertificateName, rootCertificateName,
validDays, validDays,
Authenticator(createProvider(), authMode, autoUsername, authKeyFilePath, authKeyFilePassword, signAuthThreshold)) Authenticator(createProvider(), authMode, autoUsername, authKeyFilePath, authKeyFilePassword, signAuthThreshold))
signer.sign(it) signer.sign(it)
} }
Menu().withExceptionHandler(::processError).addItem("1", "Sign all approved and unsigned CSRs", {
Menu().withExceptionHandler(::processError).addItem("1", "Generate root and intermediate certificates", {
if (confirmedKeyGen()) {
val generator = KeyCertificateGenerator(
Authenticator(createProvider(), authMode, autoUsername, authKeyFilePath, authKeyFilePassword, keyGenAuthThreshold),
keySpecifier,
keyGroup)
generator.generateAllCertificates(
listOf(CertificateNameAndPass(csrCertificateName, csrPrivateKeyPassword), CertificateNameAndPass(networkMapCertificateName, networkMapPrivateKeyPassword)),
rootCertificateName,
rootPrivateKeyPassword,
validDays)
}
}).addItem("2", "Sign all approved and unsigned CSRs", {
val approved = csrStorage.getApprovedRequests() val approved = csrStorage.getApprovedRequests()
if (approved.isNotEmpty()) { if (approved.isNotEmpty()) {
if (confirmedSign(approved)) { if (confirmedSign(approved)) {
@ -99,7 +85,7 @@ fun run(parameters: Parameters) {
} else { } else {
println("There is no approved CSR") println("There is no approved CSR")
} }
}).addItem("3", "List all approved and unsigned CSRs", { }).addItem("2", "List all approved and unsigned CSRs", {
val approved = csrStorage.getApprovedRequests() val approved = csrStorage.getApprovedRequests()
if (approved.isNotEmpty()) { if (approved.isNotEmpty()) {
println("Approved CSRs:") println("Approved CSRs:")

View File

@ -19,13 +19,15 @@ import java.util.*
data class Parameters(val dataSourceProperties: Properties, data class Parameters(val dataSourceProperties: Properties,
val databaseConfig: DatabaseConfig = DatabaseConfig(), val databaseConfig: DatabaseConfig = DatabaseConfig(),
val device: String = DEFAULT_DEVICE, val device: String = DEFAULT_DEVICE,
// TODO this needs cleaning up after the config-file-only support is implemented // TODO this needs cleaning up after the config-file-only support is implemented
val keyGroup: String, val keyGroup: String,
val keySpecifier: Int = DEFAULT_KEY_SPECIFIER, val keySpecifier: Int = DEFAULT_KEY_SPECIFIER,
val rootPrivateKeyPassword: String, val rootPrivateKeyPassword: String,
val csrPrivateKeyPassword: String, val csrPrivateKeyPassword: String,
val csrCertificateName: String = DEFAULT_CSR_CERTIFICATE_NAME, val csrCertificateName: String = DEFAULT_CSR_CERTIFICATE_NAME,
val networkMapCertificateName: String = DEFAULT_NETWORK_MAP_CERTIFICATE_NAME, val csrCertCrlDistPoint: String,
val csrCertCrlIssuer: String? = DEFAULT_CSR_CERT_CRL_ISSUER, // X500 name of the issuing authority e.g. "L=New York, C=US, OU=Org Unit, CN=Service Name",
// if null parent CA is is considered as an issuer.
val networkMapPrivateKeyPassword: String, val networkMapPrivateKeyPassword: String,
val rootCertificateName: String = DEFAULT_ROOT_CERTIFICATE_NAME, val rootCertificateName: String = DEFAULT_ROOT_CERTIFICATE_NAME,
val validDays: Int, val validDays: Int,
@ -35,7 +37,7 @@ data class Parameters(val dataSourceProperties: Properties,
val authKeyFilePath: Path? = DEFAULT_KEY_FILE_PATH, val authKeyFilePath: Path? = DEFAULT_KEY_FILE_PATH,
val authKeyFilePassword: String? = DEFAULT_KEY_FILE_PASSWORD, val authKeyFilePassword: String? = DEFAULT_KEY_FILE_PASSWORD,
val autoUsername: String? = DEFAULT_AUTO_USERNAME, val autoUsername: String? = DEFAULT_AUTO_USERNAME,
// TODO Change this to Duration in the future. // TODO Change this to Duration in the future.
val signInterval: Long = DEFAULT_SIGN_INTERVAL) { val signInterval: Long = DEFAULT_SIGN_INTERVAL) {
companion object { companion object {
val DEFAULT_DEVICE = "3001@127.0.0.1" val DEFAULT_DEVICE = "3001@127.0.0.1"
@ -48,8 +50,8 @@ data class Parameters(val dataSourceProperties: Properties,
val DEFAULT_KEY_FILE_PATH: Path? = null //Paths.get("/Users/michalkit/WinDev1706Eval/Shared/TEST4.key") val DEFAULT_KEY_FILE_PATH: Path? = null //Paths.get("/Users/michalkit/WinDev1706Eval/Shared/TEST4.key")
val DEFAULT_KEY_FILE_PASSWORD: String? = null val DEFAULT_KEY_FILE_PASSWORD: String? = null
val DEFAULT_AUTO_USERNAME: String? = null val DEFAULT_AUTO_USERNAME: String? = null
val DEFAULT_NETWORK_MAP_CERTIFICATE_NAME = "cordaintermediateca_nm" // TODO Change the value to "cordanetworkmap" since this is not a CA
val DEFAULT_SIGN_INTERVAL = 600L // in seconds (10 minutes) val DEFAULT_SIGN_INTERVAL = 600L // in seconds (10 minutes)
val DEFAULT_CSR_CERT_CRL_ISSUER: String? = null
} }
} }

View File

@ -0,0 +1,38 @@
package com.r3.corda.networkmanage.hsm.generator
import CryptoServerJCE.CryptoServerProvider
import com.r3.corda.networkmanage.hsm.authentication.CryptoServerProviderConfig
import com.r3.corda.networkmanage.hsm.authentication.createProvider
/**
* Performs user authentication against the HSM
*/
class AutoAuthenticator(providerConfig: CryptoServerProviderConfig,
private val userConfigs: List<UserAuthenticationParameters>) {
private val provider = createProvider(providerConfig)
/**
* Interactively (using console) authenticates a user against the HSM. Once authentication is completed successfully
* the [block] is executed.
* @param block to be executed once the authentication process succeeds. The block should take a [CryptoServerProvider] instance as the parameter.
*/
fun connectAndAuthenticate(block: (CryptoServerProvider) -> Unit) {
try {
for (userConfig in userConfigs) {
when(userConfig.authMode) {
AuthMode.PASSWORD -> provider.loginPassword(userConfig.username, userConfig.authToken)
AuthMode.CARD_READER -> provider.loginSign(userConfig.username, ":cs2:cyb:USB0", null)
AuthMode.KEY_FILE -> provider.loginSign(userConfig.username, userConfig.keyFilePassword, userConfig.authToken)
}
}
block(provider)
} finally {
try {
provider.logoff()
} catch (throwable: Throwable) {
println("WARNING Exception while logging off")
throwable.printStackTrace(System.out)
}
}
}
}

View File

@ -0,0 +1,95 @@
package com.r3.corda.networkmanage.hsm.generator
import com.r3.corda.networkmanage.common.utils.ShowHelpException
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import joptsimple.OptionParser
import net.corda.core.internal.isRegularFile
import net.corda.nodeapi.internal.config.parseAs
import net.corda.nodeapi.internal.crypto.CertificateType
import java.nio.file.Path
import java.nio.file.Paths
/**
* 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.
*/
data class GeneratorParameters(val hsmHost: String,
val hsmPort: Int,
val trustStoreDirectory: Path,
val trustStorePassword: String,
val userConfigs: List<UserAuthenticationParameters>,
val certConfig: CertificateConfiguration)
/**
* Holds certificate specific configuration.
*/
data class CertificateConfiguration(val keyGroup: String,
val keySpecifier: Int,
val storeKeysExternal: Boolean,
val certificateType: CertificateType,
val rootPrivateKeyPassword: String,
val privateKeyPassword: String,
val subject: String, // it is certificate [X500Name] subject
val validDays: Int,
val crlDistributionUrl: String?,
val crlIssuer: String?, // X500 name of the issuing authority e.g. "L=New York, C=US, OU=Org Unit, CN=Service Name"
val keyOverride: Int, // 1 for allow and 0 deny
val keyExport: Int, // 1 for allow, 0 for deny
val keyCurve: String, // we use "NIST-P256", check Utimaco docs for other options
val keyGenMechanism: Int) // MECH_KEYGEN_UNCOMP = 4 or MECH_RND_REAL = 0
/**
* Holds arguments for command line options.
*/
data class CommandLineOptions(val configFile: Path) {
init {
check(configFile.isRegularFile()) { "Config file $configFile does not exist" }
}
}
/**
* Parses key generator command line options.
*/
fun parseCommandLine(vararg args: String): CommandLineOptions {
val optionParser = OptionParser()
val configFileArg = optionParser
.accepts("config-file", "The path to the config file")
.withRequiredArg()
.describedAs("filepath")
val helpOption = optionParser.acceptsAll(listOf("h", "?", "help"), "show help").forHelp();
val optionSet = optionParser.parse(*args)
// Print help and exit on help option or if there are missing options.
if (optionSet.has(helpOption) || !optionSet.has(configFileArg)) {
throw ShowHelpException(optionParser)
}
val configFile = Paths.get(optionSet.valueOf(configFileArg)).toAbsolutePath()
return CommandLineOptions(configFile)
}
/**
* Parses a configuration file, which contains all the configuration - i.e. for user and certificate parameters.
*/
fun parseParameters(configFile: Path): GeneratorParameters {
return ConfigFactory
.parseFile(configFile.toFile(), ConfigParseOptions.defaults().setAllowMissing(true))
.resolve()
.parseAs()
}

View File

@ -1,101 +1,116 @@
package com.r3.corda.networkmanage.hsm.generator package com.r3.corda.networkmanage.hsm.generator
import CryptoServerCXI.CryptoServerCXI import CryptoServerCXI.CryptoServerCXI.KEY_ALGO_ECDSA
import CryptoServerCXI.CryptoServerCXI.KeyAttributes
import CryptoServerJCE.CryptoServerProvider import CryptoServerJCE.CryptoServerProvider
import com.r3.corda.networkmanage.hsm.authentication.Authenticator import com.r3.corda.networkmanage.common.utils.CORDA_NETWORK_MAP
import com.r3.corda.networkmanage.hsm.utils.X509Utilities.createIntermediateCert import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.createIntermediateCert
import com.r3.corda.networkmanage.hsm.utils.X509Utilities.createSelfSignedCACert import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.createSelfSignedCACert
import com.r3.corda.networkmanage.hsm.utils.X509Utilities.getAndInitializeKeyStore import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.getAndInitializeKeyStore
import com.r3.corda.networkmanage.hsm.utils.X509Utilities.getCleanEcdsaKeyPair import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.getCleanEcdsaKeyPair
import com.r3.corda.networkmanage.hsm.utils.X509Utilities.retrieveCertificateAndKeys import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.retrieveKeysAndCertificateChain
import net.corda.nodeapi.internal.crypto.addOrReplaceKey import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.div
import net.corda.core.internal.isDirectory
import net.corda.core.internal.x500Name
import net.corda.core.utilities.contextLogger
import net.corda.nodeapi.internal.crypto.*
import net.corda.nodeapi.internal.crypto.CertificateType.*
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_INTERMEDIATE_CA
import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA
import java.nio.file.Path
import java.security.KeyPair import java.security.KeyPair
import java.security.KeyStore import java.security.KeyStore
import java.security.PrivateKey import java.security.PrivateKey
import java.security.cert.X509Certificate
data class CertificateNameAndPass(val certificateName: String, val privateKeyPassword: String) data class CertificateNameAndPass(val certificateName: String, val privateKeyPassword: String)
/** /**
* Encapsulates logic for root and intermediate key/certificate generation. * Encapsulates logic for key and certificate generation.
*
*/ */
class KeyCertificateGenerator(private val authenticator: Authenticator, class KeyCertificateGenerator(private val parameters: GeneratorParameters) {
private val keySpecifier: Int, companion object {
private val keyGroup: String) { val logger = contextLogger()
/** }
* Generates root and intermediate key and certificates and stores them in the key store given by provider.
* If the keys and certificates already exists they will be overwritten. fun generate(provider: CryptoServerProvider) {
* @param intermediateCertificatesCredentials name and password for the intermediate key/certificate parameters.run {
* @param parentCertificateName name of the parent key/certificate require(trustStoreDirectory.isDirectory()) { "trustStoreDirectory must point to a directory." }
* @param parentPrivateKeyPassword password for the parent private key val keyName = when (certConfig.certificateType) {
* @param validDays days of certificate validity ROOT_CA -> CORDA_ROOT_CA
*/ INTERMEDIATE_CA -> CORDA_INTERMEDIATE_CA
fun generateAllCertificates(intermediateCertificatesCredentials: List<CertificateNameAndPass>, NETWORK_MAP -> CORDA_NETWORK_MAP
parentCertificateName: String, else -> throw IllegalArgumentException("Invalid certificate type. ${certConfig.certificateType}")
parentPrivateKeyPassword: String,
validDays: Int) {
authenticator.connectAndAuthenticate { provider, _ ->
val keyStore = getAndInitializeKeyStore(provider)
generateRootCertificate(provider, keyStore, parentCertificateName, parentPrivateKeyPassword, validDays)
intermediateCertificatesCredentials.forEach {
generateIntermediateCertificate(provider, keyStore, it.certificateName, it.privateKeyPassword, parentCertificateName, parentPrivateKeyPassword, validDays)
} }
val keyStore = getAndInitializeKeyStore(provider)
val keyPair = certConfig.generateEcdsaKeyPair(keyName, provider, keyStore)
val certChain = if (certConfig.certificateType == ROOT_CA) {
certConfig.generateRootCert(provider, keyPair, trustStoreDirectory, trustStorePassword)
} else {
certConfig.generateIntermediateCert(provider, keyPair, keyStore)
}
keyStore.addOrReplaceKey(keyName, keyPair.private, certConfig.privateKeyPassword.toCharArray(), certChain)
logger.info("New certificate and key pair named $keyName have been generated and stored in HSM")
} }
} }
/** private fun CertificateConfiguration.generateRootCert(provider: CryptoServerProvider,
* Generates a root certificate keyPair: KeyPair,
*/ trustStoreDirectory: Path,
private fun generateRootCertificate(provider: CryptoServerProvider, trustStorePassword: String): Array<X509Certificate> {
keyStore: KeyStore, val certificate = createSelfSignedCACert(ROOT_CA,
certificateKeyName: String, CordaX500Name.parse(subject).x500Name,
privateKeyPassword: String, keyPair,
validDays: Int) { validDays,
val keyPair = generateEcdsaKeyPair(provider, keyStore, certificateKeyName, privateKeyPassword) provider,
val selfSignedRootCertificate = createSelfSignedCACert("R3", keyPair, validDays, provider).certificate crlDistributionUrl,
keyStore.addOrReplaceKey(certificateKeyName, keyPair.private, privateKeyPassword.toCharArray(), arrayOf(selfSignedRootCertificate)) crlIssuer).certificate
println("New certificate and key pair named $certificateKeyName have been generated") val trustStorePath = trustStoreDirectory / "truststore.jks"
val trustStore = loadOrCreateKeyStore(trustStorePath, trustStorePassword)
logger.info("Trust store for distribution to nodes created in $trustStore")
trustStore.addOrReplaceCertificate(CORDA_ROOT_CA, certificate)
logger.info("Certificate $CORDA_ROOT_CA has been added to $trustStore")
trustStore.save(trustStorePath, trustStorePassword)
logger.info("Trust store has been persisted. Ready for distribution.")
return arrayOf(certificate)
} }
/** private fun CertificateConfiguration.generateIntermediateCert(
* Generates an intermediate certificate provider: CryptoServerProvider,
*/ keyPair: KeyPair,
private fun generateIntermediateCertificate(provider: CryptoServerProvider, keyStore: KeyStore): Array<X509Certificate> {
keyStore: KeyStore, val rootKeysAndCertChain = retrieveKeysAndCertificateChain(CORDA_ROOT_CA,
certificateKeyName: String, rootPrivateKeyPassword,
privateKeyPassword: String, keyStore)
parentCertificateName: String, val certificateAndKeyPair = createIntermediateCert(
parentPrivateKeyPassword: String, certificateType,
validDays: Int) { CordaX500Name.parse(subject).x500Name,
val parentCACertKey = retrieveCertificateAndKeys(parentCertificateName, parentPrivateKeyPassword, keyStore) CertificateAndKeyPair(rootKeysAndCertChain.certificateChain.first(), rootKeysAndCertChain.keyPair),
val keyPair = generateEcdsaKeyPair(provider, keyStore, certificateKeyName, privateKeyPassword) keyPair,
val intermediateCertificate = createIntermediateCert("R3 Intermediate", parentCACertKey, keyPair, validDays, provider) validDays,
keyStore.addOrReplaceKey(certificateKeyName, keyPair.private, privateKeyPassword.toCharArray(), arrayOf(intermediateCertificate.certificate, parentCACertKey.certificate)) provider,
println("New certificate and key pair named $certificateKeyName have been generated") crlDistributionUrl,
crlIssuer)
return arrayOf(certificateAndKeyPair.certificate, *rootKeysAndCertChain.certificateChain)
} }
private fun generateECDSAKey(keySpecifier: Int, keyName: String, keyGroup: String, provider: CryptoServerProvider, overwrite: Boolean = true) { private fun CertificateConfiguration.generateECDSAKey(keyName: String, provider: CryptoServerProvider) {
val generateFlag = if (overwrite) { val keyAttributes = KeyAttributes()
println("!!! WARNING: OVERWRITING KEY NAMED $keyName !!!")
CryptoServerCXI.FLAG_OVERWRITE
} else {
0
}
val keyAttributes = CryptoServerCXI.KeyAttributes()
keyAttributes.apply { keyAttributes.apply {
algo = CryptoServerCXI.KEY_ALGO_ECDSA algo = KEY_ALGO_ECDSA
group = keyGroup group = keyGroup
specifier = keySpecifier specifier = keySpecifier
export = 0 // deny export export = keyExport
name = keyName name = keyName
setCurve("NIST-P256") setCurve(keyCurve)
} }
println("Generating key...") logger.info("Generating key $keyName")
val mechanismFlag = CryptoServerCXI.MECH_RND_REAL or CryptoServerCXI.MECH_KEYGEN_UNCOMP provider.cryptoServer.generateKey(keyOverride, keyAttributes, keyGenMechanism)
provider.cryptoServer.generateKey(generateFlag, keyAttributes, mechanismFlag)
} }
private fun generateEcdsaKeyPair(provider: CryptoServerProvider, keyStore: KeyStore, keyName: String, privateKeyPassword: String): KeyPair { private fun CertificateConfiguration.generateEcdsaKeyPair(keyName: String, provider: CryptoServerProvider, keyStore: KeyStore): KeyPair {
generateECDSAKey(keySpecifier, keyName, keyGroup, provider) generateECDSAKey(keyName, provider)
val privateKey = keyStore.getKey(keyName, privateKeyPassword.toCharArray()) as PrivateKey val privateKey = keyStore.getKey(keyName, privateKeyPassword.toCharArray()) as PrivateKey
val publicKey = keyStore.getCertificate(keyName).publicKey val publicKey = keyStore.getCertificate(keyName).publicKey
return getCleanEcdsaKeyPair(publicKey, privateKey) return getCleanEcdsaKeyPair(publicKey, privateKey)

View File

@ -0,0 +1,28 @@
package com.r3.corda.networkmanage.hsm.generator
import com.r3.corda.networkmanage.hsm.authentication.CryptoServerProviderConfig
import com.r3.corda.networkmanage.hsm.utils.mapCryptoServerException
import org.apache.logging.log4j.LogManager
import java.nio.file.Paths
private val log = LogManager.getLogger("com.r3.corda.networkmanage.hsm.generator.Main")
fun main(args: Array<String>) {
val commandLineOptions = parseCommandLine(*args)
parseParameters(commandLineOptions.configFile).run {
val providerConfig = CryptoServerProviderConfig(
Device = "$hsmPort@$hsmHost",
KeySpecifier = certConfig.keySpecifier,
KeyGroup = certConfig.keyGroup,
StoreKeysExternal = certConfig.storeKeysExternal)
try {
val authenticator = AutoAuthenticator(providerConfig, userConfigs)
authenticator.connectAndAuthenticate { provider ->
val generator = KeyCertificateGenerator(this)
generator.generate(provider)
}
} catch (e: Exception) {
log.error(mapCryptoServerException(e))
}
}
}

View File

@ -3,10 +3,11 @@ package com.r3.corda.networkmanage.hsm.signer
import com.r3.corda.networkmanage.hsm.authentication.Authenticator import com.r3.corda.networkmanage.hsm.authentication.Authenticator
import com.r3.corda.networkmanage.hsm.persistence.ApprovedCertificateRequestData import com.r3.corda.networkmanage.hsm.persistence.ApprovedCertificateRequestData
import com.r3.corda.networkmanage.hsm.persistence.SignedCertificateRequestStorage import com.r3.corda.networkmanage.hsm.persistence.SignedCertificateRequestStorage
import com.r3.corda.networkmanage.hsm.utils.X509Utilities.buildCertPath import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.buildCertPath
import com.r3.corda.networkmanage.hsm.utils.X509Utilities.createClientCertificate import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.createClientCertificate
import com.r3.corda.networkmanage.hsm.utils.X509Utilities.getAndInitializeKeyStore import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.getAndInitializeKeyStore
import com.r3.corda.networkmanage.hsm.utils.X509Utilities.retrieveCertificateAndKeys import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.retrieveCertificateAndKeys
import net.corda.nodeapi.internal.crypto.CertificateType
/** /**
* Encapsulates certificate signing logic * Encapsulates certificate signing logic
@ -14,6 +15,8 @@ import com.r3.corda.networkmanage.hsm.utils.X509Utilities.retrieveCertificateAnd
class HsmCsrSigner(private val storage: SignedCertificateRequestStorage, class HsmCsrSigner(private val storage: SignedCertificateRequestStorage,
private val intermediateCertAlias: String, private val intermediateCertAlias: String,
private val intermediateCertPrivateKeyPass: String?, private val intermediateCertPrivateKeyPass: String?,
private val csrCertCrlDistPoint: String,
private val csrCertCrlIssuer: String?,
private val rootCertAlias: String, private val rootCertAlias: String,
private val validDays: Int, private val validDays: Int,
private val authenticator: Authenticator) : CertificateSigningRequestSigner { private val authenticator: Authenticator) : CertificateSigningRequestSigner {
@ -37,10 +40,14 @@ class HsmCsrSigner(private val storage: SignedCertificateRequestStorage,
val intermediatePrivateKeyPass = intermediateCertPrivateKeyPass ?: authenticator.readPassword("CA Private Key Password: ") val intermediatePrivateKeyPass = intermediateCertPrivateKeyPass ?: authenticator.readPassword("CA Private Key Password: ")
val intermediateCertAndKey = retrieveCertificateAndKeys(intermediateCertAlias, intermediatePrivateKeyPass, keyStore) val intermediateCertAndKey = retrieveCertificateAndKeys(intermediateCertAlias, intermediatePrivateKeyPass, keyStore)
toSign.forEach { toSign.forEach {
it.certPath = buildCertPath( it.certPath = buildCertPath(createClientCertificate(
createClientCertificate(intermediateCertAndKey, it.request, validDays, provider), CertificateType.NODE_CA,
intermediateCertAndKey.certificate, intermediateCertAndKey,
rootCert) it.request,
validDays,
provider,
csrCertCrlDistPoint,
csrCertCrlIssuer), rootCert)
} }
storage.store(toSign, signers) storage.store(toSign, signers)
println("The following certificates have been signed by $signers:") println("The following certificates have been signed by $signers:")

View File

@ -1,10 +1,11 @@
package com.r3.corda.networkmanage.hsm.signer package com.r3.corda.networkmanage.hsm.signer
import com.r3.corda.networkmanage.common.signer.Signer 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.authentication.Authenticator
import com.r3.corda.networkmanage.hsm.utils.X509Utilities import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities
import com.r3.corda.networkmanage.hsm.utils.X509Utilities.getAndInitializeKeyStore import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.getAndInitializeKeyStore
import com.r3.corda.networkmanage.hsm.utils.X509Utilities.verify import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.verify
import net.corda.core.internal.DigitalSignatureWithCert import net.corda.core.internal.DigitalSignatureWithCert
import net.corda.nodeapi.internal.crypto.getX509Certificate import net.corda.nodeapi.internal.crypto.getX509Certificate
import java.security.PrivateKey import java.security.PrivateKey
@ -14,8 +15,7 @@ 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] to sign bytes.
*/ */
// TODO Rename this to HsmSigner // TODO Rename this to HsmSigner
class HsmNetworkMapSigner(private val certificateKeyName: String, class HsmNetworkMapSigner(private val privateKeyPassword: String,
private val privateKeyPassword: String,
private val authenticator: Authenticator) : Signer { private val authenticator: Authenticator) : Signer {
/** /**
* Signs given data using [CryptoServerJCE.CryptoServerProvider], which connects to the underlying HSM. * Signs given data using [CryptoServerJCE.CryptoServerProvider], which connects to the underlying HSM.
@ -23,11 +23,11 @@ class HsmNetworkMapSigner(private val certificateKeyName: String,
override fun signBytes(data: ByteArray): DigitalSignatureWithCert { override fun signBytes(data: ByteArray): DigitalSignatureWithCert {
return authenticator.connectAndAuthenticate { provider, _ -> return authenticator.connectAndAuthenticate { provider, _ ->
val keyStore = getAndInitializeKeyStore(provider) val keyStore = getAndInitializeKeyStore(provider)
val certificate = keyStore.getX509Certificate(certificateKeyName) 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 // 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. // when used with the given provider.
val key = keyStore.getKey(certificateKeyName, privateKeyPassword.toCharArray()) as PrivateKey val key = keyStore.getKey(CORDA_NETWORK_MAP, privateKeyPassword.toCharArray()) as PrivateKey
val signature = Signature.getInstance(X509Utilities.SIGNATURE_ALGORITHM, provider).run { val signature = Signature.getInstance(HsmX509Utilities.SIGNATURE_ALGORITHM, provider).run {
initSign(key) initSign(key)
update(data) update(data)
sign() sign()

View File

@ -1,24 +1,24 @@
package com.r3.corda.networkmanage.hsm.utils package com.r3.corda.networkmanage.hsm.utils
import CryptoServerJCE.CryptoServerProvider import CryptoServerJCE.CryptoServerProvider
import net.corda.core.CordaOID
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.x500Name import net.corda.core.internal.x500Name
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.getX509Certificate import net.corda.nodeapi.internal.crypto.getX509Certificate
import net.corda.nodeapi.internal.crypto.toJca
import org.bouncycastle.asn1.ASN1EncodableVector import org.bouncycastle.asn1.ASN1EncodableVector
import org.bouncycastle.asn1.ASN1ObjectIdentifier
import org.bouncycastle.asn1.ASN1Sequence import org.bouncycastle.asn1.ASN1Sequence
import org.bouncycastle.asn1.DERSequence import org.bouncycastle.asn1.DERSequence
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x500.X500NameBuilder
import org.bouncycastle.asn1.x500.style.BCStyle
import org.bouncycastle.asn1.x509.* import org.bouncycastle.asn1.x509.*
import org.bouncycastle.cert.X509CertificateHolder import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.cert.X509v3CertificateBuilder import org.bouncycastle.cert.X509v3CertificateBuilder
import org.bouncycastle.cert.bc.BcX509ExtensionUtils import org.bouncycastle.cert.bc.BcX509ExtensionUtils
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
import org.bouncycastle.pkcs.PKCS10CertificationRequest import org.bouncycastle.pkcs.PKCS10CertificationRequest
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
@ -32,47 +32,48 @@ import java.time.Instant
import java.time.temporal.ChronoUnit import java.time.temporal.ChronoUnit
import java.util.* import java.util.*
object X509Utilities { object HsmX509Utilities {
val SIGNATURE_ALGORITHM = "SHA256withECDSA" val SIGNATURE_ALGORITHM = "SHA256withECDSA"
/** /**
* Create a de novo root self-signed X509 v3 CA cert for the specified [KeyPair]. * Create a de novo root self-signed X509 v3 CA cert for the specified [KeyPair].
* @param legalName The Common (CN) field of the cert Subject will be populated with the domain string * @param type type of the certificate to be created
* @param subject X500 name of the certificate subject
* @param keyPair public and private keys to be associated with the generated certificate * @param keyPair public and private keys to be associated with the generated certificate
* @param validDays number of days which this certificate is valid for * @param validDays number of days which this certificate is valid for
* @param provider provider to be used during the certificate signing process * @param provider provider to be used during the certificate signing process
* @param crlDistPoint url to the certificate revocation list of this certificate
* @param crlIssuer issuer of the certificate revocation list of this certificate
* @return an instance of [CertificateAndKeyPair] class is returned containing the new root CA Cert and its [KeyPair] for signing downstream certificates. * @return an instance of [CertificateAndKeyPair] class is returned containing the new root CA Cert and its [KeyPair] for signing downstream certificates.
* Note the generated certificate tree is capped at max depth of 2 to be in line with commercially available certificates * Note the generated certificate tree is capped at max depth of 2 to be in line with commercially available certificates
*/ */
fun createSelfSignedCACert(legalName: String, keyPair: KeyPair, validDays: Int, provider: Provider): CertificateAndKeyPair { fun createSelfSignedCACert(type: CertificateType,
subject: X500Name,
keyPair: KeyPair,
validDays: Int,
provider: Provider,
crlDistPoint: String? = null,
crlIssuer: String? = null): CertificateAndKeyPair {
// TODO this needs to be chaneged // TODO this needs to be chaneged
val issuer = getDevX509Name(legalName)
val serial = BigInteger.valueOf(random63BitValue(provider)) val serial = BigInteger.valueOf(random63BitValue(provider))
val subject = issuer
val pubKey = keyPair.public val pubKey = keyPair.public
// Ten year certificate validity // Ten year certificate validity
// TODO how do we manage certificate expiry, revocation and loss // TODO how do we manage certificate expiry, revocation and loss
val window = getCertificateValidityWindow(0, validDays) val window = getCertificateValidityWindow(0, validDays)
val keyPurposes = DERSequence(ASN1EncodableVector().apply { type.purposes.forEach { add(it) } })
val builder = JcaX509v3CertificateBuilder( val builder = JcaX509v3CertificateBuilder(subject, serial, window.first, window.second, subject, pubKey)
issuer, serial, window.first, window.second, subject, pubKey) builder.addExtension(Extension.subjectKeyIdentifier, false, createSubjectKeyIdentifier(pubKey))
builder.addExtension(Extension.basicConstraints, true, BasicConstraints(type.isCA))
builder.addExtension(Extension.subjectKeyIdentifier, false, builder.addExtension(Extension.keyUsage, false, type.keyUsage)
createSubjectKeyIdentifier(pubKey)) builder.addExtension(Extension.extendedKeyUsage, false, keyPurposes)
// TODO to remove once we allow for longer certificate chains builder.addExtension(Extension.authorityKeyIdentifier, false, JcaX509ExtensionUtils().createAuthorityKeyIdentifier(keyPair.public))
builder.addExtension(Extension.basicConstraints, true, if (type.role != null) {
BasicConstraints(2)) builder.addExtension(ASN1ObjectIdentifier(CordaOID.X509_EXTENSION_CORDA_ROLE), false, type.role)
}
val usage = KeyUsage(KeyUsage.keyCertSign or KeyUsage.digitalSignature or KeyUsage.keyEncipherment or KeyUsage.dataEncipherment or KeyUsage.cRLSign) addCrlInfo(builder, crlDistPoint, crlIssuer)
builder.addExtension(Extension.keyUsage, false, usage)
val purposes = ASN1EncodableVector()
purposes.add(KeyPurposeId.id_kp_serverAuth)
purposes.add(KeyPurposeId.id_kp_clientAuth)
purposes.add(KeyPurposeId.anyExtendedKeyUsage)
builder.addExtension(Extension.extendedKeyUsage, false, DERSequence(purposes).toASN1Primitive())
val cert = signCertificate(builder, keyPair.private, provider) val cert = signCertificate(builder, keyPair.private, provider)
@ -111,85 +112,110 @@ object X509Utilities {
return CertificateAndKeyPair(certificate, getCleanEcdsaKeyPair(publicKey, privateKey)) return CertificateAndKeyPair(certificate, getCleanEcdsaKeyPair(publicKey, privateKey))
} }
/**
* Retrieves key pair and certificate chain from the given key store. Also, the keys retrieved are cleaned in a sense of the
* [getCleanEcdsaKeyPair] method.
* @param certificateKeyName certificate and key name (alias) to be used when querying the key store.
* @param privateKeyPassword password for the private key.
* @param keyStore key store that holds the certificate with its keys.
* @return instance of [KeyPairAndCertificateChain] holding the key pair and the certificate chain.
*/
fun retrieveKeysAndCertificateChain(certificateKeyName: String, privateKeyPassword: String, keyStore: KeyStore): KeyPairAndCertificateChain {
val privateKey = keyStore.getKey(certificateKeyName, privateKeyPassword.toCharArray()) as PrivateKey
val publicKey = keyStore.getCertificate(certificateKeyName).publicKey
val certificateChain = keyStore.getCertificateChain(certificateKeyName).map { it as X509Certificate }
return KeyPairAndCertificateChain(getCleanEcdsaKeyPair(publicKey, privateKey), certificateChain.toTypedArray())
}
/** /**
* Create a de novo root intermediate X509 v3 CA cert and KeyPair. * Create a de novo root intermediate X509 v3 CA cert and KeyPair.
* @param commonName The Common (CN) field of the cert Subject will be populated with the domain string. * @param type type of the certificate to be created
* @param subject X500 name of the certificate subject
* @param certificateAuthority The Public certificate and KeyPair of the root CA certificate above this used to sign it. * @param certificateAuthority The Public certificate and KeyPair of the root CA certificate above this used to sign it.
* @param keyPair public and private keys to be associated with the generated certificate * @param keyPair public and private keys to be associated with the generated certificate
* @param validDays number of days which this certificate is valid for * @param validDays number of days which this certificate is valid for
* @param provider provider to be used during the certificate signing process * @param provider provider to be used during the certificate signing process
* @param crlDistPoint url to the certificate revocation list of this certificate
* @param crlIssuer issuer of the certificate revocation list of this certificate
* @return an instance of [CertificateAndKeyPair] class is returned containing the new intermediate CA Cert and its KeyPair for signing downstream certificates. * @return an instance of [CertificateAndKeyPair] class is returned containing the new intermediate CA Cert and its KeyPair for signing downstream certificates.
* Note the generated certificate tree is capped at max depth of 1 below this to be in line with commercially available certificates * Note the generated certificate tree is capped at max depth of 1 below this to be in line with commercially available certificates
*/ */
fun createIntermediateCert(commonName: String, fun createIntermediateCert(type: CertificateType,
subject: X500Name,
certificateAuthority: CertificateAndKeyPair, certificateAuthority: CertificateAndKeyPair,
keyPair: KeyPair, validDays: Int, provider: Provider): CertificateAndKeyPair { keyPair: KeyPair,
validDays: Int,
provider: Provider,
crlDistPoint: String?,
crlIssuer: String?): CertificateAndKeyPair {
val issuer = X509CertificateHolder(certificateAuthority.certificate.encoded).subject val issuer = X509CertificateHolder(certificateAuthority.certificate.encoded).subject
val serial = BigInteger.valueOf(random63BitValue(provider)) val serial = BigInteger.valueOf(random63BitValue(provider))
val subject = getDevX509Name(commonName)
val pubKey = keyPair.public val pubKey = keyPair.public
// Ten year certificate validity // Ten year certificate validity
// TODO how do we manage certificate expiry, revocation and loss // TODO how do we manage certificate expiry, revocation and loss
val window = getCertificateValidityWindow(0, validDays, certificateAuthority.certificate.notBefore, certificateAuthority.certificate.notAfter) val window = getCertificateValidityWindow(0, validDays, certificateAuthority.certificate.notBefore, certificateAuthority.certificate.notAfter)
val keyPurposes = DERSequence(ASN1EncodableVector().apply { type.purposes.forEach { add(it) } })
val builder = JcaX509v3CertificateBuilder( val builder = JcaX509v3CertificateBuilder(issuer, serial, window.first, window.second, subject, pubKey)
issuer, serial, window.first, window.second, subject, pubKey) builder.addExtension(Extension.subjectKeyIdentifier, false, createSubjectKeyIdentifier(pubKey))
builder.addExtension(Extension.basicConstraints, true, BasicConstraints(type.isCA))
builder.addExtension(Extension.subjectKeyIdentifier, false, builder.addExtension(Extension.keyUsage, false, type.keyUsage)
createSubjectKeyIdentifier(pubKey)) builder.addExtension(Extension.extendedKeyUsage, false, keyPurposes)
// TODO to remove onece we allow for longer certificate chains builder.addExtension(Extension.authorityKeyIdentifier, false, JcaX509ExtensionUtils().createAuthorityKeyIdentifier(certificateAuthority.keyPair.public))
builder.addExtension(Extension.basicConstraints, true, if (type.role != null) {
BasicConstraints(1)) builder.addExtension(ASN1ObjectIdentifier(CordaOID.X509_EXTENSION_CORDA_ROLE), false, type.role)
}
val usage = KeyUsage(KeyUsage.keyCertSign or KeyUsage.digitalSignature or KeyUsage.keyEncipherment or KeyUsage.dataEncipherment or KeyUsage.cRLSign) addCrlInfo(builder, crlDistPoint, crlIssuer)
builder.addExtension(Extension.keyUsage, false, usage)
val purposes = ASN1EncodableVector()
purposes.add(KeyPurposeId.id_kp_serverAuth)
purposes.add(KeyPurposeId.id_kp_clientAuth)
purposes.add(KeyPurposeId.anyExtendedKeyUsage)
builder.addExtension(Extension.extendedKeyUsage, false,
DERSequence(purposes))
val cert = signCertificate(builder, certificateAuthority.keyPair.private, provider) val cert = signCertificate(builder, certificateAuthority.keyPair.private, provider)
cert.checkValidity(Date()) cert.checkValidity(Date())
cert.verify(certificateAuthority.keyPair.public) cert.verify(certificateAuthority.keyPair.public)
return CertificateAndKeyPair(cert, KeyPair(pubKey, keyPair.private)) return CertificateAndKeyPair(cert, keyPair)
} }
/** /**
* Creates and signs a X509 v3 client certificate. * Creates and signs a X509 v3 client certificate.
* @param type type of the certificate to be created
* @param caCertAndKey signing certificate authority certificate and its keys * @param caCertAndKey signing certificate authority certificate and its keys
* @param request certficate signing request * @param request certificate signing request
* @param validDays number of days which this certificate is valid for * @param validDays number of days which this certificate is valid for
* @param provider provider to be used during the certificate signing process * @param provider provider to be used during the certificate signing process
* @param crlDistPoint url to the certificate revocation list of this certificate
* @param crlIssuer issuer of the certificate revocation list of this certificate
* @return an instance of [CertificateAndKeyPair] class is returned containing the signed client certificate. * @return an instance of [CertificateAndKeyPair] class is returned containing the signed client certificate.
*/ */
fun createClientCertificate(caCertAndKey: CertificateAndKeyPair, fun createClientCertificate(type: CertificateType,
caCertAndKey: CertificateAndKeyPair,
request: PKCS10CertificationRequest, request: PKCS10CertificationRequest,
validDays: Int, validDays: Int,
provider: Provider): Certificate { provider: Provider,
crlDistPoint: String?,
crlIssuer: String?): Certificate {
val jcaRequest = JcaPKCS10CertificationRequest(request) val jcaRequest = JcaPKCS10CertificationRequest(request)
// This can be adjusted more to our future needs. // This can be adjusted more to our future needs.
val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, CordaX500Name.parse(jcaRequest.subject.toString()).copy(commonName = null).x500Name))), arrayOf()) val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, CordaX500Name.parse(jcaRequest.subject.toString()).copy(commonName = null).x500Name))), arrayOf())
val issuerCertificate = caCertAndKey.certificate val issuerCertificate = caCertAndKey.certificate
val issuerKeyPair = caCertAndKey.keyPair val issuerKeyPair = caCertAndKey.keyPair
val certificateType = CertificateType.NODE_CA
val validityWindow = getCertificateValidityWindow(0, validDays, issuerCertificate.notBefore, issuerCertificate.notAfter) val validityWindow = getCertificateValidityWindow(0, validDays, issuerCertificate.notBefore, issuerCertificate.notAfter)
val serial = BigInteger.valueOf(random63BitValue(provider)) val serial = BigInteger.valueOf(random63BitValue(provider))
val subject = CordaX500Name.parse(jcaRequest.subject.toString()).x500Name val subject = CordaX500Name.parse(jcaRequest.subject.toString()).x500Name
val subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(ASN1Sequence.getInstance(jcaRequest.publicKey.encoded)) val subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(ASN1Sequence.getInstance(jcaRequest.publicKey.encoded))
val keyPurposes = DERSequence(ASN1EncodableVector().apply { certificateType.purposes.forEach { add(it) } }) val keyPurposes = DERSequence(ASN1EncodableVector().apply { type.purposes.forEach { add(it) } })
val builder = JcaX509v3CertificateBuilder(issuerCertificate, serial, validityWindow.first, validityWindow.second, subject, jcaRequest.publicKey) val builder = JcaX509v3CertificateBuilder(issuerCertificate, serial, validityWindow.first, validityWindow.second, subject, jcaRequest.publicKey)
.addExtension(Extension.subjectKeyIdentifier, false, BcX509ExtensionUtils().createSubjectKeyIdentifier(subjectPublicKeyInfo)) .addExtension(Extension.subjectKeyIdentifier, false, BcX509ExtensionUtils().createSubjectKeyIdentifier(subjectPublicKeyInfo))
.addExtension(Extension.basicConstraints, certificateType.isCA, BasicConstraints(certificateType.isCA)) .addExtension(Extension.basicConstraints, type.isCA, BasicConstraints(type.isCA))
.addExtension(Extension.keyUsage, false, certificateType.keyUsage) .addExtension(Extension.keyUsage, false, type.keyUsage)
.addExtension(Extension.extendedKeyUsage, false, keyPurposes) .addExtension(Extension.extendedKeyUsage, false, keyPurposes)
.addExtension(Extension.nameConstraints, true, nameConstraints) .addExtension(Extension.nameConstraints, true, nameConstraints)
.addExtension(Extension.authorityKeyIdentifier, false, JcaX509ExtensionUtils().createAuthorityKeyIdentifier(issuerKeyPair.public))
if (type.role != null) {
builder.addExtension(ASN1ObjectIdentifier(CordaOID.X509_EXTENSION_CORDA_ROLE), false, type.role)
}
addCrlInfo(builder, crlDistPoint, crlIssuer)
val certificate = signCertificate(builder, issuerKeyPair.private, provider) val certificate = signCertificate(builder, issuerKeyPair.private, provider)
certificate.checkValidity(Date()) certificate.checkValidity(Date())
certificate.verify(issuerKeyPair.public) certificate.verify(issuerKeyPair.public)
@ -285,26 +311,23 @@ object X509Utilities {
provider: Provider, provider: Provider,
signatureAlgorithm: String = SIGNATURE_ALGORITHM): X509Certificate { signatureAlgorithm: String = SIGNATURE_ALGORITHM): X509Certificate {
val signer = JcaContentSignerBuilder(signatureAlgorithm).setProvider(provider).build(signedWithPrivateKey) val signer = JcaContentSignerBuilder(signatureAlgorithm).setProvider(provider).build(signedWithPrivateKey)
return JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate(certificateBuilder.build(signer)) return certificateBuilder.build(signer).toJca()
} }
/** private fun addCrlInfo(builder: X509v3CertificateBuilder, crlDistPoint: String?, crlIssuer: String?) {
* Return a bogus X509 for dev purposes. Use [getX509Name] for something more real. if (crlDistPoint != null) {
*/ val distPointName = DistributionPointName(GeneralNames(GeneralName(GeneralName.uniformResourceIdentifier, crlDistPoint)))
private fun getDevX509Name(commonName: String): X500Name { val crlIssuerGeneralNames = crlIssuer?.let {
val nameBuilder = X500NameBuilder(BCStyle.INSTANCE) GeneralNames(GeneralName(CordaX500Name.parse(crlIssuer).x500Name))
nameBuilder.addRDN(BCStyle.CN, commonName) }
nameBuilder.addRDN(BCStyle.O, "R3") // The second argument is flag that allows you to define what reason of certificate revocation is served by this distribution point see [ReasonFlags].
nameBuilder.addRDN(BCStyle.OU, "corda") // The idea is that you have different revocation per revocation reason. Since we won't go into such a granularity, we can skip that parameter.
nameBuilder.addRDN(BCStyle.L, "London") // The third argument allows you to specify the name of the CRL issuer, it needs to be consistent with the crl (IssuingDistributionPoint) extension and the idp argument.
nameBuilder.addRDN(BCStyle.C, "UK") // If idp == true, set it, if idp == false, leave it null as done here.
return nameBuilder.build() val distPoint = DistributionPoint(distPointName, null, crlIssuerGeneralNames)
} builder.addExtension(Extension.cRLDistributionPoints, false, CRLDistPoint(arrayOf(distPoint)))
}
private fun getX509Name(myLegalName: String, nearestCity: String, email: String): X500Name {
return X500NameBuilder(BCStyle.INSTANCE)
.addRDN(BCStyle.CN, myLegalName)
.addRDN(BCStyle.L, nearestCity)
.addRDN(BCStyle.E, email).build()
} }
} }
data class KeyPairAndCertificateChain(val keyPair: KeyPair, val certificateChain: Array<X509Certificate>)

View File

@ -0,0 +1,64 @@
package com.r3.corda.networkmanage.hsm.generator
import com.r3.corda.networkmanage.common.utils.ShowHelpException
import com.typesafe.config.ConfigException
import net.corda.nodeapi.internal.crypto.CertificateType
import org.assertj.core.api.Assertions
import org.junit.Test
import java.io.File
import java.nio.file.Paths
import kotlin.test.assertEquals
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 validArgs = arrayOf("--config-file", validConfigPath)
@Test
fun `should fail when config file is missing`() {
val message = assertFailsWith<IllegalStateException> {
parseCommandLine("--config-file", "not-existing-file")
}.message
Assertions.assertThat(message).contains("Config file ")
}
@Test
fun `should throw ShowHelpException when help option is passed on the command line`() {
assertFailsWith<ShowHelpException> {
parseCommandLine("-?")
}
}
@Test
fun `should fail when config is invalid`() {
assertFailsWith<ConfigException.Missing> {
parseParameters(parseCommandLine("--config-file", invalidConfigPath).configFile)
}
}
@Test
fun `should parse generator config correctly`() {
val parameters = parseCommandLineAndGetParameters()
assertEquals("127.0.0.1", parameters.hsmHost)
assertEquals(3001, parameters.hsmPort)
val certConfig = parameters.certConfig
assertEquals(1, certConfig.keySpecifier)
assertEquals("trustpass", parameters.trustStorePassword)
assertEquals(Paths.get("."), parameters.trustStoreDirectory)
assertFalse(certConfig.storeKeysExternal)
assertFalse(parameters.userConfigs.isEmpty())
val userConfig = parameters.userConfigs.first()
assertEquals("INTEGRATION_TEST", userConfig.username)
assertEquals(AuthMode.PASSWORD, userConfig.authMode)
assertEquals("INTEGRATION_TEST", userConfig.authToken)
assertEquals(3650, certConfig.validDays)
assertEquals(CertificateType.ROOT_CA, certConfig.certificateType)
assertEquals("NIST-P256", certConfig.keyCurve)
}
private fun parseCommandLineAndGetParameters(): GeneratorParameters {
return parseParameters(parseCommandLine(*validArgs).configFile)
}
}

View File

@ -0,0 +1,12 @@
hsmHost = 127.0.0.1
hsmPort = 3001
keyGroup = "DEV.DOORMAN"
keySpecifier = 1
userConfigs = [
{
username = "INTEGRATION_TEST"
authMode = PASSWORD
authToken = "INTEGRATION_TEST"
}
]

View File

@ -3,6 +3,7 @@ keyGroup = "DEV.DOORMAN"
keySpecifier = -1 keySpecifier = -1
authMode = PASSWORD authMode = PASSWORD
csrPrivateKeyPassword = "" csrPrivateKeyPassword = ""
csrCertCrlDistPoint = "http://test.com/revoked.crl"
networkMapPrivateKeyPassword = "" networkMapPrivateKeyPassword = ""
rootPrivateKeyPassword = "" rootPrivateKeyPassword = ""
keyGroup = "DEV.DOORMAN" keyGroup = "DEV.DOORMAN"

View File

@ -3,6 +3,7 @@ keyGroup = "DEV.DOORMAN"
keySpecifier = -1 keySpecifier = -1
authMode = PASSWORD authMode = PASSWORD
csrPrivateKeyPassword = "" csrPrivateKeyPassword = ""
csrCertCrlDistPoint = "http://test.com/revoked.crl"
networkMapPrivateKeyPassword = "" networkMapPrivateKeyPassword = ""
rootPrivateKeyPassword = "" rootPrivateKeyPassword = ""
keyGroup = "DEV.DOORMAN" keyGroup = "DEV.DOORMAN"

View File

@ -34,6 +34,7 @@ include 'perftestcordapp'
include 'network-management' include 'network-management'
include 'network-management:capsule' include 'network-management:capsule'
include 'network-management:capsule-hsm' include 'network-management:capsule-hsm'
include 'network-management:capsule-hsm-cert-generator'
include 'tools:jmeter' include 'tools:jmeter'
include 'tools:explorer' include 'tools:explorer'
include 'tools:explorer:capsule' include 'tools:explorer:capsule'