diff --git a/docs/source/ha-utilities.rst b/docs/source/ha-utilities.rst index 656bf03873..555d5322d3 100644 --- a/docs/source/ha-utilities.rst +++ b/docs/source/ha-utilities.rst @@ -12,7 +12,7 @@ To run simply pass in the file or URL as the first parameter: .. parsed-literal:: - > java -jar |jar_name| + > java -jar |jar_name| .. @@ -82,12 +82,13 @@ Command-line options ha-utilities generate-internal-ssl-keystores [-hvV] [--logging-level=] [-b=] [-c=] [-l=] [-o=] [-p=] * ``-v``, ``--verbose``, ``--log-to-console``: If set, prints logging to the console as well as to a file. -* ``--logging-level=``: Enable logging at this level and higher. Possible values:ERROR, WARN, INFO, DEBUG, TRACE. Default: INFO -* ``-p``, ``--password=``: Default password for all generated keystore and private keys. Default: changeit +* ``--logging-level=``: Enable logging at this level and higher. Possible values: ERROR, WARN, INFO, DEBUG, TRACE. Default: INFO +* ``-p``, ``--keyStorePassword=``: Password for all generated keystores. Default: changeit +* ``-e``, ``--entryPassword=``: Password for all the keystores private keys. Default: changeit +* ``-t``, ``--trustStorePassword=``: Password for all the trust stores. Default: changeit * ``-o``, ``--organization=``: X500Name's organization attribute. Default: Corda * ``-l``, ``--locality=``: X500Name's locality attribute. Default: London * ``-c``, ``--country=``: X500Name's country attribute. Default: GB * ``-b``, ``--base-directory=``: The node working directory where all the files are kept. * ``-h``, ``--help``: Show this help message and exit. -* ``-V``, ``--version``: Print version information and exit. - +* ``-V``, ``--version``: Print version information and exit. \ No newline at end of file diff --git a/tools/ha-utilities/src/main/kotlin/com/r3/ha/utilities/InternalKeystoreGenerator.kt b/tools/ha-utilities/src/main/kotlin/com/r3/ha/utilities/InternalKeystoreGenerator.kt index 09fc351993..a3b51e30ba 100644 --- a/tools/ha-utilities/src/main/kotlin/com/r3/ha/utilities/InternalKeystoreGenerator.kt +++ b/tools/ha-utilities/src/main/kotlin/com/r3/ha/utilities/InternalKeystoreGenerator.kt @@ -24,8 +24,12 @@ class InternalKeystoreGenerator : CliWrapperBase("generate-internal-ssl-keystore var baseDirectory: Path = Paths.get(".").toAbsolutePath().normalize() // TODO: options to generate keys for different HA deployment mode? - @Option(names = ["-p", "--password"], description = ["Default password for all generated keystore and private keys."], defaultValue = DEFAULT_PASSWORD) - lateinit var password: String + @Option(names = ["-p", "--keyStorePassword"], description = ["Password for all generated keystores."], defaultValue = DEFAULT_PASSWORD) + lateinit var keyStorePassword: String + @Option(names = ["-e", "--entryPassword"], description = ["Password for all the keystores private keys."], defaultValue = DEFAULT_PASSWORD) + lateinit var entryPassword: String + @Option(names = ["-t", "--trustStorePassword"], description = ["Password for all the trust stores."], defaultValue = DEFAULT_PASSWORD) + lateinit var trustStorePassword: String @Option(names = ["-o", "--organization"], description = ["X500Name's organization attribute."], defaultValue = "Corda") lateinit var organization: String @Option(names = ["-u", "--organization-unit"], description = ["X500Name's organization unit attribute."], required = false) @@ -38,42 +42,49 @@ class InternalKeystoreGenerator : CliWrapperBase("generate-internal-ssl-keystore override fun runProgram(): Int { // Create tunnel certs val tunnelCertDir = baseDirectory / "tunnel" - val tunnelRoot = createRootKeystore("Internal Tunnel Root", tunnelCertDir / "tunnel-root.jks", tunnelCertDir / "tunnel-truststore.jks").getCertificateAndKeyPair(X509Utilities.CORDA_ROOT_CA, password) - createTLSKeystore("float", tunnelRoot, tunnelCertDir / "float.jks") + val tunnelRoot = createRootKeystore("Internal Tunnel Root", tunnelCertDir / "tunnel-root.jks", tunnelCertDir / "tunnel-truststore.jks", + keyStorePassword, entryPassword, trustStorePassword).getCertificateAndKeyPair(X509Utilities.CORDA_ROOT_CA, entryPassword) + createTLSKeystore("float", tunnelRoot, tunnelCertDir / "float.jks", keyStorePassword, entryPassword) + createTLSKeystore("bridge", tunnelRoot, tunnelCertDir / "bridge.jks", keyStorePassword, entryPassword) // Create artemis certs + // NB: For Artemis store password must be the same as key password due to implementation restrictions. val artemisCertDir = baseDirectory / "artemis" - val root = createRootKeystore("Internal Artemis Root", artemisCertDir / "artemis-root.jks", artemisCertDir / "artemis-truststore.jks").getCertificateAndKeyPair(X509Utilities.CORDA_ROOT_CA, password) - createTLSKeystore("bridge", root, artemisCertDir / "bridge.jks") - createTLSKeystore("artemis", root, artemisCertDir / "artemis.jks") - createTLSKeystore("artemis-client", root, artemisCertDir / "artemis-client.jks") + val root = createRootKeystore("Internal Artemis Root", artemisCertDir / "artemis-root.jks", artemisCertDir / "artemis-truststore.jks", + keyStorePassword, keyStorePassword, trustStorePassword).getCertificateAndKeyPair(X509Utilities.CORDA_ROOT_CA, keyStorePassword) + createTLSKeystore("bridge", root, artemisCertDir / "bridge.jks", keyStorePassword, keyStorePassword) + createTLSKeystore("artemis", root, artemisCertDir / "artemis.jks", keyStorePassword, keyStorePassword) + createTLSKeystore("artemis-client", root, artemisCertDir / "artemis-client.jks", keyStorePassword, keyStorePassword) - if (password == DEFAULT_PASSWORD) { - println("Password is defaulted to $DEFAULT_PASSWORD, please change the keystores password using java keytool.") + listOf(keyStorePassword to "keyStorePassword", entryPassword to "entryPassword", trustStorePassword to "trustStorePassword").forEach { + if(it.first == DEFAULT_PASSWORD) { + println("WARN: Password for '${it.second}' is defaulted to '$DEFAULT_PASSWORD'. Please consider changing the password using java keytool.") + } } + return ExitCodes.SUCCESS } - private fun createRootKeystore(commonName: String, keystorePath: Path, trustStorePath: Path): X509KeyStore { + private fun createRootKeystore(commonName: String, keystorePath: Path, trustStorePath: Path, storePassword: String, entryPassword: String, trustStorePassword: String): X509KeyStore { val key = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val rootCert = X509Utilities.createSelfSignedCACertificate(getX500Principal(commonName), key) - val keystore = X509KeyStore.fromFile(keystorePath, password, createNew = true) + val keystore = X509KeyStore.fromFile(keystorePath, storePassword, createNew = true) keystore.update { - setPrivateKey(X509Utilities.CORDA_ROOT_CA, key.private, listOf(rootCert), password) + setPrivateKey(X509Utilities.CORDA_ROOT_CA, key.private, listOf(rootCert), entryPassword) } println("$commonName keystore created in $keystorePath.") - X509KeyStore.fromFile(trustStorePath, password, createNew = true).setCertificate(X509Utilities.CORDA_ROOT_CA, rootCert) + X509KeyStore.fromFile(trustStorePath, trustStorePassword, createNew = true).setCertificate(X509Utilities.CORDA_ROOT_CA, rootCert) println("$commonName truststore created in $trustStorePath.") return keystore } - private fun createTLSKeystore(serviceName: String, root: CertificateAndKeyPair, keystorePath: Path) { + private fun createTLSKeystore(serviceName: String, root: CertificateAndKeyPair, keystorePath: Path, storePassword: String, entryPassword: String) { val key = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val cert = X509Utilities.createCertificate(CertificateType.TLS, root.certificate, root.keyPair, getX500Principal(serviceName), key.public) - X509KeyStore.fromFile(keystorePath, password, createNew = true).update { - setPrivateKey(X509Utilities.CORDA_CLIENT_TLS, key.private, listOf(cert, root.certificate), password) + X509KeyStore.fromFile(keystorePath, storePassword, createNew = true).update { + setPrivateKey(X509Utilities.CORDA_CLIENT_TLS, key.private, listOf(cert, root.certificate), entryPassword) } println("Internal TLS keystore for '$serviceName' created in $keystorePath.") } diff --git a/tools/ha-utilities/src/test/kotlin/com/r3/ha/utilities/InternalKeystoreGeneratorTest.kt b/tools/ha-utilities/src/test/kotlin/com/r3/ha/utilities/InternalKeystoreGeneratorTest.kt index a21a4a1ffd..82cc1f0a28 100644 --- a/tools/ha-utilities/src/test/kotlin/com/r3/ha/utilities/InternalKeystoreGeneratorTest.kt +++ b/tools/ha-utilities/src/test/kotlin/com/r3/ha/utilities/InternalKeystoreGeneratorTest.kt @@ -4,13 +4,16 @@ import net.corda.cliutils.CommonCliConstants.BASE_DIR import net.corda.core.internal.div import net.corda.core.internal.exists import net.corda.nodeapi.internal.crypto.X509KeyStore -import net.corda.nodeapi.internal.crypto.X509Utilities -import org.assertj.core.api.Assertions +import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA +import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_TLS +import net.corda.nodeapi.internal.crypto.getCertificateAndKeyPair import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder import picocli.CommandLine +import kotlin.test.assertEquals import kotlin.test.assertFalse +import kotlin.test.assertNotNull import kotlin.test.assertTrue class InternalKeystoreGeneratorTest { @@ -23,40 +26,52 @@ class InternalKeystoreGeneratorTest { @Test fun `generate keystores correctly`() { val workingDirectory = tempFolder.root.toPath() - CommandLine.populateCommand(generator, BASE_DIR, workingDirectory.toString()) - Assertions.assertThat(generator.baseDirectory).isEqualTo(workingDirectory) + val keyStorePassword = "keyStorePassword" + val entryPassword = "entryPassword" + val trustStorePassword = "trustStorePassword" + CommandLine.populateCommand(generator, + BASE_DIR, workingDirectory.toString(), + "--keyStorePassword", keyStorePassword, + "--entryPassword", entryPassword, + "--trustStorePassword", trustStorePassword) + assertEquals(workingDirectory, generator.baseDirectory) + assertEquals(keyStorePassword, generator.keyStorePassword) + assertEquals(entryPassword, generator.entryPassword) + assertEquals(trustStorePassword, generator.trustStorePassword) generator.runProgram() - listOf("float.jks").map { workingDirectory / "tunnel" / it }.forEach { + listOf("float.jks", "bridge.jks").map { workingDirectory / "tunnel" / it }.forEach { assertTrue(it.exists()) - assertTrue(X509KeyStore.fromFile(it, generator.password).contains(X509Utilities.CORDA_CLIENT_TLS)) - assertTrue(X509KeyStore.fromFile(it, generator.password).internal.isKeyEntry(X509Utilities.CORDA_CLIENT_TLS)) + assertTrue(X509KeyStore.fromFile(it, keyStorePassword).contains(CORDA_CLIENT_TLS)) + assertTrue(X509KeyStore.fromFile(it, keyStorePassword).internal.isKeyEntry(CORDA_CLIENT_TLS)) + assertNotNull(X509KeyStore.fromFile(it, keyStorePassword).internal.getCertificateAndKeyPair(CORDA_CLIENT_TLS, entryPassword)) } - X509KeyStore.fromFile(workingDirectory / "tunnel" / "tunnel-root.jks", generator.password).update { - assertTrue(contains(X509Utilities.CORDA_ROOT_CA)) - assertTrue(internal.isKeyEntry(X509Utilities.CORDA_ROOT_CA)) + X509KeyStore.fromFile(workingDirectory / "tunnel" / "tunnel-root.jks", keyStorePassword).update { + assertTrue(contains(CORDA_ROOT_CA)) + assertTrue(internal.isKeyEntry(CORDA_ROOT_CA)) + assertNotNull(internal.getCertificateAndKeyPair(CORDA_ROOT_CA, entryPassword)) } - X509KeyStore.fromFile(workingDirectory / "tunnel" / "tunnel-truststore.jks", generator.password).update { - assertTrue(contains(X509Utilities.CORDA_ROOT_CA)) - assertFalse(internal.isKeyEntry(X509Utilities.CORDA_ROOT_CA)) + X509KeyStore.fromFile(workingDirectory / "tunnel" / "tunnel-truststore.jks", trustStorePassword).update { + assertTrue(internal.isCertificateEntry(CORDA_ROOT_CA)) + assertFalse(internal.isKeyEntry(CORDA_ROOT_CA)) } listOf("bridge.jks", "artemis.jks", "artemis-client.jks").map { workingDirectory / "artemis" / it }.forEach { assertTrue(it.exists()) - assertTrue(X509KeyStore.fromFile(it, generator.password).contains(X509Utilities.CORDA_CLIENT_TLS)) - assertTrue(X509KeyStore.fromFile(it, generator.password).internal.isKeyEntry(X509Utilities.CORDA_CLIENT_TLS)) + assertTrue(X509KeyStore.fromFile(it, keyStorePassword).contains(CORDA_CLIENT_TLS)) + assertTrue(X509KeyStore.fromFile(it, keyStorePassword).internal.isKeyEntry(CORDA_CLIENT_TLS)) } - X509KeyStore.fromFile(workingDirectory / "artemis" / "artemis-root.jks", generator.password).update { - assertTrue(contains(X509Utilities.CORDA_ROOT_CA)) - assertTrue(internal.isKeyEntry(X509Utilities.CORDA_ROOT_CA)) + X509KeyStore.fromFile(workingDirectory / "artemis" / "artemis-root.jks", keyStorePassword).update { + assertTrue(contains(CORDA_ROOT_CA)) + assertTrue(internal.isKeyEntry(CORDA_ROOT_CA)) } - X509KeyStore.fromFile(workingDirectory / "artemis" / "artemis-truststore.jks", generator.password).update { - assertTrue(contains(X509Utilities.CORDA_ROOT_CA)) - assertFalse(internal.isKeyEntry(X509Utilities.CORDA_ROOT_CA)) + X509KeyStore.fromFile(workingDirectory / "artemis" / "artemis-truststore.jks", trustStorePassword).update { + assertTrue(contains(CORDA_ROOT_CA)) + assertFalse(internal.isKeyEntry(CORDA_ROOT_CA)) } } } \ No newline at end of file