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:
Shams Asari 2018-03-19 15:06:23 +00:00 committed by GitHub
parent a26c5c1483
commit 24c0588a92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 124 additions and 94 deletions

View File

@ -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.

View File

@ -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")
}
}

View File

@ -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))

View File

@ -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,

View File

@ -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))
}
}

View File

@ -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
))
}
}