mirror of
https://github.com/corda/corda.git
synced 2024-12-27 08:22:35 +00:00
Making BasicConstraints a critical extension (#420)
Path length will be decided in another ticket - https://r3-cev.atlassian.net/browse/ENT-1508
This commit is contained in:
parent
453029e548
commit
8c5f0ac0ca
40
docs/source/running-dev-keystore-generator.rst
Normal file
40
docs/source/running-dev-keystore-generator.rst
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
Running the dev keystore generator
|
||||||
|
==================================
|
||||||
|
|
||||||
|
The dev keystore generator is a utility tool designed only for internal use. Sometimes our certificates change (e.g. new
|
||||||
|
extensions are added, some of them are modified...). In order to stay consistent with the rest of the Corda platform and in
|
||||||
|
particular with Corda node (and its DEV execution mode), we need a facility that would allow us to easily create keystore containing
|
||||||
|
both root and doorman certificates together with their keys. Those certificates will reflect the most recent state of the Corda certificates.
|
||||||
|
In addition, a truststore file (containing the root certificate) is also generated. Once generated, those files (i.e. keystore and truststore)
|
||||||
|
can be copied to an appropriate node directory.
|
||||||
|
|
||||||
|
Although, the output of the tool is strongly bound to the node execution process (i.e. expected key store file name, trust store file name, passwords are hardcoded),
|
||||||
|
it can be used to generate arbitrary keystore and truststore files with Corda certificates. Therefore, the tool supports a custom configuration.
|
||||||
|
|
||||||
|
Configuration file
|
||||||
|
------------------
|
||||||
|
At startup the dev generator tool reads a configuration file, passed with ``--config-file`` on the command line.
|
||||||
|
|
||||||
|
This is an example of what a generator configuration file might look like:
|
||||||
|
.. literalinclude:: ../../network-management/dev-generator.conf
|
||||||
|
|
||||||
|
Invoke the tool with ``-?`` for a full list of supported command-line arguments.
|
||||||
|
|
||||||
|
If no configuration file is provided, all the options default to the node expected values.
|
||||||
|
|
||||||
|
|
||||||
|
Configuration parameters
|
||||||
|
------------------------
|
||||||
|
Allowed parameters are:
|
||||||
|
|
||||||
|
:privateKeyPass: Password for both Root and Doorman private keys. Default value: "cordacadevkeypass".
|
||||||
|
|
||||||
|
:keyStorePass: Password for the keystore file. Default value: "cordacadevpass".
|
||||||
|
|
||||||
|
:keyStoreFileName: File name for the keystore file. Default value: "cordadevcakeys.jks".
|
||||||
|
|
||||||
|
:trustStorePass: Password for the truststore file. Default value: "trustpass".
|
||||||
|
|
||||||
|
:trustStoreFileName: File name for the truststore file. Default value: "cordatruststore.jks".
|
||||||
|
|
||||||
|
:directory: Directory in which both keystore and trustore files should be created. Default value: "./certificates"
|
6
network-management/dev-generator.conf
Normal file
6
network-management/dev-generator.conf
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
privateKeyPass ="cordacadevkeypass"
|
||||||
|
keyStorePass = "cordacadevpass"
|
||||||
|
keyStoreFileName = "cordadevcakeys.jks"
|
||||||
|
trustStorePass = "trustpass"
|
||||||
|
trustStoreFileName = "cordatruststore.jks"
|
||||||
|
directory = "./certificates"
|
@ -0,0 +1,63 @@
|
|||||||
|
package com.r3.corda.networkmanage.dev
|
||||||
|
|
||||||
|
import com.r3.corda.networkmanage.common.utils.ShowHelpException
|
||||||
|
import com.r3.corda.networkmanage.hsm.generator.CommandLineOptions
|
||||||
|
import com.typesafe.config.ConfigFactory
|
||||||
|
import com.typesafe.config.ConfigParseOptions
|
||||||
|
import joptsimple.OptionParser
|
||||||
|
import net.corda.nodeapi.internal.*
|
||||||
|
import net.corda.nodeapi.internal.config.parseAs
|
||||||
|
import java.io.File
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.Paths
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds configuration necessary for generating DEV key store and trust store.
|
||||||
|
*/
|
||||||
|
data class GeneratorConfiguration(val privateKeyPass: String = DEV_CA_PRIVATE_KEY_PASS,
|
||||||
|
val keyStorePass: String = DEV_CA_KEY_STORE_PASS,
|
||||||
|
val keyStoreFileName: String = DEV_CA_KEY_STORE_FILE,
|
||||||
|
val trustStorePass: String = DEV_CA_TRUST_STORE_PASS,
|
||||||
|
val trustStoreFileName: String = DEV_CA_TRUST_STORE_FILE,
|
||||||
|
val directory: Path = DEFAULT_DIRECTORY) {
|
||||||
|
companion object {
|
||||||
|
val DEFAULT_DIRECTORY = File("./certificates").toPath()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses dev 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.
|
||||||
|
if (optionSet.has(helpOption)) {
|
||||||
|
throw ShowHelpException(optionParser)
|
||||||
|
}
|
||||||
|
return if (optionSet.has(configFileArg)) {
|
||||||
|
CommandLineOptions(Paths.get(optionSet.valueOf(configFileArg)).toAbsolutePath())
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a configuration file, which contains all the configuration - i.e. for the key store generator.
|
||||||
|
*/
|
||||||
|
fun parseParameters(configFile: Path?): GeneratorConfiguration {
|
||||||
|
return if (configFile == null) {
|
||||||
|
GeneratorConfiguration()
|
||||||
|
} else {
|
||||||
|
ConfigFactory
|
||||||
|
.parseFile(configFile.toFile(), ConfigParseOptions.defaults().setAllowMissing(true))
|
||||||
|
.resolve()
|
||||||
|
.parseAs()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
package com.r3.corda.networkmanage.dev
|
||||||
|
|
||||||
|
import com.r3.corda.networkmanage.doorman.CORDA_X500_BASE
|
||||||
|
import net.corda.core.crypto.Crypto
|
||||||
|
import net.corda.core.internal.createDirectories
|
||||||
|
import net.corda.core.internal.div
|
||||||
|
import net.corda.nodeapi.internal.crypto.CertificateType
|
||||||
|
import net.corda.nodeapi.internal.crypto.X509KeyStore
|
||||||
|
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||||
|
import org.apache.logging.log4j.LogManager
|
||||||
|
import java.io.File
|
||||||
|
import javax.security.auth.x500.X500Principal
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
private val logger = LogManager.getLogger("com.r3.corda.networkmanage.dev.Main")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is an internal utility method used to generate a DEV certificate store file containing both root and doorman keys/certificates.
|
||||||
|
* Additionally, it generates a trust file containing the root certificate.
|
||||||
|
*
|
||||||
|
* Note: It can be quickly executed in IntelliJ by right-click on the main method. It will generate the keystore and the trustore
|
||||||
|
* with settings expected by node running in the dev mode. The files will be generated in the root project directory.
|
||||||
|
* Look for the 'certificates' directory.
|
||||||
|
*/
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
run(parseParameters(parseCommandLine(*args)?.configFile))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun run(configuration: GeneratorConfiguration) {
|
||||||
|
configuration.run {
|
||||||
|
val keyStoreFile = File("$directory/$keyStoreFileName").toPath()
|
||||||
|
keyStoreFile.parent.createDirectories()
|
||||||
|
val keyStore = X509KeyStore.fromFile(keyStoreFile, keyStorePass, createNew = true)
|
||||||
|
|
||||||
|
checkCertificateNotInKeyStore(X509Utilities.CORDA_ROOT_CA, keyStore) { exitProcess(1) }
|
||||||
|
checkCertificateNotInKeyStore(X509Utilities.CORDA_INTERMEDIATE_CA, keyStore) { exitProcess(1) }
|
||||||
|
|
||||||
|
val rootKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||||
|
val rootCert = X509Utilities.createSelfSignedCACertificate(
|
||||||
|
X500Principal("CN=Corda Root CA,$CORDA_X500_BASE"),
|
||||||
|
rootKeyPair)
|
||||||
|
keyStore.update {
|
||||||
|
setPrivateKey(X509Utilities.CORDA_ROOT_CA, rootKeyPair.private, listOf(rootCert), privateKeyPass)
|
||||||
|
}
|
||||||
|
logger.info("Root CA keypair and certificate stored in ${keyStoreFile.toAbsolutePath()}.")
|
||||||
|
logger.info(rootCert)
|
||||||
|
|
||||||
|
val trustStorePath = directory / trustStoreFileName
|
||||||
|
X509KeyStore.fromFile(trustStorePath, trustStorePass, createNew = true).update {
|
||||||
|
setCertificate(X509Utilities.CORDA_ROOT_CA, rootCert)
|
||||||
|
}
|
||||||
|
logger.info("Trust store for distribution to nodes created in $trustStorePath")
|
||||||
|
|
||||||
|
val doormanKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||||
|
val cert = X509Utilities.createCertificate(
|
||||||
|
CertificateType.INTERMEDIATE_CA,
|
||||||
|
rootCert,
|
||||||
|
rootKeyPair,
|
||||||
|
X500Principal("CN=Corda Doorman CA,$CORDA_X500_BASE"),
|
||||||
|
doormanKeyPair.public
|
||||||
|
)
|
||||||
|
keyStore.update {
|
||||||
|
setPrivateKey(X509Utilities.CORDA_INTERMEDIATE_CA, doormanKeyPair.private, listOf(cert, rootCert), privateKeyPass)
|
||||||
|
}
|
||||||
|
logger.info("Doorman CA keypair and certificate stored in ${keyStoreFile.toAbsolutePath()}.")
|
||||||
|
logger.info(cert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkCertificateNotInKeyStore(certAlias: String, keyStore: X509KeyStore, onFail: () -> Unit) {
|
||||||
|
if (certAlias in keyStore) {
|
||||||
|
logger.info("$certAlias already exists in keystore, process will now terminate.")
|
||||||
|
logger.info(keyStore.getCertificate(certAlias))
|
||||||
|
onFail.invoke()
|
||||||
|
}
|
||||||
|
}
|
@ -13,7 +13,7 @@ import javax.security.auth.x500.X500Principal
|
|||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
// TODO The cert subjects need to be configurable
|
// TODO The cert subjects need to be configurable
|
||||||
private const val CORDA_X500_BASE = "O=R3 HoldCo LLC,OU=Corda,L=New York,C=US"
|
const val CORDA_X500_BASE = "O=R3 HoldCo LLC,OU=Corda,L=New York,C=US"
|
||||||
const val NETWORK_ROOT_TRUSTSTORE_FILENAME = "network-root-truststore.jks"
|
const val NETWORK_ROOT_TRUSTSTORE_FILENAME = "network-root-truststore.jks"
|
||||||
|
|
||||||
/** Read password from console, do a readLine instead if console is null (e.g. when debugging in IDE). */
|
/** Read password from console, do a readLine instead if console is null (e.g. when debugging in IDE). */
|
||||||
|
@ -8,7 +8,7 @@ import org.apache.logging.log4j.LogManager
|
|||||||
private val log = LogManager.getLogger("com.r3.corda.networkmanage.hsm.generator.Main")
|
private val log = LogManager.getLogger("com.r3.corda.networkmanage.hsm.generator.Main")
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
run(parseParameters(parseCommandLine(*args).configFile))
|
run(parseParameters(parseCommandLine(*args)?.configFile))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun run(parameters: GeneratorParameters) {
|
fun run(parameters: GeneratorParameters) {
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
package com.r3.corda.networkmanage.dev
|
||||||
|
|
||||||
|
import net.corda.nodeapi.internal.crypto.X509KeyStore
|
||||||
|
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.rules.TemporaryFolder
|
||||||
|
import java.io.File
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
class DevGeneratorTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
@JvmField
|
||||||
|
val tempFolder = TemporaryFolder()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `key store and trust store are created and contain the certificates`() {
|
||||||
|
// given
|
||||||
|
val config = GeneratorConfiguration(directory = tempFolder.root.toPath())
|
||||||
|
|
||||||
|
// when
|
||||||
|
run(config)
|
||||||
|
|
||||||
|
// then
|
||||||
|
val keyStoreFile = File("${config.directory}/${config.keyStoreFileName}").toPath()
|
||||||
|
val keyStore = X509KeyStore.fromFile(keyStoreFile, config.keyStorePass, createNew = true)
|
||||||
|
|
||||||
|
assertTrue(X509Utilities.CORDA_INTERMEDIATE_CA in keyStore)
|
||||||
|
assertTrue(X509Utilities.CORDA_ROOT_CA in keyStore)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package com.r3.corda.networkmanage.dev
|
||||||
|
|
||||||
|
import com.r3.corda.networkmanage.hsm.generator.parseCommandLine
|
||||||
|
import net.corda.nodeapi.internal.DEV_CA_KEY_STORE_FILE
|
||||||
|
import net.corda.nodeapi.internal.DEV_CA_KEY_STORE_PASS
|
||||||
|
import net.corda.nodeapi.internal.DEV_CA_TRUST_STORE_FILE
|
||||||
|
import net.corda.nodeapi.internal.DEV_CA_TRUST_STORE_PASS
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.rules.TemporaryFolder
|
||||||
|
import java.io.File
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class GeneratorConfigurationTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
@JvmField
|
||||||
|
val tempFolder = TemporaryFolder()
|
||||||
|
|
||||||
|
private val configPath = File("dev-generator.conf").absolutePath
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `config file is parsed correctly`() {
|
||||||
|
val config = parseParameters(parseCommandLine("--config-file", configPath).configFile)
|
||||||
|
assertEquals(GeneratorConfiguration.DEFAULT_DIRECTORY, config.directory)
|
||||||
|
assertEquals(DEV_CA_KEY_STORE_FILE, config.keyStoreFileName)
|
||||||
|
assertEquals(DEV_CA_KEY_STORE_PASS, config.keyStorePass)
|
||||||
|
assertEquals(DEV_CA_TRUST_STORE_PASS, config.trustStorePass)
|
||||||
|
assertEquals(DEV_CA_TRUST_STORE_FILE, config.trustStoreFileName)
|
||||||
|
}
|
||||||
|
}
|
@ -95,14 +95,18 @@ fun createDevNodeCa(intermediateCa: CertificateAndKeyPair,
|
|||||||
}
|
}
|
||||||
|
|
||||||
val DEV_INTERMEDIATE_CA: CertificateAndKeyPair get() = DevCaHelper.loadDevCa(X509Utilities.CORDA_INTERMEDIATE_CA)
|
val DEV_INTERMEDIATE_CA: CertificateAndKeyPair get() = DevCaHelper.loadDevCa(X509Utilities.CORDA_INTERMEDIATE_CA)
|
||||||
|
|
||||||
val DEV_ROOT_CA: CertificateAndKeyPair get() = DevCaHelper.loadDevCa(X509Utilities.CORDA_ROOT_CA)
|
val DEV_ROOT_CA: CertificateAndKeyPair get() = DevCaHelper.loadDevCa(X509Utilities.CORDA_ROOT_CA)
|
||||||
|
val DEV_CA_PRIVATE_KEY_PASS: String = "cordacadevkeypass"
|
||||||
|
val DEV_CA_KEY_STORE_FILE: String = "cordadevcakeys.jks"
|
||||||
|
val DEV_CA_KEY_STORE_PASS: String = "cordacadevpass"
|
||||||
|
val DEV_CA_TRUST_STORE_FILE: String = "cordatruststore.jks"
|
||||||
|
val DEV_CA_TRUST_STORE_PASS: String = "trustpass"
|
||||||
|
|
||||||
// We need a class so that we can get hold of the class loader
|
// We need a class so that we can get hold of the class loader
|
||||||
internal object DevCaHelper {
|
internal object DevCaHelper {
|
||||||
fun loadDevCa(alias: String): CertificateAndKeyPair {
|
fun loadDevCa(alias: String): CertificateAndKeyPair {
|
||||||
// TODO: Should be identity scheme
|
// TODO: Should be identity scheme
|
||||||
val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass")
|
val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/$DEV_CA_KEY_STORE_FILE"), "$DEV_CA_KEY_STORE_PASS")
|
||||||
return caKeyStore.getCertificateAndKeyPair(alias, "cordacadevkeypass")
|
return caKeyStore.getCertificateAndKeyPair(alias, "$DEV_CA_PRIVATE_KEY_PASS")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -159,7 +159,7 @@ object X509Utilities {
|
|||||||
|
|
||||||
val builder = JcaX509v3CertificateBuilder(issuer, serial, validityWindow.first, validityWindow.second, subject, subjectPublicKey)
|
val builder = JcaX509v3CertificateBuilder(issuer, serial, validityWindow.first, validityWindow.second, subject, subjectPublicKey)
|
||||||
.addExtension(Extension.subjectKeyIdentifier, false, BcX509ExtensionUtils().createSubjectKeyIdentifier(subjectPublicKeyInfo))
|
.addExtension(Extension.subjectKeyIdentifier, false, BcX509ExtensionUtils().createSubjectKeyIdentifier(subjectPublicKeyInfo))
|
||||||
.addExtension(Extension.basicConstraints, certificateType.isCA, BasicConstraints(certificateType.isCA))
|
.addExtension(Extension.basicConstraints, true, BasicConstraints(certificateType.isCA))
|
||||||
.addExtension(Extension.keyUsage, false, certificateType.keyUsage)
|
.addExtension(Extension.keyUsage, false, certificateType.keyUsage)
|
||||||
.addExtension(Extension.extendedKeyUsage, false, keyPurposes)
|
.addExtension(Extension.extendedKeyUsage, false, keyPurposes)
|
||||||
|
|
||||||
|
Binary file not shown.
Binary file not shown.
@ -8,6 +8,8 @@ import net.corda.core.identity.CordaX500Name
|
|||||||
import net.corda.core.internal.createDirectories
|
import net.corda.core.internal.createDirectories
|
||||||
import net.corda.core.internal.div
|
import net.corda.core.internal.div
|
||||||
import net.corda.core.internal.exists
|
import net.corda.core.internal.exists
|
||||||
|
import net.corda.nodeapi.internal.DEV_CA_TRUST_STORE_FILE
|
||||||
|
import net.corda.nodeapi.internal.DEV_CA_TRUST_STORE_PASS
|
||||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||||
import net.corda.nodeapi.internal.createDevKeyStores
|
import net.corda.nodeapi.internal.createDevKeyStores
|
||||||
import net.corda.nodeapi.internal.crypto.X509KeyStore
|
import net.corda.nodeapi.internal.crypto.X509KeyStore
|
||||||
@ -55,7 +57,7 @@ fun NodeConfiguration.configureWithDevSSLCertificate() = configureDevKeyAndTrust
|
|||||||
fun SSLConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name) {
|
fun SSLConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name) {
|
||||||
certificatesDirectory.createDirectories()
|
certificatesDirectory.createDirectories()
|
||||||
if (!trustStoreFile.exists()) {
|
if (!trustStoreFile.exists()) {
|
||||||
loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordatruststore.jks"), "trustpass").save(trustStoreFile, trustStorePassword)
|
loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/$DEV_CA_TRUST_STORE_FILE"), "$DEV_CA_TRUST_STORE_PASS").save(trustStoreFile, trustStorePassword)
|
||||||
}
|
}
|
||||||
if (!sslKeystore.exists() || !nodeKeystore.exists()) {
|
if (!sslKeystore.exists() || !nodeKeystore.exists()) {
|
||||||
val (nodeKeyStore) = createDevKeyStores(myLegalName)
|
val (nodeKeyStore) = createDevKeyStores(myLegalName)
|
||||||
|
Loading…
Reference in New Issue
Block a user