mirror of
https://github.com/corda/corda.git
synced 2024-12-26 16:11:12 +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
|
||||
|
||||
// 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"
|
||||
|
||||
/** 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")
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
run(parseParameters(parseCommandLine(*args).configFile))
|
||||
run(parseParameters(parseCommandLine(*args)?.configFile))
|
||||
}
|
||||
|
||||
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_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
|
||||
internal object DevCaHelper {
|
||||
fun loadDevCa(alias: String): CertificateAndKeyPair {
|
||||
// TODO: Should be identity scheme
|
||||
val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass")
|
||||
return caKeyStore.getCertificateAndKeyPair(alias, "cordacadevkeypass")
|
||||
val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/$DEV_CA_KEY_STORE_FILE"), "$DEV_CA_KEY_STORE_PASS")
|
||||
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)
|
||||
.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.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.div
|
||||
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.createDevKeyStores
|
||||
import net.corda.nodeapi.internal.crypto.X509KeyStore
|
||||
@ -55,7 +57,7 @@ fun NodeConfiguration.configureWithDevSSLCertificate() = configureDevKeyAndTrust
|
||||
fun SSLConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name) {
|
||||
certificatesDirectory.createDirectories()
|
||||
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()) {
|
||||
val (nodeKeyStore) = createDevKeyStores(myLegalName)
|
||||
|
Loading…
Reference in New Issue
Block a user