From 0589cc96493146b944f8828b2e842e4c74657d67 Mon Sep 17 00:00:00 2001 From: mikera Date: Thu, 25 Jan 2024 12:58:12 +0000 Subject: [PATCH] Updates for smarter key import --- convex-cli/src/main/java/convex/cli/Main.java | 4 ++ .../main/java/convex/cli/key/KeyImport.java | 55 ++++++++++++++++--- .../java/convex/cli/key/KeyImportTest.java | 30 +++++++++- .../src/main/java/convex/core/data/Blobs.java | 3 +- 4 files changed, 81 insertions(+), 11 deletions(-) diff --git a/convex-cli/src/main/java/convex/cli/Main.java b/convex-cli/src/main/java/convex/cli/Main.java index f61cceec8..42647bdf7 100644 --- a/convex-cli/src/main/java/convex/cli/Main.java +++ b/convex-cli/src/main/java/convex/cli/Main.java @@ -479,5 +479,9 @@ public String loadTextFile(String fname) { return result; } + public void printErr(String message) { + commandLine.getErr().println(message); + } + } diff --git a/convex-cli/src/main/java/convex/cli/key/KeyImport.java b/convex-cli/src/main/java/convex/cli/key/KeyImport.java index 8a575c802..932368a40 100644 --- a/convex-cli/src/main/java/convex/cli/key/KeyImport.java +++ b/convex-cli/src/main/java/convex/cli/key/KeyImport.java @@ -1,7 +1,8 @@ package convex.cli.key; -import java.io.Console; +import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; +import java.security.spec.InvalidKeySpecException; import org.bouncycastle.util.Arrays; import org.slf4j.Logger; @@ -9,9 +10,13 @@ import convex.cli.CLIError; import convex.core.crypto.AKeyPair; +import convex.core.crypto.BIP39; import convex.core.crypto.PEMTools; +import convex.core.data.ABlob; +import convex.core.data.Blobs; import picocli.CommandLine.Command; import picocli.CommandLine.Option; +import picocli.CommandLine.Parameters; import picocli.CommandLine.ParentCommand; @@ -43,6 +48,9 @@ public class KeyImport extends AKeyCommand { @Option(names={"--import-password"}, description="Password for the imported key.") private String importPassword; + + @Parameters(index = "0", arity = "0..1", description = "Type of file imported. Supports: pem, seed, bip39") + private String type; @Override public void run() { @@ -54,7 +62,8 @@ public void run() { if (importText == null || importText.length() == 0) { throw new CLIError("You need to provide '--text' or import filename '--import-file' to import a private key"); } - + + // Get import password if (importPassword == null) { if (cli().isInteractive()) { importPassword=new String(System.console().readPassword("Enter import password:")); @@ -63,17 +72,47 @@ public void run() { } } + // Parse input as hex string, just in case + ABlob hex=Blobs.parse(importText.trim()); - - PrivateKey privateKey = PEMTools.decryptPrivateKeyFromPEM(importText, importPassword.toCharArray()); - AKeyPair keyPair = AKeyPair.create(privateKey); - + if (type==null) { + cli().printErr("No import file type specified, attempting to auto-detect"); + if (hex!=null) { + if (hex.count()==AKeyPair.SEED_LENGTH) { + type="seed"; + cli().printErr("Detected type 'seed'"); + } else if (hex.count()==BIP39.SEED_LENGTH) { + type="bip39"; + } + } + } + + AKeyPair keyPair=null; + if ("seed".equals(type)) { + if (hex==null) throw new CLIError("'seed' import type requires a hex private key seed"); + if (hex.count()!=AKeyPair.SEED_LENGTH) throw new CLIError("32 byte hex Ed25519 seed expected as input"); + keyPair=AKeyPair.create(hex.toFlatBlob()); + } else if ("bip39".equals(type)) { + if (hex==null) { + try { + hex=BIP39.getSeed(importText, importPassword); + } catch (Exception e) { + throw new CLIError("Error interpreting BIP39 seed",e); + } + } + keyPair=BIP39.seedToKeyPair(hex.toFlatBlob()); + } else if ("pem".equals(type)) { + PrivateKey privateKey = PEMTools.decryptPrivateKeyFromPEM(importText, importPassword.toCharArray()); + keyPair = AKeyPair.create(privateKey); + } + if (keyPair==null) throw new CLIError("Unable to import keypair"); + + // Finally write to store char[] storePassword=cli().getStorePassword(); char[] keyPassword=cli().getKeyPassword(); cli().addKeyPairToStore(keyPair,keyPassword); Arrays.fill(keyPassword, 'x'); - cli().saveKeyStore(storePassword); - + cli().saveKeyStore(storePassword); cli().println(keyPair.getAccountKey().toHexString()); } } diff --git a/convex-cli/src/test/java/convex/cli/key/KeyImportTest.java b/convex-cli/src/test/java/convex/cli/key/KeyImportTest.java index 41995ac23..b95ac9380 100644 --- a/convex-cli/src/test/java/convex/cli/key/KeyImportTest.java +++ b/convex-cli/src/test/java/convex/cli/key/KeyImportTest.java @@ -28,16 +28,16 @@ public class KeyImportTest { } @Test - public void testKeyImport() { + public void testKeyImportPEM() { AKeyPair keyPair = SodiumKeyPair.generate(); AccountKey accountKey=keyPair.getAccountKey(); String pemText = PEMTools.encryptPrivateKeyToPEM(keyPair.getPrivate(), IMPORT_PASSWORD); - // command key.list CLTester tester = CLTester.run( "key", "import", + "pem", "-n", "--keystore-password", new String(KEYSTORE_PASSWORD), "--keystore", KEYSTORE_FILENAME, @@ -55,4 +55,30 @@ public void testKeyImport() { assertEquals(ExitCodes.SUCCESS,t2.getResult()); assertTrue(t2.getOutput().contains(accountKey.toHexString())); } + + @Test + public void testKeyImportBIP39() { + + CLTester tester = CLTester.run( + "key", + "import", + "bip39", + "--keystore-password", new String(KEYSTORE_PASSWORD), + "--keystore", KEYSTORE_FILENAME, + "--text", "elder mail trick garage hour enjoy attack fringe problem motion poem security caught false penalty", + "--import-password", new String("") + ); + assertEquals(ExitCodes.SUCCESS,tester.getResult()); + + // Should give Ed25519 Seed: 616421a4ea27c65919faa5555e923f6005d76695c7d9ba0fe2a484b90e23de89 + + CLTester t2=CLTester.run( + "key" , + "list", + "--keystore-password", new String(KEYSTORE_PASSWORD), + "--keystore", KEYSTORE_FILENAME); + + assertEquals(ExitCodes.SUCCESS,t2.getResult()); + assertTrue(t2.getOutput().contains("B4CDCE685F63E7768717ACF256B1450878EE6ABC7B7EE877B5D69B2466D8FBBF".toLowerCase())); + } } diff --git a/convex-core/src/main/java/convex/core/data/Blobs.java b/convex-core/src/main/java/convex/core/data/Blobs.java index b6103948b..c36263dce 100644 --- a/convex-core/src/main/java/convex/core/data/Blobs.java +++ b/convex-core/src/main/java/convex/core/data/Blobs.java @@ -51,6 +51,7 @@ public static ABlob fromHex(String a) { long slength = a.length(); if ((slength & 1) != 0) return null; Blob fullBlob = Blob.fromHex(a); + if (fullBlob==null) return null; long length = slength / 2; if (length <= Blob.CHUNK_LENGTH) return fullBlob; @@ -86,7 +87,7 @@ public static ABlob parse(Object o) { /** * Best effort attempt to parse a Blob. Must parse as a Blob of correct length. * Leading "0x" optional. - * @param s String expected to contain a HasBlobh value + * @param s String expected to contain a single Blob value in hex * @return ABlob value, or null if not parseable */ public static ABlob parse(String s) {