mirror of
https://github.com/corda/corda.git
synced 2024-12-26 16:11:12 +00:00
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:
parent
d2795954cb
commit
789ce5d44a
28
docs/source/hsm-cert-generator.rst
Normal file
28
docs/source/hsm-cert-generator.rst
Normal 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.
|
87
docs/source/running-hsm-cert-generator.rst
Normal file
87
docs/source/running-hsm-cert-generator.rst
Normal 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.
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
```
|
||||
|
||||
## 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
|
||||
### 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-<version>.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-<version>.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-<version>.jar --update-network-parameters network-parameters.conf
|
||||
```
|
||||
```
|
39
network-management/capsule-hsm-cert-generator/build.gradle
Normal file
39
network-management/capsule-hsm-cert-generator/build.gradle
Normal 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
|
||||
}
|
30
network-management/generator.conf
Normal file
30
network-management/generator.conf
Normal 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"
|
||||
}
|
||||
]
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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<NetworkParameters>
|
||||
typealias SignedNetworkMap = SignedDataWithCert<NetworkMap>
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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:")
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
@ -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<CertificateNameAndPass>,
|
||||
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<X509Certificate> {
|
||||
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<X509Certificate> {
|
||||
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)
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
@ -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:")
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
data class KeyPairAndCertificateChain(val keyPair: KeyPair, val certificateChain: Array<X509Certificate>)
|
@ -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)
|
||||
}
|
||||
}
|
12
network-management/src/test/resources/generator_fail.conf
Normal file
12
network-management/src/test/resources/generator_fail.conf
Normal 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"
|
||||
}
|
||||
]
|
@ -3,6 +3,7 @@ keyGroup = "DEV.DOORMAN"
|
||||
keySpecifier = -1
|
||||
authMode = PASSWORD
|
||||
csrPrivateKeyPassword = ""
|
||||
csrCertCrlDistPoint = "http://test.com/revoked.crl"
|
||||
networkMapPrivateKeyPassword = ""
|
||||
rootPrivateKeyPassword = ""
|
||||
keyGroup = "DEV.DOORMAN"
|
||||
|
@ -3,6 +3,7 @@ keyGroup = "DEV.DOORMAN"
|
||||
keySpecifier = -1
|
||||
authMode = PASSWORD
|
||||
csrPrivateKeyPassword = ""
|
||||
csrCertCrlDistPoint = "http://test.com/revoked.crl"
|
||||
networkMapPrivateKeyPassword = ""
|
||||
rootPrivateKeyPassword = ""
|
||||
keyGroup = "DEV.DOORMAN"
|
@ -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'
|
||||
|
Loading…
Reference in New Issue
Block a user