Skip to content

Commit

Permalink
GUI updates for better key security and management
Browse files Browse the repository at this point in the history
  • Loading branch information
mikera committed Dec 4, 2024
1 parent cafb5aa commit ab25bc0
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 39 deletions.
14 changes: 12 additions & 2 deletions convex-gui/src/main/java/convex/gui/components/ConnectPanel.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import convex.core.init.Init;
import convex.gui.components.account.AddressCombo;
import convex.gui.keys.KeyRingPanel;
import convex.gui.keys.UnlockWalletDialog;
import convex.gui.utils.SymbolIcon;
import convex.gui.utils.Toolkit;
import convex.net.IPUtils;
Expand Down Expand Up @@ -79,8 +80,17 @@ public static Convex tryConnect(JComponent parent,String prompt) {
HostCombo.registerGoodConnection(target);

AWalletEntry we=KeyRingPanel.findWalletEntry(convex);
if ((we!=null)&&!we.isLocked()) {
convex.setKeyPair(we.getKeyPair());
if ((we!=null)) {
if (!we.isLocked()) {
convex.setKeyPair(we.getKeyPair());
} else {
boolean unlock=UnlockWalletDialog.offerUnlock(parent,we);
if (unlock) {
convex.setKeyPair(we.getKeyPair());
} else {
JOptionPane.showMessageDialog(parent, "Wallet not unlocked. In read-only mode.");
}
}
}
return convex;
} catch (ConnectException e) {
Expand Down
102 changes: 96 additions & 6 deletions convex-gui/src/main/java/convex/gui/keys/KeyRingPanel.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package convex.gui.keys;

import java.awt.Color;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.IOException;
import java.security.GeneralSecurityException;
Expand All @@ -12,8 +14,10 @@

import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.filechooser.*;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -111,21 +115,62 @@ public KeyRingPanel() {
btnImportSeed.setToolTipText("Import a key pair using an Ed25519 seed");
toolBar.add(btnImportSeed);

// Import seed button
JButton btnLoadKeys = new ActionButton("Load Keys....",0xe890,this::loadStore);
btnLoadKeys.setToolTipText("Load keys from a Keystore file");
toolBar.add(btnLoadKeys);



add(toolBar, "dock south");

}


private void loadStore(ActionEvent e) {
try {
File f=chooseKeyStore(this);
if (f==null) return;
if (f.exists()) {
KeyStore ks=PFXTools.loadStore(f, null);
int num=loadKeys(ks,"Loaded from "+f.getCanonicalPath());
Toolkit.showMessge(this,num+" new keys imported.");
}
} catch (IOException | GeneralSecurityException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}

/**
* Shows dialog to choose a key store
* @param parent
* @return
*/
private static File chooseKeyStore(Component parent) {
JFileChooser chooser = new JFileChooser();
FileNameExtensionFilter filter = new FileNameExtensionFilter("PKCS #12 Keystore", "p12", "pfx");
chooser.setFileFilter(filter);
File defaultDir=FileUtils.getFile(Constants.DEFAULT_KEYSTORE_FILENAME);
if (defaultDir.isDirectory()) {
chooser.setCurrentDirectory(defaultDir);
}
int returnVal = chooser.showOpenDialog(parent);
if(returnVal != JFileChooser.APPROVE_OPTION) return null;
File f=chooser.getSelectedFile();
return f;
}

/**
* Load keys from a file. Returns number of keys loaded, or -1 if file could not be opened
* Load keys from a File. Returns number of keys loaded, or -1 if file could not be opened
* @param f
* @return
*/
private static int loadKeys(File f) {
if (!f.exists()) return -1;
try {
KeyStore ks=PFXTools.loadStore(f, null);
return loadKeys(ks, f.getCanonicalPath());
return loadKeys(ks, "Default KeyStore: "+f.getCanonicalPath());
} catch (IOException e) {
log.debug("Can't load key store: "+e.getMessage());
return -1;
Expand All @@ -136,15 +181,19 @@ private static int loadKeys(File f) {
}

private static int loadKeys(KeyStore keyStore, String source) throws KeyStoreException {
int n=0;
int numImports=0;
Enumeration<String> aliases = keyStore.aliases();
while (aliases.hasMoreElements()) {
String alias = aliases.nextElement();
AWalletEntry we=KeystoreWalletEntry.create(keyStore, alias, source);
listModel.addElement(we);
n++;
we.tryUnlock(null); // if empty password, unlock by default
AWalletEntry existing=getKeyRingEntry(we.getPublicKey());
if (existing==null) {
listModel.addElement(we);
numImports++;
}
}
return n;
return numImports;
}

public static DefaultListModel<AWalletEntry> getListModel() {
Expand Down Expand Up @@ -188,4 +237,45 @@ public static AWalletEntry getKeyRingEntry(AccountKey publicKey) {
}
return null;
}

/**
* Save a key to a store, prompting as necessary
* @param parent
* @param walletEntry Wallet entry to save
* @return True if saved successfully, false otherwise
*/
public static boolean saveKey(Component parent, AWalletEntry walletEntry) {
boolean locked=walletEntry.isLocked();
try {
File f=chooseKeyStore(parent);
if (f==null) return false;
KeyStore ks;
if (f.exists()) {
ks=PFXTools.loadStore(f, null);
} else {
ks=PFXTools.createStore(f, null);
}
String s=JOptionPane.showInputDialog(parent,"Enter key encryption password");
if (s==null) return false;

if (locked) {
boolean unlock=walletEntry.tryUnlock(s.toCharArray());
if (!unlock) {
Toolkit.showMessge(parent, "Could not unlock key with this password");
return false;
}
}
PFXTools.setKeyPair(ks, walletEntry.getKeyPair(), s.toCharArray());

// Lock wallet entry again

PFXTools.saveStore(ks, f, null);
return true;
} catch (Exception e) {
Toolkit.showErrorMessage(parent,"Failed to save to KeyStore",e);
return false;
} finally {
if (locked) walletEntry.lock();
}
}
}
79 changes: 48 additions & 31 deletions convex-gui/src/main/java/convex/gui/keys/WalletComponent.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package convex.gui.keys;

import java.awt.Color;
import java.awt.event.ActionEvent;

import javax.swing.Icon;
import javax.swing.JButton;
Expand Down Expand Up @@ -92,38 +93,34 @@ public WalletComponent(AWalletEntry initialWalletEntry) {
});

// Menu Button
JPopupMenu menu=new JPopupMenu();
//JMenuItem m1=new JMenuItem("Edit...");
//menu.add(m1);
JMenuItem m2=new JMenuItem("Show seed...");
m2.addActionListener(e-> {
AKeyPair kp=walletEntry.getKeyPair();
if (kp!=null) {
JPanel panel=new JPanel();
panel.setLayout(new MigLayout("wrap 1","[200]"));
panel.add(new Identicon(kp.getAccountKey(),Toolkit.IDENTICON_SIZE_LARGE),"align center");

panel.add(Toolkit.withTitledBorder("Ed25519 Private Seed",new CodeLabel(kp.getSeed().toString())));
panel.add(Toolkit.makeNote("WARNING: keep this private, it can be used to control your account(s)"),"grow");
panel.setBorder(Toolkit.createDialogBorder());
JOptionPane.showMessageDialog(WalletComponent.this, panel,"Ed25519 Private Seed",JOptionPane.INFORMATION_MESSAGE);
} else {
JOptionPane.showMessageDialog(WalletComponent.this, "Keypair is locked, cannot access seed","Warning",JOptionPane.WARNING_MESSAGE);
}
});
menu.add(m2);
JMenuItem m3=new JMenuItem("Remove...");
m3.addActionListener(e-> {
int confirm =JOptionPane.showConfirmDialog(WalletComponent.this, "Are you sure you want to delete this keypair from your keyring?","Confirm Delete",JOptionPane.WARNING_MESSAGE);
if (confirm==JOptionPane.OK_OPTION) {
KeyRingPanel.getListModel().removeElement(walletEntry);
}
});
menu.add(m3);
{
JPopupMenu menu=new JPopupMenu();
//JMenuItem m1=new JMenuItem("Edit...");
//menu.add(m1);
JMenuItem m2=new JMenuItem("Show seed...");
m2.addActionListener(this::showSeed);
menu.add(m2);

JMenuItem m3=new JMenuItem("Remove...");
m3.addActionListener(e-> {
int confirm =JOptionPane.showConfirmDialog(WalletComponent.this, "Are you sure you want to delete this keypair from your keyring?","Confirm Delete",JOptionPane.WARNING_MESSAGE);
if (confirm==JOptionPane.OK_OPTION) {
KeyRingPanel.getListModel().removeElement(walletEntry);
}
});
menu.add(m3);

JMenuItem m4=new JMenuItem("Save to KeyStore...");
m4.addActionListener(e-> {
KeyRingPanel.saveKey(this,walletEntry);
});
menu.add(m4);

DropdownMenu menuButton=new DropdownMenu(menu);
menuButton.setToolTipText("Settings and special actions for this key");
buttons.add(menuButton);

DropdownMenu menuButton=new DropdownMenu(menu);
menuButton.setToolTipText("Settings and special actions for this key");
buttons.add(menuButton);
}

// panel of buttons on right
add(buttons,"dock east"); // add to MigLayout
Expand All @@ -132,6 +129,26 @@ public WalletComponent(AWalletEntry initialWalletEntry) {
}


private void showSeed(ActionEvent e) {
if (walletEntry.isLocked()) {
if (!UnlockWalletDialog.offerUnlock(this,walletEntry)) return;;
}

AKeyPair kp=walletEntry.getKeyPair();
if (kp!=null) {
JPanel panel=new JPanel();
panel.setLayout(new MigLayout("wrap 1","[200]"));
panel.add(new Identicon(kp.getAccountKey(),Toolkit.IDENTICON_SIZE_LARGE),"align center");

panel.add(Toolkit.withTitledBorder("Ed25519 Private Seed",new CodeLabel(kp.getSeed().toString())));
panel.add(Toolkit.makeNote("WARNING: keep this private, it can be used to control your account(s)"),"grow");
panel.setBorder(Toolkit.createDialogBorder());
} else {
JOptionPane.showMessageDialog(WalletComponent.this, "Keypair is locked, cannot access seed","Warning",JOptionPane.WARNING_MESSAGE);
}
}


private void doUpdate() {
// TODO Auto-generated method stub
resetTooltipText(lockButton);
Expand Down
9 changes: 9 additions & 0 deletions convex-gui/src/main/java/convex/gui/utils/Toolkit.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
Expand Down Expand Up @@ -385,4 +386,12 @@ public static <E> void scrollToBottom(JScrollPane scrollPane) {
JScrollBar bar = scrollPane.getVerticalScrollBar();
bar.setValue(bar.getMaximum());
}

public static void showMessge(Component parent, Object message) {
JOptionPane.showMessageDialog(parent, message);
}

public static void showErrorMessage(Component parent, String attemptFailure,Exception e) {
JOptionPane.showMessageDialog(parent, attemptFailure+ "\n"+e.getMessage());
}
}
6 changes: 6 additions & 0 deletions convex-gui/src/main/java/convex/gui/wallet/WalletApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import convex.gui.components.Toast;
import convex.gui.dlfs.DLFSPanel;
import convex.gui.keys.KeyRingPanel;
import convex.gui.keys.UnlockWalletDialog;
import convex.gui.panels.HomePanel;
import convex.gui.peer.stake.PeerStakePanel;
import convex.gui.utils.SymbolIcon;
Expand Down Expand Up @@ -68,7 +69,12 @@ public void afterRun() {
if (convex.getKeyPair()==null) {
AWalletEntry we=KeyRingPanel.findWalletEntry(convex);
if (we!=null) {
if (we.isLocked()) {
UnlockWalletDialog.offerUnlock(homePanel, we);
}
convex.setKeyPair(we.getKeyPair());
} else {
Toolkit.showMessge(this, "The key for this account is not in your key ring.\n\nWallet opened in read-only mode: transactions will fail.");
}
}
}
Expand Down

0 comments on commit ab25bc0

Please sign in to comment.