mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-25 13:16:44 +00:00
implement card initialization functionality
This commit is contained in:
parent
3ddf4ed4b2
commit
6c13504644
9 changed files with 175 additions and 30 deletions
2
drongo
2
drongo
|
@ -1 +1 @@
|
||||||
Subproject commit 2168c56de924dd7bff5458b32f9a60a6c79066a1
|
Subproject commit a14b23f2fabc35c1c0b4b7b9f886dab10b4f7562
|
|
@ -3,8 +3,10 @@ package com.sparrowwallet.sparrow.control;
|
||||||
import com.google.common.base.Throwables;
|
import com.google.common.base.Throwables;
|
||||||
import com.sparrowwallet.drongo.KeyDerivation;
|
import com.sparrowwallet.drongo.KeyDerivation;
|
||||||
import com.sparrowwallet.drongo.crypto.ChildNumber;
|
import com.sparrowwallet.drongo.crypto.ChildNumber;
|
||||||
|
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
||||||
import com.sparrowwallet.drongo.wallet.Keystore;
|
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
import com.sparrowwallet.sparrow.EventManager;
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
import com.sparrowwallet.sparrow.event.KeystoreImportEvent;
|
import com.sparrowwallet.sparrow.event.KeystoreImportEvent;
|
||||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||||
|
@ -16,8 +18,7 @@ import javafx.concurrent.Task;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.control.Control;
|
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.Priority;
|
import javafx.scene.layout.Priority;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
|
@ -26,6 +27,8 @@ import org.controlsfx.glyphfont.Glyph;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.smartcardio.CardException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class CardImportPane extends TitledDescriptionPane {
|
public class CardImportPane extends TitledDescriptionPane {
|
||||||
|
@ -36,6 +39,7 @@ public class CardImportPane extends TitledDescriptionPane {
|
||||||
protected Button importButton;
|
protected Button importButton;
|
||||||
private final SimpleStringProperty pin = new SimpleStringProperty("");
|
private final SimpleStringProperty pin = new SimpleStringProperty("");
|
||||||
private final SimpleStringProperty errorText = new SimpleStringProperty("");
|
private final SimpleStringProperty errorText = new SimpleStringProperty("");
|
||||||
|
private boolean initialized;
|
||||||
|
|
||||||
public CardImportPane(Wallet wallet, KeystoreCardImport importer, KeyDerivation requiredDerivation) {
|
public CardImportPane(Wallet wallet, KeystoreCardImport importer, KeyDerivation requiredDerivation) {
|
||||||
super(importer.getName(), "Keystore import", importer.getKeystoreImportDescription(getAccount(wallet, requiredDerivation)), "image/" + importer.getWalletModel().getType() + ".png");
|
super(importer.getName(), "Keystore import", importer.getKeystoreImportDescription(getAccount(wallet, requiredDerivation)), "image/" + importer.getWalletModel().getType() + ".png");
|
||||||
|
@ -57,6 +61,22 @@ public class CardImportPane extends TitledDescriptionPane {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void importCard() {
|
private void importCard() {
|
||||||
|
errorText.set("");
|
||||||
|
|
||||||
|
try {
|
||||||
|
if(!importer.isInitialized()) {
|
||||||
|
setDescription("Card not initialized");
|
||||||
|
setContent(getInitializationPanel());
|
||||||
|
setExpanded(true);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
initialized = true;
|
||||||
|
}
|
||||||
|
} catch(CardException e) {
|
||||||
|
setError("Card Error", e.getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(pin.get().isEmpty()) {
|
if(pin.get().isEmpty()) {
|
||||||
setDescription("Enter PIN code");
|
setDescription("Enter PIN code");
|
||||||
setContent(getPinEntry());
|
setContent(getPinEntry());
|
||||||
|
@ -85,21 +105,71 @@ public class CardImportPane extends TitledDescriptionPane {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void setError(String title, String detail) {
|
protected void setError(String title, String detail) {
|
||||||
|
if(!initialized) {
|
||||||
|
super.setError(title, detail);
|
||||||
|
} else {
|
||||||
super.setError(title, null);
|
super.setError(title, null);
|
||||||
errorText.set(detail);
|
errorText.set(detail);
|
||||||
setContent(getPinEntry());
|
setContent(getPinEntry());
|
||||||
setExpanded(true);
|
setExpanded(true);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node getInitializationPanel() {
|
||||||
|
VBox initTypeBox = new VBox(5);
|
||||||
|
RadioButton automatic = new RadioButton("Automatic (Recommended)");
|
||||||
|
RadioButton advanced = new RadioButton("Advanced");
|
||||||
|
TextField entropy = new TextField();
|
||||||
|
entropy.setPromptText("Enter input for chain code");
|
||||||
|
entropy.setDisable(true);
|
||||||
|
|
||||||
|
ToggleGroup toggleGroup = new ToggleGroup();
|
||||||
|
automatic.setToggleGroup(toggleGroup);
|
||||||
|
advanced.setToggleGroup(toggleGroup);
|
||||||
|
automatic.setSelected(true);
|
||||||
|
toggleGroup.selectedToggleProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
entropy.setDisable(newValue == automatic);
|
||||||
|
});
|
||||||
|
|
||||||
|
initTypeBox.getChildren().addAll(automatic, advanced, entropy);
|
||||||
|
|
||||||
|
Button initializeButton = new Button("Initialize");
|
||||||
|
initializeButton.setDefaultButton(true);
|
||||||
|
initializeButton.setOnAction(event -> {
|
||||||
|
byte[] chainCode = toggleGroup.getSelectedToggle() == automatic ? null : Sha256Hash.hashTwice(entropy.getText().getBytes(StandardCharsets.UTF_8));
|
||||||
|
CardInitializationService cardInitializationService = new CardInitializationService(importer, chainCode);
|
||||||
|
cardInitializationService.setOnSucceeded(event1 -> {
|
||||||
|
initialized = true;
|
||||||
|
AppServices.showSuccessDialog("Card Initialized", "The card was successfully initialized.\n\nYou will now need to enter the PIN code found on the back. You can change the PIN code once it has been imported.");
|
||||||
|
setDescription("Enter PIN code");
|
||||||
|
setContent(getPinEntry());
|
||||||
|
setExpanded(true);
|
||||||
|
});
|
||||||
|
cardInitializationService.setOnFailed(event1 -> {
|
||||||
|
Throwable e = event1.getSource().getException();
|
||||||
|
log.error("Error initializing card", e);
|
||||||
|
AppServices.showErrorDialog("Card Initialization Failed", "The card was not initialized.\n\n" + e.getMessage());
|
||||||
|
});
|
||||||
|
cardInitializationService.start();
|
||||||
|
});
|
||||||
|
|
||||||
|
HBox contentBox = new HBox(20);
|
||||||
|
contentBox.getChildren().addAll(initTypeBox, initializeButton);
|
||||||
|
contentBox.setPadding(new Insets(10, 30, 10, 30));
|
||||||
|
HBox.setHgrow(initTypeBox, Priority.ALWAYS);
|
||||||
|
|
||||||
|
return contentBox;
|
||||||
|
}
|
||||||
|
|
||||||
private Node getPinEntry() {
|
private Node getPinEntry() {
|
||||||
VBox vBox = new VBox();
|
VBox vBox = new VBox();
|
||||||
|
|
||||||
if(!errorText.get().isEmpty()) {
|
if(!errorText.get().isEmpty()) {
|
||||||
Node contextBox = getContentBox(errorText.get());
|
Node errorBox = getContentBox(errorText.get());
|
||||||
if(contextBox instanceof HBox hBox && hBox.getPrefHeight() == 60) {
|
if(errorBox instanceof HBox hBox && hBox.getPrefHeight() == 60) {
|
||||||
hBox.setPrefHeight(50);
|
hBox.setPrefHeight(50);
|
||||||
}
|
}
|
||||||
vBox.getChildren().add(contextBox);
|
vBox.getChildren().add(errorBox);
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomPasswordField pinField = new ViewPasswordField();
|
CustomPasswordField pinField = new ViewPasswordField();
|
||||||
|
@ -121,6 +191,27 @@ public class CardImportPane extends TitledDescriptionPane {
|
||||||
return vBox;
|
return vBox;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class CardInitializationService extends Service<Void> {
|
||||||
|
private final KeystoreCardImport cardImport;
|
||||||
|
private final byte[] chainCode;
|
||||||
|
|
||||||
|
public CardInitializationService(KeystoreCardImport cardImport, byte[] chainCode) {
|
||||||
|
this.cardImport = cardImport;
|
||||||
|
this.chainCode = chainCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Task<Void> createTask() {
|
||||||
|
return new Task<>() {
|
||||||
|
@Override
|
||||||
|
protected Void call() throws Exception {
|
||||||
|
cardImport.initialize(chainCode);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class CardImportService extends Service<Keystore> {
|
public static class CardImportService extends Service<Keystore> {
|
||||||
private final KeystoreCardImport cardImport;
|
private final KeystoreCardImport cardImport;
|
||||||
private final String pin;
|
private final String pin;
|
||||||
|
|
|
@ -108,13 +108,17 @@ public class TitledDescriptionPane extends TitledPane {
|
||||||
|
|
||||||
protected void setDescription(String text) {
|
protected void setDescription(String text) {
|
||||||
descriptionLabel.getStyleClass().remove("description-error");
|
descriptionLabel.getStyleClass().remove("description-error");
|
||||||
|
if(!descriptionLabel.getStyleClass().contains("description-label")) {
|
||||||
descriptionLabel.getStyleClass().add("description-label");
|
descriptionLabel.getStyleClass().add("description-label");
|
||||||
|
}
|
||||||
descriptionLabel.setText(text);
|
descriptionLabel.setText(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setError(String title, String detail) {
|
protected void setError(String title, String detail) {
|
||||||
descriptionLabel.getStyleClass().remove("description-label");
|
descriptionLabel.getStyleClass().remove("description-label");
|
||||||
|
if(!descriptionLabel.getStyleClass().contains("description-error")) {
|
||||||
descriptionLabel.getStyleClass().add("description-error");
|
descriptionLabel.getStyleClass().add("description-error");
|
||||||
|
}
|
||||||
descriptionLabel.setText(title);
|
descriptionLabel.setText(title);
|
||||||
if(detail != null && !detail.isEmpty()) {
|
if(detail != null && !detail.isEmpty()) {
|
||||||
setContent(getContentBox(detail));
|
setContent(getContentBox(detail));
|
||||||
|
|
|
@ -2,6 +2,10 @@ package com.sparrowwallet.sparrow.io;
|
||||||
|
|
||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.StringProperty;
|
||||||
|
|
||||||
|
import javax.smartcardio.CardException;
|
||||||
|
|
||||||
public interface CardImport extends ImportExport {
|
public interface CardImport extends ImportExport {
|
||||||
|
boolean isInitialized() throws CardException;
|
||||||
|
void initialize(byte[] chainCode) throws CardException;
|
||||||
StringProperty messageProperty();
|
StringProperty messageProperty();
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,21 +23,36 @@ import java.util.List;
|
||||||
public class CardApi {
|
public class CardApi {
|
||||||
private static final Logger log = LoggerFactory.getLogger(CardApi.class);
|
private static final Logger log = LoggerFactory.getLogger(CardApi.class);
|
||||||
|
|
||||||
|
private final WalletModel cardType;
|
||||||
private final CardProtocol cardProtocol;
|
private final CardProtocol cardProtocol;
|
||||||
private String cvc;
|
private String cvc;
|
||||||
|
|
||||||
public CardApi(String cvc) throws CardException {
|
public CardApi(String cvc) throws CardException {
|
||||||
|
this(WalletModel.TAPSIGNER, cvc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CardApi(WalletModel cardType, String cvc) throws CardException {
|
||||||
|
this.cardType = cardType;
|
||||||
this.cardProtocol = new CardProtocol();
|
this.cardProtocol = new CardProtocol();
|
||||||
this.cvc = cvc;
|
this.cvc = cvc;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initialize() throws CardException {
|
public boolean isInitialized() throws CardException {
|
||||||
|
CardStatus cardStatus = getStatus();
|
||||||
|
return cardStatus.isInitialized();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initialize(byte[] chainCode) throws CardException {
|
||||||
cardProtocol.verify();
|
cardProtocol.verify();
|
||||||
cardProtocol.setup(cvc, null);
|
cardProtocol.setup(cvc, chainCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
CardStatus getStatus() throws CardException {
|
CardStatus getStatus() throws CardException {
|
||||||
return cardProtocol.getStatus();
|
CardStatus cardStatus = cardProtocol.getStatus();
|
||||||
|
if(cardStatus.getCardType() != cardType) {
|
||||||
|
throw new CardException("Please use a " + cardType.toDisplayString() + " card.");
|
||||||
|
}
|
||||||
|
return cardStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
void checkWait(CardStatus cardStatus, IntegerProperty delayProperty, StringProperty messageProperty) throws CardException {
|
void checkWait(CardStatus cardStatus, IntegerProperty delayProperty, StringProperty messageProperty) throws CardException {
|
||||||
|
@ -91,7 +106,7 @@ public class CardApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Keystore getKeystore() throws CardException {
|
public Keystore getKeystore() throws CardException {
|
||||||
CardStatus cardStatus = cardProtocol.getStatus();
|
CardStatus cardStatus = getStatus();
|
||||||
|
|
||||||
CardXpub masterXpub = cardProtocol.xpub(cvc, true);
|
CardXpub masterXpub = cardProtocol.xpub(cvc, true);
|
||||||
ExtendedKey masterXpubkey = ExtendedKey.fromDescriptor(Base58.encodeChecked(masterXpub.xpub));
|
ExtendedKey masterXpubkey = ExtendedKey.fromDescriptor(Base58.encodeChecked(masterXpub.xpub));
|
||||||
|
|
|
@ -94,17 +94,17 @@ public class CardProtocol {
|
||||||
return gson.fromJson(read, CardRead.class);
|
return gson.fromJson(read, CardRead.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CardSetup setup(String cvc, byte[] entropy) throws CardException {
|
public CardSetup setup(String cvc, byte[] chainCode) throws CardException {
|
||||||
if(entropy == null) {
|
if(chainCode == null) {
|
||||||
entropy = Sha256Hash.hashTwice(secureRandom.generateSeed(128));
|
chainCode = Sha256Hash.hashTwice(secureRandom.generateSeed(128));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(entropy.length != 32) {
|
if(chainCode.length != 32) {
|
||||||
throw new IllegalArgumentException("Invalid entropy length of " + entropy.length);
|
throw new IllegalArgumentException("Invalid chain code length of " + chainCode.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, Object> args = new HashMap<>();
|
Map<String, Object> args = new HashMap<>();
|
||||||
args.put("chain_code", entropy);
|
args.put("chain_code", chainCode);
|
||||||
JsonObject setup = sendAuth("new", args, cvc);
|
JsonObject setup = sendAuth("new", args, cvc);
|
||||||
return gson.fromJson(setup, CardSetup.class);
|
return gson.fromJson(setup, CardSetup.class);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package com.sparrowwallet.sparrow.io.ckcard;
|
||||||
import com.google.common.io.BaseEncoding;
|
import com.google.common.io.BaseEncoding;
|
||||||
import com.sparrowwallet.drongo.crypto.ChildNumber;
|
import com.sparrowwallet.drongo.crypto.ChildNumber;
|
||||||
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
||||||
|
import com.sparrowwallet.drongo.wallet.WalletModel;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -13,7 +14,7 @@ public class CardStatus extends CardResponse {
|
||||||
int proto;
|
int proto;
|
||||||
String ver;
|
String ver;
|
||||||
BigInteger birth;
|
BigInteger birth;
|
||||||
Boolean tapsigner;
|
boolean tapsigner;
|
||||||
List<BigInteger> path;
|
List<BigInteger> path;
|
||||||
BigInteger num_backups;
|
BigInteger num_backups;
|
||||||
byte[] pubkey;
|
byte[] pubkey;
|
||||||
|
@ -42,6 +43,10 @@ public class CardStatus extends CardResponse {
|
||||||
return num_backups == null || num_backups.intValue() == 0;
|
return num_backups == null || num_backups.intValue() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public WalletModel getCardType() {
|
||||||
|
return tapsigner ? WalletModel.TAPSIGNER : WalletModel.SATSCARD;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "CardStatus{" +
|
return "CardStatus{" +
|
||||||
|
|
|
@ -45,7 +45,8 @@ public class CardTransport {
|
||||||
CardChannel cardChannel = connection.getBasicChannel();
|
CardChannel cardChannel = connection.getBasicChannel();
|
||||||
ResponseAPDU resp = cardChannel.transmit(new CommandAPDU(0, 0xA4, 4, 0, Utils.hexToBytes(APPID.toUpperCase())));
|
ResponseAPDU resp = cardChannel.transmit(new CommandAPDU(0, 0xA4, 4, 0, Utils.hexToBytes(APPID.toUpperCase())));
|
||||||
if(resp.getSW() != SW_OKAY) {
|
if(resp.getSW() != SW_OKAY) {
|
||||||
throw new CardException("Card returned response of " + resp.getSW());
|
log.error("Card initialization error, response was 0x" + Integer.toHexString(resp.getSW()));
|
||||||
|
throw new CardException("Card initialization error, response was 0x" + Integer.toHexString(resp.getSW()) + ". Note that only the Tapsigner is currently supported.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,7 +120,7 @@ public class CardTransport {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch(CborException e) {
|
} catch(CborException e) {
|
||||||
e.printStackTrace();
|
log.error("CBOR encoding error", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new JsonObject();
|
return new JsonObject();
|
||||||
|
|
|
@ -15,14 +15,40 @@ import java.util.List;
|
||||||
public class CkCard implements KeystoreCardImport {
|
public class CkCard implements KeystoreCardImport {
|
||||||
private final StringProperty messageProperty = new SimpleStringProperty("");
|
private final StringProperty messageProperty = new SimpleStringProperty("");
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInitialized() throws CardException {
|
||||||
|
CardApi cardApi = null;
|
||||||
|
try {
|
||||||
|
cardApi = new CardApi(null);
|
||||||
|
return cardApi.isInitialized();
|
||||||
|
} finally {
|
||||||
|
if(cardApi != null) {
|
||||||
|
cardApi.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(byte[] chainCode) throws CardException {
|
||||||
|
CardApi cardApi = null;
|
||||||
|
try {
|
||||||
|
cardApi = new CardApi(null);
|
||||||
|
cardApi.initialize(chainCode);
|
||||||
|
} finally {
|
||||||
|
if(cardApi != null) {
|
||||||
|
cardApi.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Keystore getKeystore(String pin, List<ChildNumber> derivation) throws ImportException {
|
public Keystore getKeystore(String pin, List<ChildNumber> derivation) throws ImportException {
|
||||||
if(pin.length() < 6) {
|
if(pin.length() < 6) {
|
||||||
throw new ImportException("PIN too short");
|
throw new ImportException("PIN too short.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(pin.length() > 32) {
|
if(pin.length() > 32) {
|
||||||
throw new ImportException("PIN too long");
|
throw new ImportException("PIN too long.");
|
||||||
}
|
}
|
||||||
|
|
||||||
CardApi cardApi = null;
|
CardApi cardApi = null;
|
||||||
|
@ -30,8 +56,7 @@ public class CkCard implements KeystoreCardImport {
|
||||||
cardApi = new CardApi(pin);
|
cardApi = new CardApi(pin);
|
||||||
CardStatus cardStatus = cardApi.getStatus();
|
CardStatus cardStatus = cardApi.getStatus();
|
||||||
if(!cardStatus.isInitialized()) {
|
if(!cardStatus.isInitialized()) {
|
||||||
cardApi.initialize();
|
throw new IllegalStateException("Card is not initialized.");
|
||||||
cardStatus = cardApi.getStatus();
|
|
||||||
}
|
}
|
||||||
cardApi.checkWait(cardStatus, new SimpleIntegerProperty(), messageProperty);
|
cardApi.checkWait(cardStatus, new SimpleIntegerProperty(), messageProperty);
|
||||||
|
|
||||||
|
@ -39,7 +64,7 @@ public class CkCard implements KeystoreCardImport {
|
||||||
cardApi.setDerivation(derivation);
|
cardApi.setDerivation(derivation);
|
||||||
}
|
}
|
||||||
return cardApi.getKeystore();
|
return cardApi.getKeystore();
|
||||||
} catch(CardException e) {
|
} catch(Exception e) {
|
||||||
throw new ImportException(e);
|
throw new ImportException(e);
|
||||||
} finally {
|
} finally {
|
||||||
if(cardApi != null) {
|
if(cardApi != null) {
|
||||||
|
|
Loading…
Reference in a new issue