Skip to content

Commit

Permalink
Add CLI key delete command
Browse files Browse the repository at this point in the history
  • Loading branch information
mikera committed Aug 6, 2024
1 parent 247dcf9 commit 1db61cb
Show file tree
Hide file tree
Showing 14 changed files with 145 additions and 22 deletions.
11 changes: 10 additions & 1 deletion convex-cli/src/main/java/convex/cli/ACommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ public abstract class ACommand implements Runnable {
public abstract Main cli();

public void showUsage() {
CommandLine.usage(this, System.out);
CommandLine cl=new CommandLine(this);
cl.setUsageHelpAutoWidth(true);
cl.setUsageHelpWidth(100);
cl.setUsageHelpLongOptionsMaxWidth(40);
cl.usage(System.out);
}

public CommandLine commandLine() {
Expand Down Expand Up @@ -156,5 +160,10 @@ public void inform(String message) {
if (isColoured()) message=Coloured.yellow(message);
inform(1,message);
}

public void informWarning(String message) {
if (isColoured()) message=Coloured.orange(message);
inform(1,message);
}

}
5 changes: 1 addition & 4 deletions convex-cli/src/main/java/convex/cli/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,6 @@ public static void main(String[] args) {
*/
public int mainExecute(String[] args) {
try {
// commandLine
// .setUsageHelpLongOptionsMaxWidth(80)
// .setUsageHelpWidth(40 * 4);

// do a pre-parse to get the config filename. We need to load
// in the defaults before running the full execute
Expand All @@ -119,7 +116,7 @@ public int mainExecute(String[] args) {
}

if (commandLine.isUsageHelpRequested()) {
commandLine.usage(commandLine.getOut());
showUsage();
return ExitCodes.SUCCESS;
} else if (commandLine.isVersionHelpRequested()) {
commandLine.printVersionHelp(commandLine.getOut());
Expand Down
4 changes: 3 additions & 1 deletion convex-cli/src/main/java/convex/cli/client/Query.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
description="Execute user queries. ")
public class Query extends AClientCommand {

@Parameters(paramLabel="queryCommand", description="Query command(s). Multiple commands will be executed in sequence unless one fails")
@Parameters(
paramLabel="queryCommand",
description="Query command(s). Multiple commands will be executed in sequence unless one fails")
private String[] commands;

@Override
Expand Down
1 change: 1 addition & 0 deletions convex-cli/src/main/java/convex/cli/key/Key.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
KeyGenerate.class,
KeyList.class,
KeyExport.class,
KeyDelete.class,
CommandLine.HelpCommand.class
},
mixinStandardHelpOptions=false,
Expand Down
100 changes: 100 additions & 0 deletions convex-cli/src/main/java/convex/cli/key/KeyDelete.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package convex.cli.key;

import java.security.KeyStore;
import java.security.KeyStoreException;
import java.util.Enumeration;
import java.util.HashSet;

import convex.cli.CLIError;
import convex.cli.ExitCodes;
import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;

@Command(name="delete",
mixinStandardHelpOptions=false,
description="Delete key(s) from the keystore. Use with caution")
public class KeyDelete extends AKeyCommand {

static final String WILD="+";

@Parameters(
paramLabel="keys",
description="Key(s) to delete. Should be a hex prefix of a specific key, or trailing '"+WILD+"' as a wildcard")
private String[] keys;

protected void deleteEntry(String alias) throws KeyStoreException {
storeMixin.getKeystore().deleteEntry(alias);
inform(2,"Deleting Key: "+alias);
}

@Override
public void run() {

if ((keys==null)||(keys.length==0)) {
showUsage();
return;
}

KeyStore keyStore = storeMixin.loadKeyStore();
if (keyStore==null) throw new CLIError("Keystore does not exist. Specify a valid --keystore or use `convex key gen` to create one.");

HashSet<String> toDelete=new HashSet<>();

for (String argKey : keys) {
String key=argKey.trim();
if (key.startsWith("0x")) key=key.substring(2);
if (key.length()==0) throw new CLIError(ExitCodes.DATAERR,"Empty key specified?");

// If a trailing wildcard was used, strip it and set wildcard flag
boolean wild=key.endsWith(WILD);
if (wild) {
key=key.substring(0,key.length()-WILD.length());
}

inform(3,"Looking for keys to delete like: "+key);

String found=null;
Enumeration<String> aliases;
try {
aliases = keyStore.aliases();
while (aliases.hasMoreElements()) {
String alias = aliases.nextElement();
if (alias.startsWith(key)) {
if (wild) {
toDelete.add(alias);
} else {
if (found!=null) {
throw new CLIError("Duplicate keys found for prefix: "+argKey);
}
found=alias;
}
}
}
} catch (KeyStoreException e) {
throw new CLIError("Unexpected error reading keystore",e);
}

// check if we can remove specific key
if (found!=null) {
toDelete.add(found);
}
}

if (toDelete.isEmpty()) {
informWarning("No matching keys found");
} else {
for (String s: toDelete) {
try {
deleteEntry(s);
} catch (KeyStoreException e) {
throw new CLIError("Unable to remove key: "+s,e);
}
}
}

storeMixin.saveKeyStore();

}


}
2 changes: 1 addition & 1 deletion convex-cli/src/main/java/convex/cli/key/KeyExport.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
*/
@Command(name="export",
mixinStandardHelpOptions=false,
description="Export a private key from the keystore. Uee with caution")
description="Export a private key from the keystore. Use with caution.")
public class KeyExport extends AKeyCommand {

private static final Logger log = LoggerFactory.getLogger(KeyExport.class);
Expand Down
4 changes: 2 additions & 2 deletions convex-cli/src/main/java/convex/cli/local/Local.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@
CommandLine.HelpCommand.class
},
mixinStandardHelpOptions=true,
description="Operate a local Convex network and related utilities.")
description="Operate a local Convex network and related utilities. Primarily useful for development / testing.")
public class Local extends ATopCommand {

@Override
public void run() {

showUsage();
}
}
4 changes: 2 additions & 2 deletions convex-cli/src/main/java/convex/cli/mixins/KeyMixin.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ public class KeyMixin extends AMixin {
@Option(names = { "-k","--key" },
defaultValue = "${env:CONVEX_KEY}",
scope = ScopeType.INHERIT,
description = "Key pair to use. Specifiy with a hex prefix of a public key / alias. Can specify with CONVEX_KEY environment variable.")
description = "Key pair to use from keystore. Supports a hex prefix. Can specify with CONVEX_KEY.")
protected String publicKey;

@Option(names = { "-p","--keypass" },
defaultValue = "${env:CONVEX_KEY_PASSWORD}",
scope = ScopeType.INHERIT,
description = "Key pair password in key store. Can also specify with CONVEX_KEY_PASSWORD environment variable.")
description = "Key pair password in keystore. Can specify with CONVEX_KEY_PASSWORD.")
protected String keyPassword;

public String getPublicKey() {
Expand Down
6 changes: 3 additions & 3 deletions convex-cli/src/main/java/convex/cli/mixins/PeerKeyMixin.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ public class PeerKeyMixin extends AMixin {
@Option(names = { "--peer-key" },
defaultValue = "${env:CONVEX_PEER_KEY}",
scope = ScopeType.INHERIT,
description = "Peer Key pair. Specifiy with a hex prefix of a public key. "+
"Can ALSO specify with CONVEX_PEER_KEY environment variable.")
description = "Peer key. Allows a hex prefix of a public key in keystore. "+
"Can also specify with CONVEX_PEER_KEY.")
protected String publicKey;

@Option(names = { "--peer-keypass" },
defaultValue = "${env:CONVEX_PEER_KEY_PASSWORD}",
scope = ScopeType.INHERIT,
description = "Peer Key pair password in key store. Can also specify with CONVEX_PEER_KEY_PASSWORD environment variable.")
description = "Peer key password in keystore. Can also specify with CONVEX_PEER_KEY_PASSWORD.")
protected String keyPassword;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@
public class RemotePeerMixin extends AMixin {

@Option(names={"--port"},
defaultValue="${env:CONVEX_PORT:-"+Constants.DEFAULT_PEER_PORT+"",
description="Port number to connect to a peer.")
defaultValue="${env:CONVEX_PORT:-"+Constants.DEFAULT_PEER_PORT+"}",
description="Port number to connect to a peer. Defaulting to: ${DEFAULT-VALUE}")
private Integer port;

@Option(names={"--host"},
defaultValue=Constants.HOSTNAME_PEER,
description="Hostname for remote peer connection. Default: ${DEFAULT-VALUE}")
defaultValue="${env:CONVEX_HOST:-"+Constants.HOSTNAME_PEER+"}",
description="Hostname for remote peer connection. Defaulting to: ${DEFAULT-VALUE}")
private String hostname;

/**
Expand Down
4 changes: 4 additions & 0 deletions convex-cli/src/main/java/convex/cli/output/Coloured.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,8 @@ public static String blue(String text) {
public static String yellow(String text) {
return Ansi.ON.string("@|fg(226) "+text+"|@");
}

public static String orange(String text) {
return Ansi.ON.string("@|fg(172) "+text+"|@");
}
}
4 changes: 2 additions & 2 deletions convex-cli/src/main/java/convex/cli/peer/APeerCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public EtchStore getEtchStore() {
}

/**
* Get the keypair for the peer
* Get the keypair for the peer. Always returns a valid key pair, may generate one.
*/
protected AKeyPair ensurePeerKey() {
String peerPublicKey=peerKeyMixin.getPublicKey();
Expand All @@ -52,7 +52,7 @@ protected AKeyPair ensurePeerKey() {
boolean shouldGenerate=question("No --peer-key specified. Generate one? (y/n)");
if (shouldGenerate) {
AKeyPair kp=AKeyPair.generate();
inform(2,"Generated peer key: "+kp.getAccountKey());
inform(2,"Generated peer key: "+kp.getAccountKey().toChecksumHex());
char[] keyPass=peerKeyMixin.getKeyPassword();
storeMixin.addKeyPairToStore(kp, keyPass);
storeMixin.saveKeyStore();
Expand Down
3 changes: 1 addition & 2 deletions convex-cli/src/main/java/convex/cli/peer/Peer.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@ public class Peer extends ATopCommand {

@Override
public void run() {
// sub command run with no command provided
CommandLine.usage(new Peer(), System.out);
showUsage();
}


Expand Down
11 changes: 11 additions & 0 deletions convex-cli/src/test/java/convex/cli/key/KeyTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.io.IOException;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -52,10 +53,20 @@ public void testKeyGenerateAndUse() throws IOException {
// command key.list
tester = CLTester.run("key", "list", "--storepass", KEYSTORE_PASSWORD, "--keystore", fileName);
tester.assertExitCode(ExitCodes.SUCCESS);
assertTrue(tester.getOutput().contains(key));

// command key.list with non-existant keystore
tester = CLTester.run("key", "list", "--storepass", KEYSTORE_PASSWORD, "--keystore","bad-keystore.pfx");
assertNotEquals(ExitCodes.SUCCESS,tester.getResult());

// command key.list
tester = CLTester.run("key", "delete", "--storepass", KEYSTORE_PASSWORD, "--keystore", fileName, "+");
tester.assertExitCode(ExitCodes.SUCCESS);

// command key.list
tester = CLTester.run("key", "list", "-v0", "--storepass", KEYSTORE_PASSWORD, "--keystore", fileName);
tester.assertExitCode(ExitCodes.SUCCESS);
assertFalse(tester.getOutput().contains(key));

}
}

0 comments on commit 1db61cb

Please sign in to comment.