From 789ce5d44aef0660a0e8f9c2741e7e234af38841 Mon Sep 17 00:00:00 2001 From: Michal Kit Date: Tue, 23 Jan 2018 11:50:03 +0000 Subject: [PATCH] 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 --- docs/source/hsm-cert-generator.rst | 28 +++ docs/source/running-hsm-cert-generator.rst | 87 +++++++++ docs/source/running-signing-service.rst | 11 +- network-management/README.md | 20 +- .../capsule-hsm-cert-generator/build.gradle | 39 ++++ network-management/generator.conf | 30 +++ network-management/hsm.conf | 2 +- .../r3/corda/networkmanage/HsmSimulator.kt | 7 +- .../networkmanage/hsm/HsmKeyGenerationTest.kt | 117 ++++++++++++ .../com/r3/corda/networkmanage/hsm/HsmTest.kt | 3 +- .../corda/networkmanage/common/utils/Utils.kt | 2 + .../r3/corda/networkmanage/doorman/Main.kt | 6 +- .../com/r3/corda/networkmanage/hsm/Main.kt | 22 +-- .../hsm/configuration/Configuration.kt | 10 +- .../hsm/generator/AutoAuthenticator.kt | 38 ++++ .../hsm/generator/GeneratorParameters.kt | 95 ++++++++++ .../hsm/generator/KeyCertificateGenerator.kt | 161 ++++++++-------- .../corda/networkmanage/hsm/generator/Main.kt | 28 +++ .../networkmanage/hsm/signer/HsmCsrSigner.kt | 23 ++- .../hsm/signer/HsmNetworkMapSigner.kt | 16 +- .../{X509Utils.kt => HsmX509Utilities.kt} | 173 ++++++++++-------- .../hsm/generator/GeneratorParametersTest.kt | 64 +++++++ .../src/test/resources/generator_fail.conf | 12 ++ .../src/test/resources/hsm.conf | 1 + .../src/test/resources/hsm_fail.conf | 1 + settings.gradle | 1 + 26 files changed, 798 insertions(+), 199 deletions(-) create mode 100644 docs/source/hsm-cert-generator.rst create mode 100644 docs/source/running-hsm-cert-generator.rst create mode 100644 network-management/capsule-hsm-cert-generator/build.gradle create mode 100644 network-management/generator.conf create mode 100644 network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmKeyGenerationTest.kt create mode 100644 network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/generator/AutoAuthenticator.kt create mode 100644 network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/generator/GeneratorParameters.kt create mode 100644 network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/generator/Main.kt rename network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/utils/{X509Utils.kt => HsmX509Utilities.kt} (64%) create mode 100644 network-management/src/test/kotlin/com/r3/corda/networkmanage/hsm/generator/GeneratorParametersTest.kt create mode 100644 network-management/src/test/resources/generator_fail.conf diff --git a/docs/source/hsm-cert-generator.rst b/docs/source/hsm-cert-generator.rst new file mode 100644 index 0000000000..b6b74a8382 --- /dev/null +++ b/docs/source/hsm-cert-generator.rst @@ -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. diff --git a/docs/source/running-hsm-cert-generator.rst b/docs/source/running-hsm-cert-generator.rst new file mode 100644 index 0000000000..20a793554a --- /dev/null +++ b/docs/source/running-hsm-cert-generator.rst @@ -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. \ No newline at end of file diff --git a/docs/source/running-signing-service.rst b/docs/source/running-signing-service.rst index 6b84578dda..61a2997c8b 100644 --- a/docs/source/running-signing-service.rst +++ b/docs/source/running-signing-service.rst @@ -30,15 +30,20 @@ Allowed parameters are: :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. -: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. -: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. diff --git a/network-management/README.md b/network-management/README.md index 11a60535d3..28b0bbb704 100644 --- a/network-management/README.md +++ b/network-management/README.md @@ -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-.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-.jar +``` + #Configuring network management service ### Local signing @@ -135,7 +147,7 @@ networkMapConfig { ### 1. Create keystore for local signer - If local signer is enabled, the server will look for key stores in the certificate folder on start up. + If local signer is enabled, the server will look for key stores in the certificate folder on start up. The key stores can be created using `--mode` flag. ``` java -jar doorman-.jar --mode ROOT_KEYGEN @@ -146,11 +158,11 @@ networkMapConfig { ``` A trust store file containing the root trust certificate will be produced in the location `distribute-nodes / truststore.jks` - (relative to `rootStorePath`). `truststore.jks` must be copied to the `certificates` directory of each node before + (relative to `rootStorePath`). `truststore.jks` must be copied to the `certificates` directory of each node before they attempt to register. The trust store password is `trustpass`. ### 2. Start Doorman service for notary registration - Start the network management server with the doorman service for initial bootstrapping. Network map service (`networkMapConfig`) should be **disabled** at this point. + Start the network management server with the doorman service for initial bootstrapping. Network map service (`networkMapConfig`) should be **disabled** at this point. **Comment out** network map config in the config file and start the server by running : ``` java -jar doorman-.jar @@ -185,4 +197,4 @@ A network parameters file is required to start the network map service for the f We can now restart the network management server with both doorman and network map service. ``` java -jar doorman-.jar --update-network-parameters network-parameters.conf -``` +``` \ No newline at end of file diff --git a/network-management/capsule-hsm-cert-generator/build.gradle b/network-management/capsule-hsm-cert-generator/build.gradle new file mode 100644 index 0000000000..22182333da --- /dev/null +++ b/network-management/capsule-hsm-cert-generator/build.gradle @@ -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 +} \ No newline at end of file diff --git a/network-management/generator.conf b/network-management/generator.conf new file mode 100644 index 0000000000..ab1b09957a --- /dev/null +++ b/network-management/generator.conf @@ -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" + } +] diff --git a/network-management/hsm.conf b/network-management/hsm.conf index f20f794dfe..124222d894 100644 --- a/network-management/hsm.conf +++ b/network-management/hsm.conf @@ -7,6 +7,7 @@ rootCertificateName = "corda_root_ca" rootPrivateKeyPassword = "Password" csrPrivateKeyPassword = "Password" csrCertificateName = "intermediate_ca" +csrCertCrlDistPoint = "http://test.com/revoked.crl" networkMapCertificateName = "intermediate_ca" networkMapPrivateKeyPassword = "Password" validDays = 3650 @@ -16,7 +17,6 @@ authKeyFilePath = "./Administrator.key" authKeyFilePassword = "Password" autoUsername = "AUTO_USER" signInterval = 600 - h2port = 0 dataSourceProperties { "dataSourceClassName" = org.h2.jdbcx.JdbcDataSource diff --git a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/HsmSimulator.kt b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/HsmSimulator.kt index 232377ec09..a7e1231f18 100644 --- a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/HsmSimulator.kt +++ b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/HsmSimulator.kt @@ -33,6 +33,7 @@ data class CryptoUserCredentials(val username: String, val password: String) class HsmSimulator(private val serverAddress: String = DEFAULT_SERVER_ADDRESS, private val imageRepoTag: String = DEFAULT_IMAGE_REPO_TAG, private val imageVersion: String = DEFAULT_IMAGE_VERSION, + private val pullImage: Boolean = DEFAULT_PULL_IMAGE, private val registryUser: String? = REGISTRY_USERNAME, 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_IMAGE_REPO_TAG = "corda.azurecr.io/network-management/hsm-simulator" val DEFAULT_IMAGE_VERSION = "latest" + val DEFAULT_PULL_IMAGE = true val HSM_SIMULATOR_PORT = "3001/tcp" val CONTAINER_KILL_TIMEOUT_SECONDS = 10 @@ -63,7 +65,10 @@ class HsmSimulator(private val serverAddress: String = DEFAULT_SERVER_ADDRESS, override fun before() { assumeFalse("Docker registry username is not set!. Skipping the test.", registryUser.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() docker.startHsmSimulatorContainer() } diff --git a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmKeyGenerationTest.kt b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmKeyGenerationTest.kt new file mode 100644 index 0000000000..5ea1cd6017 --- /dev/null +++ b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmKeyGenerationTest.kt @@ -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) + } + } +} \ No newline at end of file diff --git a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmTest.kt b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmTest.kt index c78f1ca852..24e6f9909d 100644 --- a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmTest.kt +++ b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmTest.kt @@ -33,7 +33,8 @@ class HsmTest { networkMapPrivateKeyPassword = "", rootPrivateKeyPassword = "", keyGroup = "DEV.DOORMAN", - validDays = 3650 + validDays = 3650, + csrCertCrlDistPoint = "http://test.com/revoked.crl" ) private lateinit var inputReader: InputReader diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/utils/Utils.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/utils/Utils.kt index 3554580976..6b1571662b 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/utils/Utils.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/utils/Utils.kt @@ -16,6 +16,8 @@ import java.security.PublicKey import java.security.cert.CertPath import java.security.cert.X509Certificate +const val CORDA_NETWORK_MAP = "cordanetworkmap" + // TODO These should be defined in node-api typealias SignedNetworkParameters = SignedDataWithCert typealias SignedNetworkMap = SignedDataWithCert diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt index c7dd3f0271..7d9fede12b 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt @@ -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.Companion.DOORMAN_SIGNATURE 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.ShowHelpException 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_NETWORK_MAP_CERTIFICATE_NAME import net.corda.core.crypto.Crypto import net.corda.core.crypto.SignatureScheme import net.corda.core.identity.CordaX500Name @@ -127,7 +127,7 @@ fun generateSigningKeyPairs(keystoreFile: Path, rootStoreFile: Path, rootKeystor X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) storeCertIfAbsent( - DEFAULT_NETWORK_MAP_CERTIFICATE_NAME, + CORDA_NETWORK_MAP, CertificateType.NETWORK_MAP, X500Principal("CN=Corda Network Map,OU=Corda,O=R3 Ltd,L=London,C=GB"), 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) } diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/Main.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/Main.kt index a586ef5329..d1a08f1ded 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/Main.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/Main.kt @@ -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.configuration.Parameters 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.persistence.ApprovedCertificateRequestData import com.r3.corda.networkmanage.hsm.persistence.DBSignedCertificateRequestStorage @@ -59,7 +57,6 @@ fun run(parameters: Parameters) { val database = configureDatabase(dataSourceProperties, databaseConfig) val csrStorage = DBSignedCertificateRequestStorage(database) val hsmSigner = HsmNetworkMapSigner( - networkMapCertificateName, networkMapPrivateKeyPassword, Authenticator(createProvider(), AuthMode.KEY_FILE, autoUsername, authKeyFilePath, authKeyFilePassword, signAuthThreshold)) @@ -72,25 +69,14 @@ fun run(parameters: Parameters) { csrStorage, csrCertificateName, csrPrivateKeyPassword, + csrCertCrlDistPoint, + csrCertCrlIssuer, rootCertificateName, validDays, Authenticator(createProvider(), authMode, autoUsername, authKeyFilePath, authKeyFilePassword, signAuthThreshold)) signer.sign(it) } - - 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", { + Menu().withExceptionHandler(::processError).addItem("1", "Sign all approved and unsigned CSRs", { val approved = csrStorage.getApprovedRequests() if (approved.isNotEmpty()) { if (confirmedSign(approved)) { @@ -99,7 +85,7 @@ fun run(parameters: Parameters) { } else { 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() if (approved.isNotEmpty()) { println("Approved CSRs:") diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/configuration/Configuration.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/configuration/Configuration.kt index 09475b4910..75f331b828 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/configuration/Configuration.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/configuration/Configuration.kt @@ -19,13 +19,15 @@ import java.util.* data class Parameters(val dataSourceProperties: Properties, val databaseConfig: DatabaseConfig = DatabaseConfig(), 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 keySpecifier: Int = DEFAULT_KEY_SPECIFIER, val rootPrivateKeyPassword: String, val csrPrivateKeyPassword: String, 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 rootCertificateName: String = DEFAULT_ROOT_CERTIFICATE_NAME, val validDays: Int, @@ -35,7 +37,7 @@ data class Parameters(val dataSourceProperties: Properties, val authKeyFilePath: Path? = DEFAULT_KEY_FILE_PATH, val authKeyFilePassword: String? = DEFAULT_KEY_FILE_PASSWORD, 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) { companion object { 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_PASSWORD: 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_CSR_CERT_CRL_ISSUER: String? = null } } diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/generator/AutoAuthenticator.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/generator/AutoAuthenticator.kt new file mode 100644 index 0000000000..a0004ab30e --- /dev/null +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/generator/AutoAuthenticator.kt @@ -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) { + 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) + } + } + } +} \ No newline at end of file diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/generator/GeneratorParameters.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/generator/GeneratorParameters.kt new file mode 100644 index 0000000000..646f866325 --- /dev/null +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/generator/GeneratorParameters.kt @@ -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, + 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() +} \ No newline at end of file diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/generator/KeyCertificateGenerator.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/generator/KeyCertificateGenerator.kt index 8940ad5155..4e68aa6330 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/generator/KeyCertificateGenerator.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/generator/KeyCertificateGenerator.kt @@ -1,101 +1,116 @@ package com.r3.corda.networkmanage.hsm.generator -import CryptoServerCXI.CryptoServerCXI +import CryptoServerCXI.CryptoServerCXI.KEY_ALGO_ECDSA +import CryptoServerCXI.CryptoServerCXI.KeyAttributes import CryptoServerJCE.CryptoServerProvider -import com.r3.corda.networkmanage.hsm.authentication.Authenticator -import com.r3.corda.networkmanage.hsm.utils.X509Utilities.createIntermediateCert -import com.r3.corda.networkmanage.hsm.utils.X509Utilities.createSelfSignedCACert -import com.r3.corda.networkmanage.hsm.utils.X509Utilities.getAndInitializeKeyStore -import com.r3.corda.networkmanage.hsm.utils.X509Utilities.getCleanEcdsaKeyPair -import com.r3.corda.networkmanage.hsm.utils.X509Utilities.retrieveCertificateAndKeys -import net.corda.nodeapi.internal.crypto.addOrReplaceKey +import com.r3.corda.networkmanage.common.utils.CORDA_NETWORK_MAP +import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.createIntermediateCert +import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.createSelfSignedCACert +import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.getAndInitializeKeyStore +import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.getCleanEcdsaKeyPair +import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.retrieveKeysAndCertificateChain +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.KeyStore import java.security.PrivateKey +import java.security.cert.X509Certificate 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, - private val keySpecifier: Int, - private val keyGroup: String) { - /** - * 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. - * @param intermediateCertificatesCredentials name and password for the intermediate key/certificate - * @param parentCertificateName name of the parent key/certificate - * @param parentPrivateKeyPassword password for the parent private key - * @param validDays days of certificate validity - */ - fun generateAllCertificates(intermediateCertificatesCredentials: List, - parentCertificateName: String, - 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) +class KeyCertificateGenerator(private val parameters: GeneratorParameters) { + companion object { + val logger = contextLogger() + } + + fun generate(provider: CryptoServerProvider) { + parameters.run { + require(trustStoreDirectory.isDirectory()) { "trustStoreDirectory must point to a directory." } + val keyName = when (certConfig.certificateType) { + ROOT_CA -> CORDA_ROOT_CA + INTERMEDIATE_CA -> CORDA_INTERMEDIATE_CA + NETWORK_MAP -> CORDA_NETWORK_MAP + else -> throw IllegalArgumentException("Invalid certificate type. ${certConfig.certificateType}") } + 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") } } - /** - * Generates a root certificate - */ - private fun generateRootCertificate(provider: CryptoServerProvider, - keyStore: KeyStore, - certificateKeyName: String, - privateKeyPassword: String, - validDays: Int) { - val keyPair = generateEcdsaKeyPair(provider, keyStore, certificateKeyName, privateKeyPassword) - val selfSignedRootCertificate = createSelfSignedCACert("R3", keyPair, validDays, provider).certificate - keyStore.addOrReplaceKey(certificateKeyName, keyPair.private, privateKeyPassword.toCharArray(), arrayOf(selfSignedRootCertificate)) - println("New certificate and key pair named $certificateKeyName have been generated") + private fun CertificateConfiguration.generateRootCert(provider: CryptoServerProvider, + keyPair: KeyPair, + trustStoreDirectory: Path, + trustStorePassword: String): Array { + val certificate = createSelfSignedCACert(ROOT_CA, + CordaX500Name.parse(subject).x500Name, + keyPair, + validDays, + provider, + crlDistributionUrl, + crlIssuer).certificate + 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) } - /** - * Generates an intermediate certificate - */ - private fun generateIntermediateCertificate(provider: CryptoServerProvider, - keyStore: KeyStore, - certificateKeyName: String, - privateKeyPassword: String, - parentCertificateName: String, - parentPrivateKeyPassword: String, - validDays: Int) { - val parentCACertKey = retrieveCertificateAndKeys(parentCertificateName, parentPrivateKeyPassword, keyStore) - val keyPair = generateEcdsaKeyPair(provider, keyStore, certificateKeyName, privateKeyPassword) - val intermediateCertificate = createIntermediateCert("R3 Intermediate", parentCACertKey, keyPair, validDays, provider) - keyStore.addOrReplaceKey(certificateKeyName, keyPair.private, privateKeyPassword.toCharArray(), arrayOf(intermediateCertificate.certificate, parentCACertKey.certificate)) - println("New certificate and key pair named $certificateKeyName have been generated") + private fun CertificateConfiguration.generateIntermediateCert( + provider: CryptoServerProvider, + keyPair: KeyPair, + keyStore: KeyStore): Array { + val rootKeysAndCertChain = retrieveKeysAndCertificateChain(CORDA_ROOT_CA, + rootPrivateKeyPassword, + keyStore) + val certificateAndKeyPair = createIntermediateCert( + certificateType, + CordaX500Name.parse(subject).x500Name, + CertificateAndKeyPair(rootKeysAndCertChain.certificateChain.first(), rootKeysAndCertChain.keyPair), + keyPair, + validDays, + provider, + crlDistributionUrl, + crlIssuer) + return arrayOf(certificateAndKeyPair.certificate, *rootKeysAndCertChain.certificateChain) } - private fun generateECDSAKey(keySpecifier: Int, keyName: String, keyGroup: String, provider: CryptoServerProvider, overwrite: Boolean = true) { - val generateFlag = if (overwrite) { - println("!!! WARNING: OVERWRITING KEY NAMED $keyName !!!") - CryptoServerCXI.FLAG_OVERWRITE - } else { - 0 - } - val keyAttributes = CryptoServerCXI.KeyAttributes() + private fun CertificateConfiguration.generateECDSAKey(keyName: String, provider: CryptoServerProvider) { + val keyAttributes = KeyAttributes() keyAttributes.apply { - algo = CryptoServerCXI.KEY_ALGO_ECDSA + algo = KEY_ALGO_ECDSA group = keyGroup specifier = keySpecifier - export = 0 // deny export + export = keyExport name = keyName - setCurve("NIST-P256") + setCurve(keyCurve) } - println("Generating key...") - val mechanismFlag = CryptoServerCXI.MECH_RND_REAL or CryptoServerCXI.MECH_KEYGEN_UNCOMP - provider.cryptoServer.generateKey(generateFlag, keyAttributes, mechanismFlag) + logger.info("Generating key $keyName") + provider.cryptoServer.generateKey(keyOverride, keyAttributes, keyGenMechanism) } - private fun generateEcdsaKeyPair(provider: CryptoServerProvider, keyStore: KeyStore, keyName: String, privateKeyPassword: String): KeyPair { - generateECDSAKey(keySpecifier, keyName, keyGroup, provider) + private fun CertificateConfiguration.generateEcdsaKeyPair(keyName: String, provider: CryptoServerProvider, keyStore: KeyStore): KeyPair { + generateECDSAKey(keyName, provider) val privateKey = keyStore.getKey(keyName, privateKeyPassword.toCharArray()) as PrivateKey val publicKey = keyStore.getCertificate(keyName).publicKey return getCleanEcdsaKeyPair(publicKey, privateKey) diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/generator/Main.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/generator/Main.kt new file mode 100644 index 0000000000..61dbc9d732 --- /dev/null +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/generator/Main.kt @@ -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) { + 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)) + } + } +} \ No newline at end of file diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/signer/HsmCsrSigner.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/signer/HsmCsrSigner.kt index 4d6cd46fb9..13f6d465e7 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/signer/HsmCsrSigner.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/signer/HsmCsrSigner.kt @@ -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.persistence.ApprovedCertificateRequestData import com.r3.corda.networkmanage.hsm.persistence.SignedCertificateRequestStorage -import com.r3.corda.networkmanage.hsm.utils.X509Utilities.buildCertPath -import com.r3.corda.networkmanage.hsm.utils.X509Utilities.createClientCertificate -import com.r3.corda.networkmanage.hsm.utils.X509Utilities.getAndInitializeKeyStore -import com.r3.corda.networkmanage.hsm.utils.X509Utilities.retrieveCertificateAndKeys +import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.buildCertPath +import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.createClientCertificate +import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.getAndInitializeKeyStore +import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.retrieveCertificateAndKeys +import net.corda.nodeapi.internal.crypto.CertificateType /** * Encapsulates certificate signing logic @@ -14,6 +15,8 @@ import com.r3.corda.networkmanage.hsm.utils.X509Utilities.retrieveCertificateAnd class HsmCsrSigner(private val storage: SignedCertificateRequestStorage, private val intermediateCertAlias: String, private val intermediateCertPrivateKeyPass: String?, + private val csrCertCrlDistPoint: String, + private val csrCertCrlIssuer: String?, private val rootCertAlias: String, private val validDays: Int, 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 intermediateCertAndKey = retrieveCertificateAndKeys(intermediateCertAlias, intermediatePrivateKeyPass, keyStore) toSign.forEach { - it.certPath = buildCertPath( - createClientCertificate(intermediateCertAndKey, it.request, validDays, provider), - intermediateCertAndKey.certificate, - rootCert) + it.certPath = buildCertPath(createClientCertificate( + CertificateType.NODE_CA, + intermediateCertAndKey, + it.request, + validDays, + provider, + csrCertCrlDistPoint, + csrCertCrlIssuer), rootCert) } storage.store(toSign, signers) println("The following certificates have been signed by $signers:") diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/signer/HsmNetworkMapSigner.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/signer/HsmNetworkMapSigner.kt index aa0c4e6e17..3cf98d681e 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/signer/HsmNetworkMapSigner.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/signer/HsmNetworkMapSigner.kt @@ -1,10 +1,11 @@ package com.r3.corda.networkmanage.hsm.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.utils.X509Utilities -import com.r3.corda.networkmanage.hsm.utils.X509Utilities.getAndInitializeKeyStore -import com.r3.corda.networkmanage.hsm.utils.X509Utilities.verify +import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities +import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.getAndInitializeKeyStore +import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.verify import net.corda.core.internal.DigitalSignatureWithCert import net.corda.nodeapi.internal.crypto.getX509Certificate 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. */ // TODO Rename this to HsmSigner -class HsmNetworkMapSigner(private val certificateKeyName: String, - private val privateKeyPassword: String, +class HsmNetworkMapSigner(private val privateKeyPassword: String, private val authenticator: Authenticator) : Signer { /** * 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 { return authenticator.connectAndAuthenticate { 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 // when used with the given provider. - val key = keyStore.getKey(certificateKeyName, privateKeyPassword.toCharArray()) as PrivateKey - val signature = Signature.getInstance(X509Utilities.SIGNATURE_ALGORITHM, provider).run { + val key = keyStore.getKey(CORDA_NETWORK_MAP, privateKeyPassword.toCharArray()) as PrivateKey + val signature = Signature.getInstance(HsmX509Utilities.SIGNATURE_ALGORITHM, provider).run { initSign(key) update(data) sign() diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/utils/X509Utils.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/utils/HsmX509Utilities.kt similarity index 64% rename from network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/utils/X509Utils.kt rename to network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/utils/HsmX509Utilities.kt index 9bdd397e46..0eb65ccb2c 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/utils/X509Utils.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/utils/HsmX509Utilities.kt @@ -1,24 +1,24 @@ package com.r3.corda.networkmanage.hsm.utils import CryptoServerJCE.CryptoServerProvider +import net.corda.core.CordaOID import net.corda.core.identity.CordaX500Name import net.corda.core.internal.x500Name import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.getX509Certificate +import net.corda.nodeapi.internal.crypto.toJca import org.bouncycastle.asn1.ASN1EncodableVector +import org.bouncycastle.asn1.ASN1ObjectIdentifier import org.bouncycastle.asn1.ASN1Sequence import org.bouncycastle.asn1.DERSequence 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.cert.X509CertificateHolder import org.bouncycastle.cert.X509v3CertificateBuilder 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.jce.provider.BouncyCastleProvider import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder import org.bouncycastle.pkcs.PKCS10CertificationRequest import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest @@ -32,47 +32,48 @@ import java.time.Instant import java.time.temporal.ChronoUnit import java.util.* -object X509Utilities { +object HsmX509Utilities { val SIGNATURE_ALGORITHM = "SHA256withECDSA" /** * 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 validDays number of days which this certificate is valid for * @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. * 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 - val issuer = getDevX509Name(legalName) val serial = BigInteger.valueOf(random63BitValue(provider)) - val subject = issuer val pubKey = keyPair.public // Ten year certificate validity // TODO how do we manage certificate expiry, revocation and loss val window = getCertificateValidityWindow(0, validDays) + val keyPurposes = DERSequence(ASN1EncodableVector().apply { type.purposes.forEach { add(it) } }) - val builder = JcaX509v3CertificateBuilder( - issuer, serial, window.first, window.second, subject, pubKey) - - builder.addExtension(Extension.subjectKeyIdentifier, false, - createSubjectKeyIdentifier(pubKey)) - // TODO to remove once we allow for longer certificate chains - builder.addExtension(Extension.basicConstraints, true, - BasicConstraints(2)) - - val usage = KeyUsage(KeyUsage.keyCertSign or KeyUsage.digitalSignature or KeyUsage.keyEncipherment or KeyUsage.dataEncipherment or KeyUsage.cRLSign) - 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 builder = JcaX509v3CertificateBuilder(subject, 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.keyUsage, false, type.keyUsage) + builder.addExtension(Extension.extendedKeyUsage, false, keyPurposes) + builder.addExtension(Extension.authorityKeyIdentifier, false, JcaX509ExtensionUtils().createAuthorityKeyIdentifier(keyPair.public)) + if (type.role != null) { + builder.addExtension(ASN1ObjectIdentifier(CordaOID.X509_EXTENSION_CORDA_ROLE), false, type.role) + } + addCrlInfo(builder, crlDistPoint, crlIssuer) val cert = signCertificate(builder, keyPair.private, provider) @@ -111,85 +112,110 @@ object X509Utilities { 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. - * @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 keyPair public and private keys to be associated with the generated certificate * @param validDays number of days which this certificate is valid for * @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. * 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, - 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 serial = BigInteger.valueOf(random63BitValue(provider)) - val subject = getDevX509Name(commonName) val pubKey = keyPair.public // Ten year certificate validity // TODO how do we manage certificate expiry, revocation and loss val window = getCertificateValidityWindow(0, validDays, certificateAuthority.certificate.notBefore, certificateAuthority.certificate.notAfter) + val keyPurposes = DERSequence(ASN1EncodableVector().apply { type.purposes.forEach { add(it) } }) - val builder = JcaX509v3CertificateBuilder( - issuer, serial, window.first, window.second, subject, pubKey) - - builder.addExtension(Extension.subjectKeyIdentifier, false, - createSubjectKeyIdentifier(pubKey)) - // TODO to remove onece we allow for longer certificate chains - builder.addExtension(Extension.basicConstraints, true, - BasicConstraints(1)) - - val usage = KeyUsage(KeyUsage.keyCertSign or KeyUsage.digitalSignature or KeyUsage.keyEncipherment or KeyUsage.dataEncipherment or KeyUsage.cRLSign) - 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 builder = JcaX509v3CertificateBuilder(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.keyUsage, false, type.keyUsage) + builder.addExtension(Extension.extendedKeyUsage, false, keyPurposes) + builder.addExtension(Extension.authorityKeyIdentifier, false, JcaX509ExtensionUtils().createAuthorityKeyIdentifier(certificateAuthority.keyPair.public)) + if (type.role != null) { + builder.addExtension(ASN1ObjectIdentifier(CordaOID.X509_EXTENSION_CORDA_ROLE), false, type.role) + } + addCrlInfo(builder, crlDistPoint, crlIssuer) val cert = signCertificate(builder, certificateAuthority.keyPair.private, provider) cert.checkValidity(Date()) cert.verify(certificateAuthority.keyPair.public) - return CertificateAndKeyPair(cert, KeyPair(pubKey, keyPair.private)) + return CertificateAndKeyPair(cert, keyPair) } /** * 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 request certficate signing request + * @param request certificate signing request * @param validDays number of days which this certificate is valid for * @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. */ - fun createClientCertificate(caCertAndKey: CertificateAndKeyPair, + fun createClientCertificate(type: CertificateType, + caCertAndKey: CertificateAndKeyPair, request: PKCS10CertificationRequest, validDays: Int, - provider: Provider): Certificate { + provider: Provider, + crlDistPoint: String?, + crlIssuer: String?): Certificate { val jcaRequest = JcaPKCS10CertificationRequest(request) // 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 issuerCertificate = caCertAndKey.certificate val issuerKeyPair = caCertAndKey.keyPair - val certificateType = CertificateType.NODE_CA val validityWindow = getCertificateValidityWindow(0, validDays, issuerCertificate.notBefore, issuerCertificate.notAfter) val serial = BigInteger.valueOf(random63BitValue(provider)) val subject = CordaX500Name.parse(jcaRequest.subject.toString()).x500Name 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) .addExtension(Extension.subjectKeyIdentifier, false, BcX509ExtensionUtils().createSubjectKeyIdentifier(subjectPublicKeyInfo)) - .addExtension(Extension.basicConstraints, certificateType.isCA, BasicConstraints(certificateType.isCA)) - .addExtension(Extension.keyUsage, false, certificateType.keyUsage) + .addExtension(Extension.basicConstraints, type.isCA, BasicConstraints(type.isCA)) + .addExtension(Extension.keyUsage, false, type.keyUsage) .addExtension(Extension.extendedKeyUsage, false, keyPurposes) .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) certificate.checkValidity(Date()) certificate.verify(issuerKeyPair.public) @@ -285,26 +311,23 @@ object X509Utilities { provider: Provider, signatureAlgorithm: String = SIGNATURE_ALGORITHM): X509Certificate { val signer = JcaContentSignerBuilder(signatureAlgorithm).setProvider(provider).build(signedWithPrivateKey) - return JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate(certificateBuilder.build(signer)) + return certificateBuilder.build(signer).toJca() } - /** - * Return a bogus X509 for dev purposes. Use [getX509Name] for something more real. - */ - private fun getDevX509Name(commonName: String): X500Name { - val nameBuilder = X500NameBuilder(BCStyle.INSTANCE) - nameBuilder.addRDN(BCStyle.CN, commonName) - nameBuilder.addRDN(BCStyle.O, "R3") - nameBuilder.addRDN(BCStyle.OU, "corda") - nameBuilder.addRDN(BCStyle.L, "London") - nameBuilder.addRDN(BCStyle.C, "UK") - return nameBuilder.build() + private fun addCrlInfo(builder: X509v3CertificateBuilder, crlDistPoint: String?, crlIssuer: String?) { + if (crlDistPoint != null) { + val distPointName = DistributionPointName(GeneralNames(GeneralName(GeneralName.uniformResourceIdentifier, crlDistPoint))) + val crlIssuerGeneralNames = crlIssuer?.let { + GeneralNames(GeneralName(CordaX500Name.parse(crlIssuer).x500Name)) + } + // The second argument is flag that allows you to define what reason of certificate revocation is served by this distribution point see [ReasonFlags]. + // 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. + // 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. + // If idp == true, set it, if idp == false, leave it null as done here. + 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() - } -} \ No newline at end of file +data class KeyPairAndCertificateChain(val keyPair: KeyPair, val certificateChain: Array) \ No newline at end of file diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/hsm/generator/GeneratorParametersTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/hsm/generator/GeneratorParametersTest.kt new file mode 100644 index 0000000000..ca7c231f3a --- /dev/null +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/hsm/generator/GeneratorParametersTest.kt @@ -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 { + 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 { + parseCommandLine("-?") + } + } + + @Test + fun `should fail when config is invalid`() { + assertFailsWith { + 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) + } +} \ No newline at end of file diff --git a/network-management/src/test/resources/generator_fail.conf b/network-management/src/test/resources/generator_fail.conf new file mode 100644 index 0000000000..28bcc921c8 --- /dev/null +++ b/network-management/src/test/resources/generator_fail.conf @@ -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" + } +] diff --git a/network-management/src/test/resources/hsm.conf b/network-management/src/test/resources/hsm.conf index 774043c4bc..def056018f 100644 --- a/network-management/src/test/resources/hsm.conf +++ b/network-management/src/test/resources/hsm.conf @@ -3,6 +3,7 @@ keyGroup = "DEV.DOORMAN" keySpecifier = -1 authMode = PASSWORD csrPrivateKeyPassword = "" +csrCertCrlDistPoint = "http://test.com/revoked.crl" networkMapPrivateKeyPassword = "" rootPrivateKeyPassword = "" keyGroup = "DEV.DOORMAN" diff --git a/network-management/src/test/resources/hsm_fail.conf b/network-management/src/test/resources/hsm_fail.conf index 23d0593b4a..6d748db0f6 100644 --- a/network-management/src/test/resources/hsm_fail.conf +++ b/network-management/src/test/resources/hsm_fail.conf @@ -3,6 +3,7 @@ keyGroup = "DEV.DOORMAN" keySpecifier = -1 authMode = PASSWORD csrPrivateKeyPassword = "" +csrCertCrlDistPoint = "http://test.com/revoked.crl" networkMapPrivateKeyPassword = "" rootPrivateKeyPassword = "" keyGroup = "DEV.DOORMAN" \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 1341d72878..ab70260eaf 100644 --- a/settings.gradle +++ b/settings.gradle @@ -34,6 +34,7 @@ include 'perftestcordapp' include 'network-management' include 'network-management:capsule' include 'network-management:capsule-hsm' +include 'network-management:capsule-hsm-cert-generator' include 'tools:jmeter' include 'tools:explorer' include 'tools:explorer:capsule'