mirror of
https://github.com/corda/corda.git
synced 2025-01-27 22:59:54 +00:00
Key store passwords not required in the command line for the private key copying tool. (#577)
If not provided they will be asked for via the command line.
This commit is contained in:
parent
a26c5c1483
commit
24c0588a92
@ -55,28 +55,10 @@ trustStorePassword = "password"
|
||||
|
||||
# Private key copy tool
|
||||
|
||||
The key copy tool copies the private key from the source keystore to the destination keystore, it's similar to the ``importkeystore`` option in Java keytool with extra support for Corda's key algorithms.
|
||||
The key copy tool copies the private key from the source keystore to the destination keystore, it's similar to the
|
||||
``importkeystore`` option in Java keytool with extra support for Corda's key algorithms.
|
||||
**This is useful for provisioning keystores for distributed notaries.**
|
||||
|
||||
### Command line option
|
||||
|
||||
```
|
||||
Argument Description
|
||||
--------- -----------
|
||||
srckeystore Path to the source keystore containing the private key.
|
||||
|
||||
destkeystore Path to the destination keystore which the private key should copy to.
|
||||
|
||||
srcstorepass Source keystore password.
|
||||
|
||||
deststorepass Destination keystore password.
|
||||
|
||||
srcalias The alias of the private key the tool is copying.
|
||||
|
||||
destalias Optional: The private key will be stored using this alias if provided, otherwise [srcalias] will be used.
|
||||
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
``java -jar registration-tool-<<version>>.jar --importkeystore [options]``
|
||||
@ -94,3 +76,5 @@ java -jar registration-tool-<<version>>.jar \
|
||||
--deststorepass nodepassword \
|
||||
--srcalias distributed-notary-private-key
|
||||
```
|
||||
|
||||
``--help`` prints a list of all the available options.
|
||||
|
@ -4,8 +4,8 @@ import com.r3.corda.networkmanage.registration.ToolOption.KeyCopierOption
|
||||
import net.corda.nodeapi.internal.crypto.X509KeyStore
|
||||
|
||||
/**
|
||||
* This utility will copy private key with [KeyCopierOption.srcAlias] from provided source keystore and copy it to target
|
||||
* keystore with the same alias, or [KeyCopierOption.destAlias] if provided.
|
||||
* This utility will copy private key with [KeyCopierOption.sourceAlias] from provided source keystore and copy it to target
|
||||
* keystore with the same alias, or [KeyCopierOption.destinationAlias] if provided.
|
||||
*
|
||||
* This tool uses Corda's security provider which support EdDSA keys.
|
||||
*/
|
||||
@ -20,13 +20,13 @@ fun KeyCopierOption.copyKeystore() {
|
||||
println()
|
||||
|
||||
// Read private key and certificates from notary identity keystore.
|
||||
val srcKeystore = X509KeyStore.fromFile(srcPath, srcPass)
|
||||
val srcPrivateKey = srcKeystore.getPrivateKey(srcAlias)
|
||||
val srcCertChain = srcKeystore.getCertificateChain(srcAlias)
|
||||
val srcKeystore = X509KeyStore.fromFile(sourceFile, sourcePassword ?: readPassword("Source key store password:"))
|
||||
val srcPrivateKey = srcKeystore.getPrivateKey(sourceAlias)
|
||||
val srcCertChain = srcKeystore.getCertificateChain(sourceAlias)
|
||||
|
||||
X509KeyStore.fromFile(destPath, destPass).update {
|
||||
val keyAlias = destAlias ?: srcAlias
|
||||
X509KeyStore.fromFile(desinationFile, destinationPassword ?: readPassword("Destination key store password:")).update {
|
||||
val keyAlias = destinationAlias ?: sourceAlias
|
||||
setPrivateKey(keyAlias, srcPrivateKey, srcCertChain)
|
||||
println("Added '$keyAlias' to keystore : $destPath, the tool will now terminate.")
|
||||
println("Added '$keyAlias' to keystore : $desinationFile")
|
||||
}
|
||||
}
|
||||
|
@ -2,90 +2,101 @@ package com.r3.corda.networkmanage.registration
|
||||
|
||||
import com.r3.corda.networkmanage.registration.ToolOption.KeyCopierOption
|
||||
import com.r3.corda.networkmanage.registration.ToolOption.RegistrationOption
|
||||
import joptsimple.ArgumentAcceptingOptionSpec
|
||||
import joptsimple.OptionParser
|
||||
import joptsimple.OptionSet
|
||||
import joptsimple.OptionSpecBuilder
|
||||
import joptsimple.util.PathConverter
|
||||
import joptsimple.util.PathProperties
|
||||
import java.nio.file.Path
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val options = parseOptions(*args)
|
||||
val options = try {
|
||||
parseOptions(*args)
|
||||
} catch (e: ShowHelpException) {
|
||||
e.errorMessage?.let(::println)
|
||||
e.parser.printHelpOn(System.out)
|
||||
exitProcess(0)
|
||||
}
|
||||
when (options) {
|
||||
is RegistrationOption -> options.runRegistration()
|
||||
is KeyCopierOption -> options.copyKeystore()
|
||||
}
|
||||
}
|
||||
|
||||
private const val importKeyFlag = "importkeystore"
|
||||
|
||||
internal fun parseOptions(vararg args: String): ToolOption {
|
||||
fun parseOptions(vararg args: String): ToolOption {
|
||||
val optionParser = OptionParser()
|
||||
val isCopyKeyArg = optionParser.accepts(importKeyFlag)
|
||||
val helpOption = optionParser.acceptsAll(listOf("h", "help"), "show help").forHelp()
|
||||
|
||||
val importKeyStoreArg = optionParser.accepts("importkeystore")
|
||||
|
||||
val configFileArg = optionParser
|
||||
.accepts("config-file", "The path to the registration config file")
|
||||
.availableUnless(importKeyFlag)
|
||||
.accepts("config-file", "Path to the registration config file")
|
||||
.availableUnless(importKeyStoreArg)
|
||||
.requiredUnless(importKeyStoreArg)
|
||||
.withRequiredArg()
|
||||
.withValuesConvertedBy(PathConverter(PathProperties.FILE_EXISTING))
|
||||
|
||||
// key copy tool args
|
||||
val destKeystorePathArg = optionParser.accepts("destkeystore")
|
||||
.requireOnlyIf(importKeyFlag)
|
||||
.withRequiredArg()
|
||||
.withValuesConvertedBy(PathConverter(PathProperties.FILE_EXISTING))
|
||||
val srcKeystorePathArg = optionParser.accepts("srckeystore")
|
||||
.requireOnlyIf(importKeyFlag)
|
||||
val destKeystorePathArg = optionParser.accepts("destkeystore", "Path to the destination keystore which the private key should be copied to")
|
||||
.requireOnlyIf(importKeyStoreArg)
|
||||
.withRequiredArg()
|
||||
.withValuesConvertedBy(PathConverter(PathProperties.FILE_EXISTING))
|
||||
|
||||
val destPasswordArg = optionParser.accepts("deststorepass")
|
||||
.requireOnlyIf(importKeyFlag)
|
||||
val srcKeystorePathArg = optionParser.accepts("srckeystore", "Path to the source keystore containing the private key")
|
||||
.requireOnlyIf(importKeyStoreArg)
|
||||
.withRequiredArg()
|
||||
val srcPasswordArg = optionParser.accepts("srcstorepass")
|
||||
.requireOnlyIf(importKeyFlag)
|
||||
.withValuesConvertedBy(PathConverter(PathProperties.FILE_EXISTING))
|
||||
|
||||
val destPasswordArg = optionParser.accepts("deststorepass", "Source keystore password. Read in from the console if not specified.")
|
||||
.availableIf(importKeyStoreArg)
|
||||
.withRequiredArg()
|
||||
|
||||
val destAliasArg = optionParser.accepts("destalias")
|
||||
.availableIf(importKeyFlag)
|
||||
val srcPasswordArg = optionParser.accepts("srcstorepass", "Destination keystore password. Read in from the console if not specified.")
|
||||
.availableIf(importKeyStoreArg)
|
||||
.withRequiredArg()
|
||||
val srcAliasArg = optionParser.accepts("srcalias")
|
||||
.requireOnlyIf(importKeyFlag)
|
||||
|
||||
val destAliasArg = optionParser.accepts("destalias", "The alias under which the private key will be stored in the destination key store. If not provided then [srcalias] is used.")
|
||||
.availableIf(importKeyStoreArg)
|
||||
.withRequiredArg()
|
||||
|
||||
val srcAliasArg = optionParser.accepts("srcalias", "The alias under which the private key resides in the source key store")
|
||||
.requireOnlyIf(importKeyStoreArg)
|
||||
.withRequiredArg()
|
||||
|
||||
val optionSet = optionParser.parse(*args)
|
||||
val isCopyKey = optionSet.has(isCopyKeyArg)
|
||||
if (optionSet.has(helpOption)) {
|
||||
throw ShowHelpException(optionParser)
|
||||
}
|
||||
|
||||
val isCopyKey = optionSet.has(importKeyStoreArg)
|
||||
return if (isCopyKey) {
|
||||
val targetKeystorePath = optionSet.valueOf(destKeystorePathArg)
|
||||
val srcKeystorePath = optionSet.valueOf(srcKeystorePathArg)
|
||||
val destPassword = optionSet.valueOf(destPasswordArg)
|
||||
val targetKeystorePath = optionSet.valueOf(destKeystorePathArg)
|
||||
val srcPassword = optionSet.valueOf(srcPasswordArg)
|
||||
val destAlias = optionSet.getOrNull(destAliasArg)
|
||||
val destPassword = optionSet.valueOf(destPasswordArg)
|
||||
val srcAlias = optionSet.valueOf(srcAliasArg)
|
||||
|
||||
val destAlias = optionSet.valueOf(destAliasArg)
|
||||
KeyCopierOption(srcKeystorePath, targetKeystorePath, srcPassword, destPassword, srcAlias, destAlias)
|
||||
} else {
|
||||
val configFilePath = optionSet.valueOf(configFileArg)
|
||||
RegistrationOption(configFilePath)
|
||||
val configFile = optionSet.valueOf(configFileArg)
|
||||
RegistrationOption(configFile)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <V : Any> OptionSet.getOrNull(opt: ArgumentAcceptingOptionSpec<V>): V? = if (has(opt)) valueOf(opt) else null
|
||||
|
||||
private fun OptionSpecBuilder.requireOnlyIf(optionName: String): OptionSpecBuilder = requiredIf(optionName).availableIf(optionName)
|
||||
private fun OptionSpecBuilder.requireOnlyIf(option: OptionSpecBuilder): OptionSpecBuilder = requiredIf(option).availableIf(option)
|
||||
|
||||
sealed class ToolOption {
|
||||
data class RegistrationOption(val configFilePath: Path) : ToolOption()
|
||||
data class KeyCopierOption(val srcPath: Path,
|
||||
val destPath: Path,
|
||||
val srcPass: String,
|
||||
val destPass: String,
|
||||
val srcAlias: String,
|
||||
val destAlias: String?) : ToolOption()
|
||||
data class RegistrationOption(val configFile: Path) : ToolOption()
|
||||
data class KeyCopierOption(val sourceFile: Path,
|
||||
val desinationFile: Path,
|
||||
val sourcePassword: String?,
|
||||
val destinationPassword: String?,
|
||||
val sourceAlias: String,
|
||||
val destinationAlias: String?) : ToolOption()
|
||||
}
|
||||
|
||||
class ShowHelpException(val parser: OptionParser, val errorMessage: String? = null) : Exception()
|
||||
|
||||
fun readPassword(fmt: String): String {
|
||||
return if (System.console() != null) {
|
||||
String(System.console().readPassword(fmt))
|
||||
|
@ -24,14 +24,14 @@ import java.net.URL
|
||||
import java.nio.file.Path
|
||||
|
||||
fun RegistrationOption.runRegistration() {
|
||||
val config = ConfigFactory.parseFile(configFilePath.toFile(), ConfigParseOptions.defaults().setAllowMissing(false))
|
||||
val config = ConfigFactory.parseFile(configFile.toFile(), ConfigParseOptions.defaults().setAllowMissing(false))
|
||||
.resolve()
|
||||
.parseAs<RegistrationConfig>()
|
||||
|
||||
val sslConfig = object : SSLConfiguration {
|
||||
override val keyStorePassword: String by lazy { config.keyStorePassword ?: readPassword("Node Keystore password:") }
|
||||
override val trustStorePassword: String by lazy { config.trustStorePassword ?: readPassword("Node TrustStore password:") }
|
||||
override val certificatesDirectory: Path = configFilePath.parent / "certificates"
|
||||
override val certificatesDirectory: Path = configFile.parent / "certificates"
|
||||
}
|
||||
|
||||
NetworkRegistrationHelper(sslConfig,
|
||||
|
@ -19,23 +19,29 @@ class KeyCopyToolTest {
|
||||
|
||||
@Test
|
||||
fun `key copy correctly`() {
|
||||
val keyCopyOption = ToolOption.KeyCopierOption(tempDir / "srcKeystore.jks", tempDir / "destKeystore.jks", "srctestpass", "desttestpass", "TestKeyAlias", null)
|
||||
val keyCopyOption = ToolOption.KeyCopierOption(
|
||||
sourceFile = tempDir / "srcKeystore.jks",
|
||||
desinationFile = tempDir / "destKeystore.jks",
|
||||
sourcePassword = "srctestpass",
|
||||
destinationPassword = "desttestpass",
|
||||
sourceAlias = "TestKeyAlias",
|
||||
destinationAlias = null)
|
||||
|
||||
// Prepare source and destination keystores
|
||||
val keyPair = Crypto.generateKeyPair()
|
||||
val cert = X509Utilities.createSelfSignedCACertificate(X500Principal("O=Test"), keyPair)
|
||||
|
||||
X509KeyStore.fromFile(keyCopyOption.srcPath, keyCopyOption.srcPass, createNew = true).update {
|
||||
setPrivateKey(keyCopyOption.srcAlias, keyPair.private, listOf(cert))
|
||||
X509KeyStore.fromFile(keyCopyOption.sourceFile, keyCopyOption.sourcePassword!!, createNew = true).update {
|
||||
setPrivateKey(keyCopyOption.sourceAlias, keyPair.private, listOf(cert))
|
||||
}
|
||||
X509KeyStore.fromFile(keyCopyOption.destPath, keyCopyOption.destPass, createNew = true)
|
||||
X509KeyStore.fromFile(keyCopyOption.desinationFile, keyCopyOption.destinationPassword!!, createNew = true)
|
||||
|
||||
// Copy private key from src keystore to dest keystore using the tool
|
||||
keyCopyOption.copyKeystore()
|
||||
|
||||
// Verify key copied correctly
|
||||
val destKeystore = X509KeyStore.fromFile(keyCopyOption.destPath, keyCopyOption.destPass)
|
||||
assertEquals(keyPair.private, destKeystore.getPrivateKey(keyCopyOption.srcAlias, keyCopyOption.destPass))
|
||||
assertEquals(cert, destKeystore.getCertificate(keyCopyOption.srcAlias))
|
||||
val destKeystore = X509KeyStore.fromFile(keyCopyOption.desinationFile, keyCopyOption.destinationPassword!!)
|
||||
assertEquals(keyPair.private, destKeystore.getPrivateKey(keyCopyOption.sourceAlias, keyCopyOption.destinationPassword!!))
|
||||
assertEquals(cert, destKeystore.getCertificate(keyCopyOption.sourceAlias))
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
package com.r3.corda.networkmanage.registration
|
||||
|
||||
import joptsimple.OptionException
|
||||
import junit.framework.Assert.assertEquals
|
||||
import net.corda.core.internal.div
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
@ -27,17 +27,21 @@ class OptionParserTest {
|
||||
|
||||
@Test
|
||||
fun `parse registration args correctly`() {
|
||||
requireNotNull(parseOptions("--config-file", "${tempDir / "test.file"}") as? ToolOption.RegistrationOption).apply {
|
||||
assertEquals(tempDir / "test.file", configFilePath)
|
||||
}
|
||||
val options = parseOptions("--config-file", "${tempDir / "test.file"}") as ToolOption.RegistrationOption
|
||||
assertThat(options.configFile).isEqualTo(tempDir / "test.file")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `registration args should be unavailable in key copy mode`() {
|
||||
assertThatThrownBy {
|
||||
val keyCopyArgs = arrayOf("--importkeystore", "--srckeystore", "${tempDir / "source.jks"}", "--srcstorepass", "password1", "--destkeystore", "${tempDir / "target.jks"}", "--deststorepass", "password2", "-srcalias", "testalias")
|
||||
parseOptions(*keyCopyArgs, "--config-file", "test.file")
|
||||
}.isInstanceOf(OptionException::class.java)
|
||||
val keyCopyArgs = arrayOf(
|
||||
"--importkeystore",
|
||||
"--srckeystore", "${tempDir / "source.jks"}",
|
||||
"--srcstorepass", "password1",
|
||||
"--destkeystore", "${tempDir / "target.jks"}",
|
||||
"--deststorepass", "password2",
|
||||
"--srcalias", "testalias")
|
||||
assertThatThrownBy { parseOptions(*keyCopyArgs, "--config-file", "test.file") }
|
||||
.isInstanceOf(OptionException::class.java)
|
||||
.hasMessageContaining("Option(s) [config-file] are unavailable given other options on the command line")
|
||||
}
|
||||
|
||||
@ -50,14 +54,39 @@ class OptionParserTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parse key copy option correctly`() {
|
||||
val keyCopyArgs = arrayOf("--importkeystore", "--srckeystore", "${tempDir / "source.jks"}", "--srcstorepass", "password1", "--destkeystore", "${tempDir / "target.jks"}", "--deststorepass", "password2", "-srcalias", "testalias")
|
||||
requireNotNull(parseOptions(*keyCopyArgs) as? ToolOption.KeyCopierOption).apply {
|
||||
assertEquals(tempDir / "source.jks", srcPath)
|
||||
assertEquals(tempDir / "target.jks", destPath)
|
||||
assertEquals("password1", srcPass)
|
||||
assertEquals("password2", destPass)
|
||||
assertEquals("testalias", srcAlias)
|
||||
}
|
||||
fun `all import keystore options`() {
|
||||
val keyCopyArgs = arrayOf(
|
||||
"--importkeystore",
|
||||
"--srckeystore", "${tempDir / "source.jks"}",
|
||||
"--srcstorepass", "password1",
|
||||
"--destkeystore", "${tempDir / "target.jks"}",
|
||||
"--deststorepass", "password2",
|
||||
"--srcalias", "testalias",
|
||||
"--destalias", "testalias2")
|
||||
assertThat(parseOptions(*keyCopyArgs)).isEqualTo(ToolOption.KeyCopierOption(
|
||||
sourceFile = tempDir / "source.jks",
|
||||
desinationFile = tempDir / "target.jks",
|
||||
sourcePassword = "password1",
|
||||
destinationPassword = "password2",
|
||||
sourceAlias = "testalias",
|
||||
destinationAlias = "testalias2"
|
||||
))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `minimum import keystore options`() {
|
||||
val keyCopyArgs = arrayOf(
|
||||
"--importkeystore",
|
||||
"--srckeystore", "${tempDir / "source.jks"}",
|
||||
"--destkeystore", "${tempDir / "target.jks"}",
|
||||
"--srcalias", "testalias")
|
||||
assertThat(parseOptions(*keyCopyArgs)).isEqualTo(ToolOption.KeyCopierOption(
|
||||
sourceFile = tempDir / "source.jks",
|
||||
desinationFile = tempDir / "target.jks",
|
||||
sourcePassword = null,
|
||||
destinationPassword = null,
|
||||
sourceAlias = "testalias",
|
||||
destinationAlias = null
|
||||
))
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user