mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-25 05:06:45 +00:00
add functionality for tapsigner backup and pin change
This commit is contained in:
parent
6b59ff60ad
commit
3ddf4ed4b2
8 changed files with 283 additions and 21 deletions
|
@ -0,0 +1,93 @@
|
||||||
|
package com.sparrowwallet.sparrow.control;
|
||||||
|
|
||||||
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.binding.BooleanBinding;
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
import org.controlsfx.control.textfield.CustomPasswordField;
|
||||||
|
import org.controlsfx.glyphfont.FontAwesome;
|
||||||
|
import org.controlsfx.glyphfont.Glyph;
|
||||||
|
import org.controlsfx.validation.ValidationResult;
|
||||||
|
import org.controlsfx.validation.ValidationSupport;
|
||||||
|
import org.controlsfx.validation.decoration.StyleClassValidationDecoration;
|
||||||
|
import tornadofx.control.Field;
|
||||||
|
import tornadofx.control.Fieldset;
|
||||||
|
import tornadofx.control.Form;
|
||||||
|
|
||||||
|
public class CardPinDialog extends Dialog<CardPinDialog.CardPinChange> {
|
||||||
|
private final CustomPasswordField existingPin;
|
||||||
|
private final CustomPasswordField newPin;
|
||||||
|
private final CustomPasswordField newPinConfirm;
|
||||||
|
private final CheckBox backupFirst;
|
||||||
|
private final ButtonType okButtonType;
|
||||||
|
|
||||||
|
public CardPinDialog() {
|
||||||
|
this.existingPin = new ViewPasswordField();
|
||||||
|
this.newPin = new ViewPasswordField();
|
||||||
|
this.newPinConfirm = new ViewPasswordField();
|
||||||
|
this.backupFirst = new CheckBox();
|
||||||
|
|
||||||
|
final DialogPane dialogPane = getDialogPane();
|
||||||
|
setTitle("Change Card PIN");
|
||||||
|
dialogPane.setHeaderText("Enter the current PIN, and then the new PIN twice. PIN must be between 6 and 32 digits.");
|
||||||
|
dialogPane.getStylesheets().add(AppServices.class.getResource("general.css").toExternalForm());
|
||||||
|
AppServices.setStageIcon(dialogPane.getScene().getWindow());
|
||||||
|
dialogPane.getButtonTypes().addAll(ButtonType.CANCEL);
|
||||||
|
dialogPane.setPrefWidth(380);
|
||||||
|
dialogPane.setPrefHeight(260);
|
||||||
|
AppServices.moveToActiveWindowScreen(this);
|
||||||
|
|
||||||
|
Glyph lock = new Glyph("FontAwesome", FontAwesome.Glyph.LOCK);
|
||||||
|
lock.setFontSize(50);
|
||||||
|
dialogPane.setGraphic(lock);
|
||||||
|
|
||||||
|
Form form = new Form();
|
||||||
|
Fieldset fieldset = new Fieldset();
|
||||||
|
fieldset.setText("");
|
||||||
|
fieldset.setSpacing(10);
|
||||||
|
|
||||||
|
Field currentField = new Field();
|
||||||
|
currentField.setText("Current PIN:");
|
||||||
|
currentField.getInputs().add(existingPin);
|
||||||
|
|
||||||
|
Field newField = new Field();
|
||||||
|
newField.setText("New PIN:");
|
||||||
|
newField.getInputs().add(newPin);
|
||||||
|
|
||||||
|
Field confirmField = new Field();
|
||||||
|
confirmField.setText("Confirm new PIN:");
|
||||||
|
confirmField.getInputs().add(newPinConfirm);
|
||||||
|
|
||||||
|
Field backupField = new Field();
|
||||||
|
backupField.setText("Backup First:");
|
||||||
|
backupField.getInputs().add(backupFirst);
|
||||||
|
|
||||||
|
fieldset.getChildren().addAll(currentField, newField, confirmField, backupField);
|
||||||
|
form.getChildren().add(fieldset);
|
||||||
|
dialogPane.setContent(form);
|
||||||
|
|
||||||
|
ValidationSupport validationSupport = new ValidationSupport();
|
||||||
|
Platform.runLater( () -> {
|
||||||
|
validationSupport.setValidationDecorator(new StyleClassValidationDecoration());
|
||||||
|
validationSupport.registerValidator(existingPin, (Control c, String newValue) -> ValidationResult.fromErrorIf(c, "Incorrect PIN length", existingPin.getText().length() < 6 || existingPin.getText().length() > 32));
|
||||||
|
validationSupport.registerValidator(newPin, (Control c, String newValue) -> ValidationResult.fromErrorIf(c, "Incorrect PIN length", newPin.getText().length() < 6 || newPin.getText().length() > 32));
|
||||||
|
validationSupport.registerValidator(newPinConfirm, (Control c, String newValue) -> ValidationResult.fromErrorIf(c, "PIN confirmation does not match", !newPinConfirm.getText().equals(newPin.getText())));
|
||||||
|
});
|
||||||
|
|
||||||
|
okButtonType = new javafx.scene.control.ButtonType("Change", ButtonBar.ButtonData.OK_DONE);
|
||||||
|
dialogPane.getButtonTypes().addAll(okButtonType);
|
||||||
|
Button okButton = (Button) dialogPane.lookupButton(okButtonType);
|
||||||
|
okButton.setPrefWidth(130);
|
||||||
|
BooleanBinding isInvalid = Bindings.createBooleanBinding(() -> existingPin.getText().length() < 6 || existingPin.getText().length() > 32
|
||||||
|
|| newPin.getText().length() < 6 || newPin.getText().length() > 32
|
||||||
|
|| !newPin.getText().equals(newPinConfirm.getText()),
|
||||||
|
existingPin.textProperty(), newPin.textProperty(), newPinConfirm.textProperty());
|
||||||
|
okButton.disableProperty().bind(isInvalid);
|
||||||
|
|
||||||
|
Platform.runLater(existingPin::requestFocus);
|
||||||
|
setResultConverter(dialogButton -> dialogButton == okButtonType ? new CardPinChange(existingPin.getText(), newPin.getText(), backupFirst.isSelected()) : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public record CardPinChange(String currentPin, String newPin, boolean backupFirst) { }
|
||||||
|
}
|
|
@ -8,7 +8,12 @@ import com.sparrowwallet.drongo.protocol.Base58;
|
||||||
import com.sparrowwallet.drongo.wallet.Keystore;
|
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||||
import com.sparrowwallet.drongo.wallet.KeystoreSource;
|
import com.sparrowwallet.drongo.wallet.KeystoreSource;
|
||||||
import com.sparrowwallet.drongo.wallet.WalletModel;
|
import com.sparrowwallet.drongo.wallet.WalletModel;
|
||||||
|
import javafx.beans.property.IntegerProperty;
|
||||||
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.StringProperty;
|
||||||
|
import javafx.concurrent.Service;
|
||||||
|
import javafx.concurrent.Task;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -35,10 +40,11 @@ public class CardApi {
|
||||||
return cardProtocol.getStatus();
|
return cardProtocol.getStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
void checkWait(CardStatus cardStatus, StringProperty messageProperty) throws CardException {
|
void checkWait(CardStatus cardStatus, IntegerProperty delayProperty, StringProperty messageProperty) throws CardException {
|
||||||
if(cardStatus.auth_delay != null) {
|
if(cardStatus.auth_delay != null) {
|
||||||
int delay = cardStatus.auth_delay.intValue();
|
int delay = cardStatus.auth_delay.intValue();
|
||||||
while(delay > 0) {
|
while(delay > 0) {
|
||||||
|
delayProperty.set(delay);
|
||||||
messageProperty.set("Auth delay, waiting " + delay + "s...");
|
messageProperty.set("Auth delay, waiting " + delay + "s...");
|
||||||
CardWait cardWait = cardProtocol.authWait();
|
CardWait cardWait = cardProtocol.authWait();
|
||||||
if(cardWait.success) {
|
if(cardWait.success) {
|
||||||
|
@ -48,6 +54,38 @@ public class CardApi {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Service<Void> getAuthDelayService() throws CardException {
|
||||||
|
CardStatus cardStatus = getStatus();
|
||||||
|
if(cardStatus.auth_delay != null) {
|
||||||
|
return new AuthDelayService(cardStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean requiresBackup() throws CardException {
|
||||||
|
CardStatus cardStatus = getStatus();
|
||||||
|
return cardStatus.requiresBackup();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Service<String> getBackupService() {
|
||||||
|
return new BackupService();
|
||||||
|
}
|
||||||
|
|
||||||
|
String getBackup() throws CardException {
|
||||||
|
CardBackup cardBackup = cardProtocol.backup(cvc);
|
||||||
|
return Utils.bytesToHex(cardBackup.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean changePin(String newCvc) throws CardException {
|
||||||
|
CardChange cardChange = cardProtocol.change(cvc, newCvc);
|
||||||
|
if(cardChange.success) {
|
||||||
|
cvc = newCvc;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cardChange.success;
|
||||||
|
}
|
||||||
|
|
||||||
public void setDerivation(List<ChildNumber> derivation) throws CardException {
|
public void setDerivation(List<ChildNumber> derivation) throws CardException {
|
||||||
cardProtocol.derive(cvc, derivation);
|
cardProtocol.derive(cvc, derivation);
|
||||||
}
|
}
|
||||||
|
@ -81,4 +119,45 @@ public class CardApi {
|
||||||
log.warn("Error disconnecting from card reader", e);
|
log.warn("Error disconnecting from card reader", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isReaderAvailable() {
|
||||||
|
return CardTransport.isReaderAvailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AuthDelayService extends Service<Void> {
|
||||||
|
private final CardStatus cardStatus;
|
||||||
|
private final IntegerProperty delayProperty;
|
||||||
|
private final StringProperty messageProperty;
|
||||||
|
|
||||||
|
AuthDelayService(CardStatus cardStatus) {
|
||||||
|
this.cardStatus = cardStatus;
|
||||||
|
this.delayProperty = new SimpleIntegerProperty();
|
||||||
|
this.messageProperty = new SimpleStringProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Task<Void> createTask() {
|
||||||
|
return new Task<>() {
|
||||||
|
@Override
|
||||||
|
protected Void call() throws Exception {
|
||||||
|
delayProperty.addListener((observable, oldValue, newValue) -> updateProgress(cardStatus.auth_delay.intValue() - newValue.intValue(), cardStatus.auth_delay.intValue()));
|
||||||
|
messageProperty.addListener((observable, oldValue, newValue) -> updateMessage(newValue));
|
||||||
|
checkWait(cardStatus, delayProperty, messageProperty);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BackupService extends Service<String> {
|
||||||
|
@Override
|
||||||
|
protected Task<String> createTask() {
|
||||||
|
return new Task<>() {
|
||||||
|
@Override
|
||||||
|
protected String call() throws Exception {
|
||||||
|
return getBackup();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,10 @@ public class CardStatus extends CardResponse {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean requiresBackup() {
|
||||||
|
return num_backups == null || num_backups.intValue() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "CardStatus{" +
|
return "CardStatus{" +
|
||||||
|
|
|
@ -32,7 +32,7 @@ public class CardTransport {
|
||||||
|
|
||||||
private final Card connection;
|
private final Card connection;
|
||||||
|
|
||||||
public CardTransport() throws CardException {
|
CardTransport() throws CardException {
|
||||||
TerminalFactory tf = TerminalFactory.getDefault();
|
TerminalFactory tf = TerminalFactory.getDefault();
|
||||||
List<CardTerminal> terminals = tf.terminals().list();
|
List<CardTerminal> terminals = tf.terminals().list();
|
||||||
if(terminals.isEmpty()) {
|
if(terminals.isEmpty()) {
|
||||||
|
@ -49,7 +49,7 @@ public class CardTransport {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public JsonObject send(String cmd, Map<String, Object> args) throws CardException {
|
JsonObject send(String cmd, Map<String, Object> args) throws CardException {
|
||||||
Map<String, Object> sendMap = new LinkedHashMap<>();
|
Map<String, Object> sendMap = new LinkedHashMap<>();
|
||||||
sendMap.put("cmd", cmd);
|
sendMap.put("cmd", cmd);
|
||||||
sendMap.putAll(args);
|
sendMap.putAll(args);
|
||||||
|
@ -106,11 +106,10 @@ public class CardTransport {
|
||||||
if(result.get("error") != null) {
|
if(result.get("error") != null) {
|
||||||
String msg = result.get("error").getAsString();
|
String msg = result.get("error").getAsString();
|
||||||
int code = result.get("code") == null ? 500 : result.get("code").getAsInt();
|
int code = result.get("code") == null ? 500 : result.get("code").getAsInt();
|
||||||
String message = code + " on " + cmd + ": " + msg;
|
|
||||||
if(code == 205) {
|
if(code == 205) {
|
||||||
throw new CardUnluckyNumberException(message);
|
throw new CardUnluckyNumberException("Card chose unlucky number, please retry.");
|
||||||
} else if(code == 401) {
|
} else if(code == 401) {
|
||||||
throw new CardAuthorizationException(message);
|
throw new CardAuthorizationException("Incorrect PIN provided.");
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new CardException(code + " on " + cmd + ": " + msg);
|
throw new CardException(code + " on " + cmd + ": " + msg);
|
||||||
|
@ -146,11 +145,11 @@ public class CardTransport {
|
||||||
throw new IllegalArgumentException("Cannot convert dataItem of type " + dataItem.getClass() + "to JsonElement");
|
throw new IllegalArgumentException("Cannot convert dataItem of type " + dataItem.getClass() + "to JsonElement");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void disconnect() throws CardException {
|
void disconnect() throws CardException {
|
||||||
connection.disconnect(true);
|
connection.disconnect(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isReaderAvailable() {
|
static boolean isReaderAvailable() {
|
||||||
try {
|
try {
|
||||||
TerminalFactory tf = TerminalFactory.getDefault();
|
TerminalFactory tf = TerminalFactory.getDefault();
|
||||||
return !tf.terminals().list().isEmpty();
|
return !tf.terminals().list().isEmpty();
|
||||||
|
|
|
@ -5,6 +5,7 @@ import com.sparrowwallet.drongo.wallet.Keystore;
|
||||||
import com.sparrowwallet.drongo.wallet.WalletModel;
|
import com.sparrowwallet.drongo.wallet.WalletModel;
|
||||||
import com.sparrowwallet.sparrow.io.KeystoreCardImport;
|
import com.sparrowwallet.sparrow.io.KeystoreCardImport;
|
||||||
import com.sparrowwallet.sparrow.io.ImportException;
|
import com.sparrowwallet.sparrow.io.ImportException;
|
||||||
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.StringProperty;
|
||||||
|
|
||||||
|
@ -32,7 +33,7 @@ public class CkCard implements KeystoreCardImport {
|
||||||
cardApi.initialize();
|
cardApi.initialize();
|
||||||
cardStatus = cardApi.getStatus();
|
cardStatus = cardApi.getStatus();
|
||||||
}
|
}
|
||||||
cardApi.checkWait(cardStatus, messageProperty);
|
cardApi.checkWait(cardStatus, new SimpleIntegerProperty(), messageProperty);
|
||||||
|
|
||||||
if(!derivation.equals(cardStatus.getDerivation())) {
|
if(!derivation.equals(cardStatus.getDerivation())) {
|
||||||
cardApi.setDerivation(derivation);
|
cardApi.setDerivation(derivation);
|
||||||
|
|
|
@ -11,11 +11,12 @@ import javafx.scene.control.Accordion;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.smartcardio.TerminalFactory;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.sparrowwallet.sparrow.io.ckcard.CardApi.isReaderAvailable;
|
||||||
|
|
||||||
public class HwAirgappedController extends KeystoreImportDetailController {
|
public class HwAirgappedController extends KeystoreImportDetailController {
|
||||||
private static final Logger log = LoggerFactory.getLogger(HwAirgappedController.class);
|
private static final Logger log = LoggerFactory.getLogger(HwAirgappedController.class);
|
||||||
|
|
||||||
|
@ -54,15 +55,4 @@ public class HwAirgappedController extends KeystoreImportDetailController {
|
||||||
|
|
||||||
importAccordion.getPanes().sort(Comparator.comparing(o -> ((TitledDescriptionPane) o).getTitle()));
|
importAccordion.getPanes().sort(Comparator.comparing(o -> ((TitledDescriptionPane) o).getTitle()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isReaderAvailable() {
|
|
||||||
try {
|
|
||||||
TerminalFactory tf = TerminalFactory.getDefault();
|
|
||||||
return !tf.terminals().list().isEmpty();
|
|
||||||
} catch(Exception e) {
|
|
||||||
log.error("Error detecting card terminals", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,10 @@ import com.sparrowwallet.sparrow.event.*;
|
||||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5Brands;
|
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5Brands;
|
||||||
import com.sparrowwallet.sparrow.io.Storage;
|
import com.sparrowwallet.sparrow.io.Storage;
|
||||||
|
import com.sparrowwallet.sparrow.io.ckcard.CardApi;
|
||||||
import com.sparrowwallet.sparrow.keystoreimport.KeystoreImportDialog;
|
import com.sparrowwallet.sparrow.keystoreimport.KeystoreImportDialog;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
import javafx.concurrent.Service;
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.fxml.Initializable;
|
import javafx.fxml.Initializable;
|
||||||
|
@ -29,11 +31,14 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import tornadofx.control.Field;
|
import tornadofx.control.Field;
|
||||||
|
|
||||||
|
import javax.smartcardio.CardException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static com.sparrowwallet.sparrow.io.ckcard.CardApi.isReaderAvailable;
|
||||||
|
|
||||||
public class KeystoreController extends WalletFormController implements Initializable {
|
public class KeystoreController extends WalletFormController implements Initializable {
|
||||||
private static final Logger log = LoggerFactory.getLogger(KeystoreController.class);
|
private static final Logger log = LoggerFactory.getLogger(KeystoreController.class);
|
||||||
|
|
||||||
|
@ -56,6 +61,9 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||||
@FXML
|
@FXML
|
||||||
private Button viewKeyButton;
|
private Button viewKeyButton;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button changePinButton;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Button importButton;
|
private Button importButton;
|
||||||
|
|
||||||
|
@ -117,6 +125,7 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||||
|
|
||||||
viewSeedButton.managedProperty().bind(viewSeedButton.visibleProperty());
|
viewSeedButton.managedProperty().bind(viewSeedButton.visibleProperty());
|
||||||
viewKeyButton.managedProperty().bind(viewKeyButton.visibleProperty());
|
viewKeyButton.managedProperty().bind(viewKeyButton.visibleProperty());
|
||||||
|
changePinButton.managedProperty().bind(changePinButton.visibleProperty());
|
||||||
scanXpubQR.managedProperty().bind(scanXpubQR.visibleProperty());
|
scanXpubQR.managedProperty().bind(scanXpubQR.visibleProperty());
|
||||||
displayXpubQR.managedProperty().bind(displayXpubQR.visibleProperty());
|
displayXpubQR.managedProperty().bind(displayXpubQR.visibleProperty());
|
||||||
displayXpubQR.visibleProperty().bind(scanXpubQR.visibleProperty().not());
|
displayXpubQR.visibleProperty().bind(scanXpubQR.visibleProperty().not());
|
||||||
|
@ -273,6 +282,7 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||||
type.setGraphic(getTypeIcon(keystore));
|
type.setGraphic(getTypeIcon(keystore));
|
||||||
viewSeedButton.setVisible(keystore.getSource() == KeystoreSource.SW_SEED && keystore.hasSeed());
|
viewSeedButton.setVisible(keystore.getSource() == KeystoreSource.SW_SEED && keystore.hasSeed());
|
||||||
viewKeyButton.setVisible(keystore.getSource() == KeystoreSource.SW_SEED && keystore.hasMasterPrivateExtendedKey());
|
viewKeyButton.setVisible(keystore.getSource() == KeystoreSource.SW_SEED && keystore.hasMasterPrivateExtendedKey());
|
||||||
|
changePinButton.setVisible(keystore.getWalletModel() == WalletModel.TAPSIGNER);
|
||||||
|
|
||||||
importButton.setText(keystore.getSource() == KeystoreSource.SW_WATCH ? "Import..." : "Replace...");
|
importButton.setText(keystore.getSource() == KeystoreSource.SW_WATCH ? "Import..." : "Replace...");
|
||||||
importButton.setTooltip(new Tooltip(keystore.getSource() == KeystoreSource.SW_WATCH ? "Import a keystore from an external source" : "Replace this keystore with another source"));
|
importButton.setTooltip(new Tooltip(keystore.getSource() == KeystoreSource.SW_WATCH ? "Import a keystore from an external source" : "Replace this keystore with another source"));
|
||||||
|
@ -404,6 +414,84 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void changeCardPin(ActionEvent event) {
|
||||||
|
if(!isReaderAvailable()) {
|
||||||
|
AppServices.showErrorDialog("No card reader", "Connect a card reader to change the card PIN.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CardPinDialog cardPinDialog = new CardPinDialog();
|
||||||
|
Optional<CardPinDialog.CardPinChange> optPinChange = cardPinDialog.showAndWait();
|
||||||
|
if(optPinChange.isPresent()) {
|
||||||
|
String currentPin = optPinChange.get().currentPin();
|
||||||
|
String newPin = optPinChange.get().newPin();
|
||||||
|
boolean backupFirst = optPinChange.get().backupFirst();
|
||||||
|
try {
|
||||||
|
CardApi cardApi = new CardApi(currentPin);
|
||||||
|
Service<Void> authDelayService = cardApi.getAuthDelayService();
|
||||||
|
if(authDelayService != null) {
|
||||||
|
authDelayService.setOnSucceeded(event1 -> {
|
||||||
|
try {
|
||||||
|
changeCardPin(newPin, backupFirst, cardApi);
|
||||||
|
} catch(CardException e) {
|
||||||
|
log.error("Error communicating with card", e);
|
||||||
|
AppServices.showErrorDialog("Error communicating with card", e.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
authDelayService.setOnFailed(event1 -> {
|
||||||
|
Throwable e = event1.getSource().getException();
|
||||||
|
log.error("Error communicating with card", e);
|
||||||
|
AppServices.showErrorDialog("Error communicating with card", e.getMessage());
|
||||||
|
});
|
||||||
|
ServiceProgressDialog serviceProgressDialog = new ServiceProgressDialog("Authentication Delay", "Waiting for authenication delay to clear...", "/image/tapsigner.png", authDelayService);
|
||||||
|
AppServices.moveToActiveWindowScreen(serviceProgressDialog);
|
||||||
|
authDelayService.start();
|
||||||
|
} else {
|
||||||
|
changeCardPin(newPin, backupFirst, cardApi);
|
||||||
|
}
|
||||||
|
} catch(CardException e) {
|
||||||
|
log.error("Error communicating with card", e);
|
||||||
|
AppServices.showErrorDialog("Error communicating with card", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void changeCardPin(String newPin, boolean backupFirst, CardApi cardApi) throws CardException {
|
||||||
|
boolean requiresBackup = cardApi.requiresBackup();
|
||||||
|
if(backupFirst || requiresBackup) {
|
||||||
|
Service<String> backupService = cardApi.getBackupService();
|
||||||
|
backupService.setOnSucceeded(event -> {
|
||||||
|
String backup = backupService.getValue();
|
||||||
|
TextAreaDialog backupDialog = new TextAreaDialog(backup, false);
|
||||||
|
backupDialog.setTitle("Backup Private Key");
|
||||||
|
backupDialog.getDialogPane().setHeaderText((requiresBackup ? "Please backup first by saving" : "Save") + " the following text in a safe place. It contains an encrypted copy of the card's private key, and can be decrypted using the backup key written on the back of the card.");
|
||||||
|
backupDialog.showAndWait();
|
||||||
|
try {
|
||||||
|
changePin(newPin, cardApi);
|
||||||
|
} catch(Exception e) {
|
||||||
|
log.error("Error communicating with card", e);
|
||||||
|
AppServices.showErrorDialog("Error communicating with card", e.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
backupService.setOnFailed(event -> {
|
||||||
|
Throwable e = event.getSource().getException();
|
||||||
|
log.error("Error communicating with card", e);
|
||||||
|
AppServices.showErrorDialog("Error communicating with card", e.getMessage());
|
||||||
|
});
|
||||||
|
backupService.start();
|
||||||
|
} else {
|
||||||
|
changePin(newPin, cardApi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void changePin(String newPin, CardApi cardApi) throws CardException {
|
||||||
|
if(cardApi.changePin(newPin)) {
|
||||||
|
AppServices.showSuccessDialog("PIN changed", "The card's PIN has been changed.");
|
||||||
|
} else {
|
||||||
|
AppServices.showSuccessDialog("Could not change PIN", "The card's PIN was not changed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void scanXpubQR(ActionEvent event) {
|
public void scanXpubQR(ActionEvent event) {
|
||||||
QRScanDialog qrScanDialog = new QRScanDialog();
|
QRScanDialog qrScanDialog = new QRScanDialog();
|
||||||
Optional<QRScanDialog.Result> optionalResult = qrScanDialog.showAndWait();
|
Optional<QRScanDialog.Result> optionalResult = qrScanDialog.showAndWait();
|
||||||
|
|
|
@ -40,6 +40,14 @@
|
||||||
<Tooltip text="View master private key"/>
|
<Tooltip text="View master private key"/>
|
||||||
</tooltip>
|
</tooltip>
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button fx:id="changePinButton" text="Change Pin..." graphicTextGap="5" onAction="#changeCardPin">
|
||||||
|
<graphic>
|
||||||
|
<Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="12" icon="WIFI" />
|
||||||
|
</graphic>
|
||||||
|
<tooltip>
|
||||||
|
<Tooltip text="Change the PIN of current card"/>
|
||||||
|
</tooltip>
|
||||||
|
</Button>
|
||||||
<Pane HBox.hgrow="ALWAYS" />
|
<Pane HBox.hgrow="ALWAYS" />
|
||||||
<Button fx:id="importButton" text="Import..." graphicTextGap="5" onAction="#importKeystore">
|
<Button fx:id="importButton" text="Import..." graphicTextGap="5" onAction="#importKeystore">
|
||||||
<graphic>
|
<graphic>
|
||||||
|
|
Loading…
Reference in a new issue