From 7e58b7397da60e924a343db1d3dd27b6f4f04b14 Mon Sep 17 00:00:00 2001 From: James Brown <33660060+jamesbr3@users.noreply.github.com> Date: Thu, 11 Apr 2019 16:45:04 +0100 Subject: [PATCH] ENT-3142 NetworkParameters signing tool (#4996) --- experimental/netparams/build.gradle | 32 +++ .../kotlin/net.corda.netparams/NetParams.kt | 226 ++++++++++++++++++ settings.gradle | 1 + 3 files changed, 259 insertions(+) create mode 100644 experimental/netparams/build.gradle create mode 100644 experimental/netparams/src/main/kotlin/net.corda.netparams/NetParams.kt diff --git a/experimental/netparams/build.gradle b/experimental/netparams/build.gradle new file mode 100644 index 0000000000..1664444b50 --- /dev/null +++ b/experimental/netparams/build.gradle @@ -0,0 +1,32 @@ +apply plugin: 'java' +apply plugin: 'kotlin' + +description 'NetworkParameters signing tool' + +dependencies { + compile project(':tools:cliutils') + compile "org.slf4j:jul-to-slf4j:$slf4j_version" + compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version" + compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version" + compile project(':core') + compile project(':node-api') +} + +jar { + from(configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }) { + exclude "META-INF/*.SF" + exclude "META-INF/*.DSA" + exclude "META-INF/*.RSA" + } + baseName = "netparams" + manifest { + attributes( + 'Main-Class': 'net.corda.netparams.NetParamsKt' + ) + } +} + +processResources { + from file("$rootDir/config/dev/log4j2.xml") + from file("$rootDir/node-api/src/main/resources/certificates/cordadevcakeys.jks") +} diff --git a/experimental/netparams/src/main/kotlin/net.corda.netparams/NetParams.kt b/experimental/netparams/src/main/kotlin/net.corda.netparams/NetParams.kt new file mode 100644 index 0000000000..d90bb527a0 --- /dev/null +++ b/experimental/netparams/src/main/kotlin/net.corda.netparams/NetParams.kt @@ -0,0 +1,226 @@ +package net.corda.netparams + +import com.typesafe.config.Config +import com.typesafe.config.ConfigFactory +import com.typesafe.config.ConfigParseOptions +import net.corda.cliutils.CordaCliWrapper +import net.corda.cliutils.start +import net.corda.core.identity.Party +import net.corda.core.internal.* +import net.corda.core.node.NetworkParameters +import net.corda.core.node.NotaryInfo +import net.corda.core.serialization.SerializationContext +import net.corda.core.serialization.SerializedBytes +import net.corda.core.serialization.deserialize +import net.corda.core.serialization.internal.SerializationEnvironment +import net.corda.core.serialization.internal.nodeSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.nodeapi.internal.SignedNodeInfo +import net.corda.nodeapi.internal.createDevNetworkMapCa +import net.corda.nodeapi.internal.crypto.X509KeyStore +import net.corda.serialization.internal.* +import net.corda.serialization.internal.amqp.* +import picocli.CommandLine.* +import java.io.File +import java.nio.file.Path +import java.nio.file.StandardCopyOption +import java.time.Instant + +/** + * NetworkParameters signing tool for Corda + * + * This utility can be used to create and sign NetworkParameters files. This is useful for manual bootstrapping + * of a corda network, or by distribution via the NetworkMap + * + * By default the NetworkParameters will be signed with the development NetworkMap key, which is what happens + * currently with the Corda network bootstrapper. Arbitrary signing keys can be specified using + * the --keystore and --alias parameters, in which case the specified Java keystore (and key) will be used instead. + * + * Values for the NetworkParameters are specified in a configuration file (in HOCON format), + * using the --config option. An example content is shown below: + * + * notaries : [] + * minimumPlatformVersion = 1 + * maxMessageSize = 10485760 + * maxTransactionSize = 10485760 + * whitelistContracts = {} + * eventHorizonDays = 30 + * epoch = 1 + * + * Notary nodes for the network can be specified in the notaries[] section (as a string of filenames), as below: + * + * notaries: ["/path/to/nodeinfo"] + * + * or via the commandline with the --notary-info option. Notaries are identified either by their respective nodeInfo files, + * or by a path to the identity certificate (JKS file containing the identity cert) + * + * Example usage: + * + * # Generate NetworkParameters with one notary, identified by it's NodeInfo file (and signed with default development NetworkMap key) + * java -jar netparams.jar --config netparams.conf --notary-info /path/to/nodeinfo --output /path/to/network-parameters + * + * # Generate using the notary's identity certificate (instead of it's nodeinfo) + * java -jar netparams.jar --config netparams.conf --notary-keystore /path/to/node/certificates/nodekeystore.jks + * + * # Generate using an arbitrary 'netparams' signing key + * java -jar netparams.jar --config netparams.conf --notary-info /path/to/nodeinfo --keystore /my/keystore.jks --keyalias netparams + * + */ +fun main(args: Array) { + NetParamsSigner().start(args) +} + +class NetParamsSigner : CordaCliWrapper("netparams-signer", "Sign network parameters") { + + @Option(names = ["--config"], paramLabel = "file", description = ["Network Parameters config."]) + private var configFile: Path? = null + + @Option(names = ["--output"], paramLabel = "file", description = ["Network Parameters "]) + private var outputFile: Path? = null + + @Option(names = ["--notary-info"], paramLabel = "nodeInfo", description = ["Path to notary NodeInfo"]) + private var notaryInfos: MutableList = mutableListOf() + + @Option(names = ["--notary-keystore"], paramLabel = "keyStore", description = ["Path to node keystore"]) + private var notaryKeyStores: MutableList = mutableListOf() + + @Option(names = ["--notary-keypass"], paramLabel = "password", description = ["Password to node keystore"]) + private var notaryKeyPasswords: MutableList = mutableListOf() + + @Option(names = ["--keystore"], description = ["Keystore containing NetworkParameters signing key"]) + private var keyStorePath: Path? = null + + @Option(names = ["--keystore-pass"], description = ["Keystore password"]) + private var keyStorePass: String? = null + + @Option(names = ["--keyalias"], description = ["Alias of signing key"]) + private var keyAlias: String? = null + + @Option(names = ["--keypass"], description = ["Password of signing key"]) + private var keyPass: String? = null + + private fun getInput(prompt: String): String { + print(prompt) + System.out.flush() + val console = System.console() + if(console != null) + return console.readPassword().toString() + else + return readLine()!! + } + + private object AMQPInspectorSerializationScheme : AbstractAMQPSerializationScheme(emptyList()) { + override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean { + return magic == amqpMagic + } + + override fun rpcClientSerializerFactory(context: SerializationContext) = throw UnsupportedOperationException() + override fun rpcServerSerializerFactory(context: SerializationContext) = throw UnsupportedOperationException() + } + + private fun initialiseSerialization() { + nodeSerializationEnv = SerializationEnvironment.with( + SerializationFactoryImpl().apply { + registerScheme(AMQPInspectorSerializationScheme) + }, + AMQP_P2P_CONTEXT) + } + + override fun runProgram(): Int { + require(configFile != null ) { "The --config parameter must be specified" } + + initialiseSerialization() + + // notary specified by nodeInfo paths + val n1: List = notaryInfos.map { + val party = identityFromNodeInfoPath(it.toFile()) + NotaryInfo(party, false) + } + + // notary specified by keystore path (+password) + require(notaryKeyStores.size == notaryKeyPasswords.size) + val n2: List = notaryKeyStores.zip(notaryKeyPasswords) { path, password -> + val party = identityFromKeyStore(path.toFile(), password) + NotaryInfo(party, false) + } + + val networkParameters = parametersFromConfig(configFile!!, n1 + n2) + print(networkParameters.toString()) + + val signingkey = if(keyStorePath != null) { + + require(keyAlias != null) { "The --keyAlias parameters must be specified" } + + if(keyStorePass == null) + keyStorePass = getInput("Store password (${keyStorePath?.fileName}): ") + + if(keyPass == null) + keyPass = getInput("Key password (${keyAlias}): ") + + val keyStore = X509KeyStore.fromFile(keyStorePath!!, keyStorePass!!) + keyStore.getCertificateAndKeyPair(keyAlias!!, keyPass!!) + } + else { + // issue from the development root + createDevNetworkMapCa() + } + + // sign & serialise + val serializedSignedNetParams = signingkey.sign(networkParameters).serialize() + + if(outputFile != null) { + print("\nWriting: " + outputFile) + serializedSignedNetParams.open().copyTo(outputFile!!, StandardCopyOption.REPLACE_EXISTING) + } + else { + print("\nUse --output to write results") + } + + return 0 + } + + fun identityFromKeyStore(keyStorePath: File, keyStorePass: String, alias: String = "identity-private-key") : Party { + + val keyStore = X509KeyStore.fromFile(keyStorePath.toPath(), keyStorePass) + return Party(keyStore.getCertificate(alias)) + } + + fun identityFromNodeInfoPath(nodeInfoPath: File) : Party{ + + val serializedNodeInfo = SerializedBytes(nodeInfoPath.toPath().readAll()) + val signedNodeInfo = serializedNodeInfo.deserialize() + val nodeInfo = signedNodeInfo.verified() + + return nodeInfo.legalIdentities.last() + } + + fun parseConfig(config: Config, optionalNotaryList: List) : NetworkParameters { + + // convert the notary list (of nodeinfo paths) to NotaryInfos + val notaryList: List = config.getConfigList("notaries").map { + val path = it.getString("notaryNodeInfoFile") + val party = identityFromNodeInfoPath(File(path)) + + NotaryInfo(party, it.getBoolean("validating")) + } + + return NetworkParameters( + minimumPlatformVersion = config.getInt("minimumPlatformVersion"), + maxMessageSize = config.getInt("maxMessageSize"), + maxTransactionSize = config.getInt("maxTransactionSize"), + epoch = config.getInt("epoch"), + modifiedTime = Instant.now(), + whitelistedContractImplementations = emptyMap(), // TODO: not supported + notaries = notaryList + optionalNotaryList + ) + } + + fun parametersFromConfig(file: Path, notaryList: List) : NetworkParameters { + + val parseOptions = ConfigParseOptions.defaults().setAllowMissing(true) + val config = ConfigFactory.parseFile(file.toFile(), parseOptions).resolve() + + return parseConfig(config, notaryList) + } +} + diff --git a/settings.gradle b/settings.gradle index 96a3a3f7af..586a8b2ae5 100644 --- a/settings.gradle +++ b/settings.gradle @@ -23,6 +23,7 @@ include 'experimental:behave' include 'experimental:quasar-hook' include 'experimental:corda-utils' include 'experimental:nodeinfo' +include 'experimental:netparams' include 'jdk8u-deterministic' include 'test-common' include 'test-cli'