Skip to content

Commit

Permalink
Updates for smarter key import
Browse files Browse the repository at this point in the history
  • Loading branch information
mikera committed Jan 25, 2024
1 parent 6b1e9c1 commit 0589cc9
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 11 deletions.
4 changes: 4 additions & 0 deletions convex-cli/src/main/java/convex/cli/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -479,5 +479,9 @@ public String loadTextFile(String fname) {
return result;
}

public void printErr(String message) {
commandLine.getErr().println(message);
}


}
55 changes: 47 additions & 8 deletions convex-cli/src/main/java/convex/cli/key/KeyImport.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
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;
import org.slf4j.LoggerFactory;

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;


Expand Down Expand Up @@ -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() {
Expand All @@ -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:"));
Expand All @@ -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());
}
}
30 changes: 28 additions & 2 deletions convex-cli/src/test/java/convex/cli/key/KeyImportTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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()));
}
}
3 changes: 2 additions & 1 deletion convex-core/src/main/java/convex/core/data/Blobs.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit 0589cc9

Please sign in to comment.