diff --git a/CHANGELOG.md b/CHANGELOG.md index 10aeaef9f92..42cff87dd9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog ## 22.1.0 +- Add `--ec-curve` parameter to export/export-address public-key subcommands [#3333](https://github.com/hyperledger/besu/pull/3333) ### 22.1.0 Breaking Changes diff --git a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/PublicKeySubCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/PublicKeySubCommand.java index ae54d8d6b44..33f78a6eecb 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/PublicKeySubCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/PublicKeySubCommand.java @@ -24,6 +24,8 @@ import org.hyperledger.besu.cli.subcommands.PublicKeySubCommand.AddressSubCommand; import org.hyperledger.besu.cli.subcommands.PublicKeySubCommand.ExportSubCommand; import org.hyperledger.besu.crypto.KeyPair; +import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; +import org.hyperledger.besu.crypto.SignatureAlgorithmType; import org.hyperledger.besu.ethereum.core.Util; import java.io.BufferedWriter; @@ -97,6 +99,7 @@ static class ExportSubCommand extends KeyPairSubcommand implements Runnable { @Override public void run() { + configureEcCurve(ecCurve, parentCommand.spec.commandLine()); run(publicKeyExportFile, keyPair -> keyPair.getPublicKey().toString()); } } @@ -126,6 +129,7 @@ static class AddressSubCommand extends KeyPairSubcommand implements Runnable { @Override public void run() { + configureEcCurve(ecCurve, parentCommand.spec.commandLine()); run(addressExportFile, keyPair -> Util.publicKeyToAddress(keyPair.getPublicKey()).toString()); } } @@ -134,10 +138,20 @@ private static class KeyPairSubcommand { @SuppressWarnings("unused") @ParentCommand - private PublicKeySubCommand parentCommand; // Picocli injects reference to parent command + protected PublicKeySubCommand parentCommand; // Picocli injects reference to parent command @Mixin private final NodePrivateKeyFileOption nodePrivateKeyFileOption = null; + @Option( + names = "--ec-curve", + paramLabel = "", + description = + "Elliptic curve to use when creating a new key (default: " + + SignatureAlgorithmType.DEFAULT_EC_CURVE_NAME + + ")", + arity = "0..1") + protected String ecCurve = null; + @Spec private final CommandSpec spec = null; protected final void run( @@ -172,5 +186,15 @@ protected final void run( parentCommand.out.println(output); } } + + protected static void configureEcCurve(final String ecCurve, final CommandLine commandLine) { + if (ecCurve != null) { + try { + SignatureAlgorithmFactory.setInstance(SignatureAlgorithmType.create(ecCurve)); + } catch (IllegalArgumentException e) { + throw new CommandLine.ParameterException(commandLine, e.getMessage(), e); + } + } + } } } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/PublicKeySubCommandTest.java b/besu/src/test/java/org/hyperledger/besu/cli/PublicKeySubCommandTest.java index 971ce1cf9b9..ea1332ecac0 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/PublicKeySubCommandTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/PublicKeySubCommandTest.java @@ -22,6 +22,7 @@ import org.hyperledger.besu.crypto.NodeKey; import org.hyperledger.besu.crypto.SECPPrivateKey; import org.hyperledger.besu.crypto.SignatureAlgorithm; +import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; import org.hyperledger.besu.ethereum.core.Util; import java.io.File; @@ -33,6 +34,7 @@ import org.bouncycastle.asn1.sec.SECNamedCurves; import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.crypto.params.ECDomainParameters; +import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; @@ -63,27 +65,35 @@ public class PublicKeySubCommandTest extends CommandTestAbstract { + System.lineSeparator(); private static final String EXPECTED_PUBLIC_KEY_EXPORT_USAGE = - "Usage: besu public-key export [-hV] [--node-private-key-file=]" + "Usage: besu public-key export [-hV] [--ec-curve[=]]" + System.lineSeparator() - + " [--to=]" + + " [--node-private-key-file=] [--to=]" + System.lineSeparator() + "This command outputs the node public key. Default output is standard output." + System.lineSeparator() - + " -h, --help Show this help message and exit." + + " --ec-curve[=] Elliptic curve to use when creating a new key" + + System.lineSeparator() + + " (default: secp256k1)" + + System.lineSeparator() + + " -h, --help Show this help message and exit." + System.lineSeparator() + " --node-private-key-file=" + System.lineSeparator() - + " The node's private key file (default: a file named \"key\" in" + + " The node's private key file (default: a file named" + System.lineSeparator() - + " the Besu data directory)" + + " \"key\" in the Besu data directory)" + System.lineSeparator() - + " --to= File to write public key to instead of standard output" + + " --to= File to write public key to instead of standard" + System.lineSeparator() - + " -V, --version Print version information and exit." + + " output" + + System.lineSeparator() + + " -V, --version Print version information and exit." + System.lineSeparator(); private static final String EXPECTED_PUBLIC_KEY_EXPORT_ADDRESS_USAGE = - "Usage: besu public-key export-address [-hV] [--node-private-key-file=]" + "Usage: besu public-key export-address [-hV] [--ec-curve[=]]" + + System.lineSeparator() + + " [--node-private-key-file=]" + System.lineSeparator() + " [--to=]" + System.lineSeparator() @@ -91,17 +101,21 @@ public class PublicKeySubCommandTest extends CommandTestAbstract { + System.lineSeparator() + "output." + System.lineSeparator() - + " -h, --help Show this help message and exit." + + " --ec-curve[=] Elliptic curve to use when creating a new key" + + System.lineSeparator() + + " (default: secp256k1)" + + System.lineSeparator() + + " -h, --help Show this help message and exit." + System.lineSeparator() + " --node-private-key-file=" + System.lineSeparator() - + " The node's private key file (default: a file named \"key\" in" + + " The node's private key file (default: a file named" + System.lineSeparator() - + " the Besu data directory)" + + " \"key\" in the Besu data directory)" + System.lineSeparator() - + " --to= File to write address to instead of standard output" + + " --to= File to write address to instead of standard output" + System.lineSeparator() - + " -V, --version Print version information and exit." + + " -V, --version Print version information and exit." + System.lineSeparator(); private static final String PUBLIC_KEY_SUBCOMMAND_NAME = "public-key"; @@ -117,6 +131,11 @@ public static void setUp() { curve = new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH()); } + @Before + public void before() { + SignatureAlgorithmFactory.resetInstance(); + } + // public-key sub-command @Test public void publicKeySubCommandExistsAndHasSubCommands() { @@ -323,4 +342,129 @@ public void callingPublicKeyExportAddressSubCommandWithInvalidFileMustDisplayErr assertThat(commandErrorOutput.toString(UTF_8)) .startsWith("Private key cannot be loaded from file"); } + + @Test + public void + callingPublicKeyExportSubCommandWithEcCurveNameCorrectlyConfiguresSignatureAlgorithmFactory() + throws Exception { + assertThat(SignatureAlgorithmFactory.isInstanceSet()).isFalse(); + + final File file = File.createTempFile("public", "key"); + + parseCommand( + PUBLIC_KEY_SUBCOMMAND_NAME, + PUBLIC_KEY_EXPORT_SUBCOMMAND_NAME, + "--to", + file.getPath(), + "--ec-curve", + CURVE_NAME); + + assertThat(SignatureAlgorithmFactory.isInstanceSet()).isTrue(); + assertThat(SignatureAlgorithmFactory.getInstance().getCurveName()).isEqualTo(CURVE_NAME); + } + + @Test + public void + callingPublicKeyExportSubCommandWithoutEcCurveNameDoesNotConfiguresSignatureAlgorithmFactory() + throws Exception { + assertThat(SignatureAlgorithmFactory.isInstanceSet()).isFalse(); + + final File file = File.createTempFile("public", "key"); + + parseCommand( + PUBLIC_KEY_SUBCOMMAND_NAME, PUBLIC_KEY_EXPORT_SUBCOMMAND_NAME, "--to", file.getPath()); + + assertThat(SignatureAlgorithmFactory.isInstanceSet()).isFalse(); + } + + @Test + public void callingPublicKeyExportSubCommandWithInvalidEcCurveNameFails() throws Exception { + final File file = File.createTempFile("public", "key"); + + parseCommand( + PUBLIC_KEY_SUBCOMMAND_NAME, + PUBLIC_KEY_EXPORT_SUBCOMMAND_NAME, + "--to", + file.getPath(), + "--ec-curve", + "foo"); + + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("foo is not in the list of valid elliptic curves"); + } + + @Test + public void + callingPublicKeyExportAddressSubCommandWithEcCurveNameCorrectlyConfiguresSignatureAlgorithmFactory() + throws Exception { + assertThat(SignatureAlgorithmFactory.isInstanceSet()).isFalse(); + + final SECPPrivateKey privateKey = + SECPPrivateKey.create( + Bytes32.fromHexString( + "0x8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63"), + ALGORITHM); + + final Path privateKeyFile = Files.createTempFile("private", "address"); + Files.writeString(privateKeyFile, privateKey.toString()); + + parseCommand( + PUBLIC_KEY_SUBCOMMAND_NAME, + PUBLIC_KEY_EXPORT_ADDRESS_SUBCOMMAND_NAME, + "--node-private-key-file", + privateKeyFile.toString(), + "--ec-curve", + CURVE_NAME); + + assertThat(SignatureAlgorithmFactory.isInstanceSet()).isTrue(); + assertThat(SignatureAlgorithmFactory.getInstance().getCurveName()).isEqualTo(CURVE_NAME); + } + + @Test + public void + callingPublicKeyExportAddressSubCommandWithoutEcCurveNameDoesNotConfiguresSignatureAlgorithmFactory() + throws Exception { + assertThat(SignatureAlgorithmFactory.isInstanceSet()).isFalse(); + + final SECPPrivateKey privateKey = + SECPPrivateKey.create( + Bytes32.fromHexString( + "0x8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63"), + ALGORITHM); + + final Path privateKeyFile = Files.createTempFile("private", "address"); + Files.writeString(privateKeyFile, privateKey.toString()); + + parseCommand( + PUBLIC_KEY_SUBCOMMAND_NAME, + PUBLIC_KEY_EXPORT_ADDRESS_SUBCOMMAND_NAME, + "--node-private-key-file", + privateKeyFile.toString()); + + assertThat(SignatureAlgorithmFactory.isInstanceSet()).isFalse(); + } + + @Test + public void callingPublicKeyExportAddressSubCommandWithInvalidEcCurveNameFails() + throws Exception { + final SECPPrivateKey privateKey = + SECPPrivateKey.create( + Bytes32.fromHexString( + "0x8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63"), + ALGORITHM); + + final Path privateKeyFile = Files.createTempFile("private", "address"); + Files.writeString(privateKeyFile, privateKey.toString()); + + parseCommand( + PUBLIC_KEY_SUBCOMMAND_NAME, + PUBLIC_KEY_EXPORT_ADDRESS_SUBCOMMAND_NAME, + "--node-private-key-file", + privateKeyFile.toString(), + "--ec-curve", + "foo"); + + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("foo is not in the list of valid elliptic curves"); + } } diff --git a/crypto/src/main/java/org/hyperledger/besu/crypto/KeyPairUtil.java b/crypto/src/main/java/org/hyperledger/besu/crypto/KeyPairUtil.java index 690b9a98bbc..b3a7d57f527 100644 --- a/crypto/src/main/java/org/hyperledger/besu/crypto/KeyPairUtil.java +++ b/crypto/src/main/java/org/hyperledger/besu/crypto/KeyPairUtil.java @@ -69,11 +69,13 @@ public static KeyPair loadKeyPair(final File keyFile) { LOG.info( "Loaded public key {} from {}", key.getPublicKey().toString(), keyFile.getAbsolutePath()); } else { - key = SIGNATURE_ALGORITHM.get().generateKeyPair(); + final SignatureAlgorithm signatureAlgorithm = SIGNATURE_ALGORITHM.get(); + key = signatureAlgorithm.generateKeyPair(); storeKeyFile(key, keyFile.getParentFile().toPath()); LOG.info( - "Generated new public key {} and stored it to {}", + "Generated new {} public key {} and stored it to {}", + signatureAlgorithm.getCurveName(), key.getPublicKey().toString(), keyFile.getAbsolutePath()); } diff --git a/crypto/src/main/java/org/hyperledger/besu/crypto/SignatureAlgorithmType.java b/crypto/src/main/java/org/hyperledger/besu/crypto/SignatureAlgorithmType.java index c5ed1a7753b..adcf80ca14f 100644 --- a/crypto/src/main/java/org/hyperledger/besu/crypto/SignatureAlgorithmType.java +++ b/crypto/src/main/java/org/hyperledger/besu/crypto/SignatureAlgorithmType.java @@ -22,7 +22,7 @@ public class SignatureAlgorithmType { - private static final String DEFAULT_EC_CURVE_NAME = "secp256k1"; + public static final String DEFAULT_EC_CURVE_NAME = "secp256k1"; private static final ImmutableMap> SUPPORTED_ALGORITHMS = ImmutableMap.of(DEFAULT_EC_CURVE_NAME, SECP256K1::new, "secp256r1", SECP256R1::new);