mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-11-02 20:36:44 +00:00
encrypt electrum wallet exports including private keys where password is available
This commit is contained in:
parent
4feb4a3a79
commit
b5196d1ac2
19 changed files with 83 additions and 39 deletions
|
@ -122,16 +122,12 @@ public class FileWalletExportPane extends TitledDescriptionPane {
|
|||
Optional<SecureString> password = dlg.showAndWait();
|
||||
if(password.isPresent()) {
|
||||
final String walletId = AppServices.get().getOpenWallets().get(wallet).getWalletId(wallet);
|
||||
String walletPassword = password.get().asString();
|
||||
Storage.DecryptWalletService decryptWalletService = new Storage.DecryptWalletService(copy, password.get());
|
||||
decryptWalletService.setOnSucceeded(workerStateEvent -> {
|
||||
EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.END, "Done"));
|
||||
Wallet decryptedWallet = decryptWalletService.getValue();
|
||||
|
||||
try {
|
||||
exportWallet(file, decryptedWallet);
|
||||
} finally {
|
||||
decryptedWallet.clearPrivate();
|
||||
}
|
||||
exportWallet(file, decryptedWallet, walletPassword);
|
||||
});
|
||||
decryptWalletService.setOnFailed(workerStateEvent -> {
|
||||
EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.END, "Failed"));
|
||||
|
@ -141,14 +137,14 @@ public class FileWalletExportPane extends TitledDescriptionPane {
|
|||
decryptWalletService.start();
|
||||
}
|
||||
} else {
|
||||
exportWallet(file, wallet);
|
||||
exportWallet(file, wallet, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void exportWallet(File file, Wallet exportWallet) {
|
||||
private void exportWallet(File file, Wallet exportWallet, String password) {
|
||||
try {
|
||||
if(file != null) {
|
||||
FileWalletExportService fileWalletExportService = new FileWalletExportService(exporter, file, exportWallet);
|
||||
FileWalletExportService fileWalletExportService = new FileWalletExportService(exporter, file, exportWallet, password);
|
||||
fileWalletExportService.setOnSucceeded(event -> {
|
||||
EventManager.get().post(new WalletExportEvent(exportWallet));
|
||||
});
|
||||
|
@ -163,7 +159,7 @@ public class FileWalletExportPane extends TitledDescriptionPane {
|
|||
fileWalletExportService.start();
|
||||
} else {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
exporter.exportWallet(exportWallet, outputStream);
|
||||
exporter.exportWallet(exportWallet, outputStream, password);
|
||||
QRDisplayDialog qrDisplayDialog;
|
||||
if(exporter instanceof CoboVaultMultisig) {
|
||||
qrDisplayDialog = new QRDisplayDialog(RegistryType.BYTES.toString(), outputStream.toByteArray(), true);
|
||||
|
@ -185,6 +181,10 @@ public class FileWalletExportPane extends TitledDescriptionPane {
|
|||
errorMessage = e.getCause().getMessage();
|
||||
}
|
||||
setError("Export Error", errorMessage);
|
||||
} finally {
|
||||
if(file == null && password != null) {
|
||||
exportWallet.clearPrivate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -192,11 +192,13 @@ public class FileWalletExportPane extends TitledDescriptionPane {
|
|||
private final WalletExport exporter;
|
||||
private final File file;
|
||||
private final Wallet wallet;
|
||||
private final String password;
|
||||
|
||||
public FileWalletExportService(WalletExport exporter, File file, Wallet wallet) {
|
||||
public FileWalletExportService(WalletExport exporter, File file, Wallet wallet, String password) {
|
||||
this.exporter = exporter;
|
||||
this.file = file;
|
||||
this.wallet = wallet;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -205,7 +207,11 @@ public class FileWalletExportPane extends TitledDescriptionPane {
|
|||
@Override
|
||||
protected Void call() throws Exception {
|
||||
try(OutputStream outputStream = new FileOutputStream(file)) {
|
||||
exporter.exportWallet(wallet, outputStream);
|
||||
exporter.exportWallet(wallet, outputStream, password);
|
||||
} finally {
|
||||
if(password != null) {
|
||||
wallet.clearPrivate();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
@ -189,7 +189,7 @@ public class Bip129 implements KeystoreFileExport, KeystoreFileImport, WalletExp
|
|||
}
|
||||
|
||||
@Override
|
||||
public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException {
|
||||
public void exportWallet(Wallet wallet, OutputStream outputStream, String password) throws ExportException {
|
||||
try {
|
||||
String record = "BSMS 1.0\n" +
|
||||
OutputDescriptor.getOutputDescriptor(wallet) +
|
||||
|
|
|
@ -94,7 +94,7 @@ public class CaravanMultisig implements WalletImport, WalletExport {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException {
|
||||
public void exportWallet(Wallet wallet, OutputStream outputStream, String password) throws ExportException {
|
||||
if(!wallet.isValid()) {
|
||||
throw new ExportException("Cannot export an incomplete wallet");
|
||||
}
|
||||
|
|
|
@ -165,7 +165,7 @@ public class ColdcardMultisig implements WalletImport, KeystoreFileImport, Walle
|
|||
}
|
||||
|
||||
@Override
|
||||
public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException {
|
||||
public void exportWallet(Wallet wallet, OutputStream outputStream, String password) throws ExportException {
|
||||
if(!wallet.isValid()) {
|
||||
throw new ExportException("Cannot export an incomplete wallet");
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ public class Descriptor implements WalletImport, WalletExport {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException {
|
||||
public void exportWallet(Wallet wallet, OutputStream outputStream, String password) throws ExportException {
|
||||
try {
|
||||
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
|
||||
bufferedWriter.write("# Receive and change descriptor (BIP389):");
|
||||
|
|
|
@ -2,10 +2,7 @@ package com.sparrowwallet.sparrow.io;
|
|||
|
||||
import com.google.gson.*;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.sparrowwallet.drongo.ExtendedKey;
|
||||
import com.sparrowwallet.drongo.KeyDerivation;
|
||||
import com.sparrowwallet.drongo.KeyPurpose;
|
||||
import com.sparrowwallet.drongo.Utils;
|
||||
import com.sparrowwallet.drongo.*;
|
||||
import com.sparrowwallet.drongo.address.Address;
|
||||
import com.sparrowwallet.drongo.crypto.*;
|
||||
import com.sparrowwallet.drongo.policy.Policy;
|
||||
|
@ -18,7 +15,10 @@ import org.slf4j.LoggerFactory;
|
|||
import java.io.*;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.*;
|
||||
import java.util.zip.DeflaterOutputStream;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
|
||||
public class Electrum implements KeystoreFileImport, WalletImport, WalletExport {
|
||||
|
@ -301,11 +301,11 @@ public class Electrum implements KeystoreFileImport, WalletImport, WalletExport
|
|||
|
||||
@Override
|
||||
public String getExportFileExtension(Wallet wallet) {
|
||||
return "json";
|
||||
return wallet.isEncrypted() ? "" : "json";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException {
|
||||
public void exportWallet(Wallet wallet, OutputStream outputStream, String password) throws ExportException {
|
||||
try {
|
||||
ElectrumJsonWallet ew = new ElectrumJsonWallet();
|
||||
if(wallet.getPolicyType().equals(PolicyType.SINGLE)) {
|
||||
|
@ -342,7 +342,18 @@ public class Electrum implements KeystoreFileImport, WalletImport, WalletExport
|
|||
ek.seed = keystore.getSeed().getMnemonicString().asString();
|
||||
ek.passphrase = keystore.getSeed().getPassphrase() == null ? null : keystore.getSeed().getPassphrase().asString();
|
||||
}
|
||||
if(password != null) {
|
||||
ek.xprv = encrypt(ek.xprv, password);
|
||||
if(ek.seed != null) {
|
||||
ek.seed = encrypt(ek.seed, password);
|
||||
}
|
||||
if(ek.passphrase != null) {
|
||||
ek.passphrase = encrypt(ek.passphrase, password);
|
||||
}
|
||||
ew.use_encryption = true;
|
||||
} else {
|
||||
ew.use_encryption = false;
|
||||
}
|
||||
} else if(keystore.getSource() == KeystoreSource.SW_WATCH) {
|
||||
ek.type = "bip32";
|
||||
ek.xpub = keystore.getExtendedPublicKey().toString(xpubHeader);
|
||||
|
@ -373,14 +384,41 @@ public class Electrum implements KeystoreFileImport, WalletImport, WalletExport
|
|||
|
||||
gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
|
||||
String json = gson.toJson(eJson);
|
||||
if(password != null) {
|
||||
ECKey encryptionKey = Pbkdf2KeyDeriver.DEFAULT_INSTANCE.deriveECKey(password);
|
||||
outputStream = new DeflaterOutputStream(new ECIESOutputStream(outputStream, encryptionKey));
|
||||
}
|
||||
|
||||
outputStream.write(json.getBytes(StandardCharsets.UTF_8));
|
||||
outputStream.flush();
|
||||
outputStream.close();
|
||||
} catch (Exception e) {
|
||||
log.error("Error exporting Electrum Wallet", e);
|
||||
throw new ExportException("Error exporting Electrum Wallet", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String encrypt(String plain, String password) throws NoSuchAlgorithmException {
|
||||
if(plain == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] plainBytes = plain.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
KeyDeriver keyDeriver = new DoubleSha256KeyDeriver();
|
||||
Key key = keyDeriver.deriveKey(password);
|
||||
|
||||
KeyCrypter keyCrypter = new AESKeyCrypter();
|
||||
byte[] initializationVector = new byte[16];
|
||||
SecureRandom.getInstanceStrong().nextBytes(initializationVector);
|
||||
EncryptedData encryptedData = keyCrypter.encrypt(plainBytes, initializationVector, key);
|
||||
byte[] encrypted = new byte[initializationVector.length + encryptedData.getEncryptedBytes().length];
|
||||
System.arraycopy(initializationVector, 0, encrypted, 0, 16);
|
||||
System.arraycopy(encryptedData.getEncryptedBytes(), 0, encrypted, 16, encryptedData.getEncryptedBytes().length);
|
||||
byte[] encryptedBase64 = Base64.getEncoder().encode(encrypted);
|
||||
return new String(encryptedBase64, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEncrypted(File file) {
|
||||
return (FileType.BINARY.equals(IOUtils.getFileType(file)));
|
||||
|
|
|
@ -29,7 +29,7 @@ public class ElectrumPersonalServer implements WalletExport {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException {
|
||||
public void exportWallet(Wallet wallet, OutputStream outputStream, String password) throws ExportException {
|
||||
if(wallet.getScriptType() == ScriptType.P2TR) {
|
||||
throw new ExportException(getName() + " does not support Taproot wallets.");
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ public class Sparrow implements WalletImport, WalletExport {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException {
|
||||
public void exportWallet(Wallet wallet, OutputStream outputStream, String password) throws ExportException {
|
||||
try {
|
||||
Wallet exportedWallet = !wallet.isMasterWallet() ? wallet.getMasterWallet() : wallet;
|
||||
PersistenceType persistenceType = PersistenceType.DB;
|
||||
|
|
|
@ -65,7 +65,7 @@ public class SpecterDIY implements KeystoreFileImport, WalletExport {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException {
|
||||
public void exportWallet(Wallet wallet, OutputStream outputStream, String password) throws ExportException {
|
||||
try {
|
||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));
|
||||
writer.append("addwallet ").append(wallet.getFullName()).append("&").append(OutputDescriptor.getOutputDescriptor(wallet).toString().replace('\'', 'h')).append("\n");
|
||||
|
|
|
@ -18,7 +18,7 @@ public class SpecterDesktop implements WalletImport, WalletExport {
|
|||
private static final Logger log = LoggerFactory.getLogger(SpecterDesktop.class);
|
||||
|
||||
@Override
|
||||
public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException {
|
||||
public void exportWallet(Wallet wallet, OutputStream outputStream, String password) throws ExportException {
|
||||
try {
|
||||
SpecterWallet specterWallet = new SpecterWallet();
|
||||
specterWallet.label = wallet.getFullName();
|
||||
|
|
|
@ -752,7 +752,7 @@ public class Storage {
|
|||
protected Void call() throws IOException, ExportException {
|
||||
Sparrow export = new Sparrow();
|
||||
try(BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(newWalletFile))) {
|
||||
export.exportWallet(wallet, outputStream);
|
||||
export.exportWallet(wallet, outputStream, null);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
@ -5,7 +5,7 @@ import com.sparrowwallet.drongo.wallet.Wallet;
|
|||
import java.io.OutputStream;
|
||||
|
||||
public interface WalletExport extends ImportExport {
|
||||
void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException;
|
||||
void exportWallet(Wallet wallet, OutputStream outputStream, String password) throws ExportException;
|
||||
String getWalletExportDescription();
|
||||
String getExportFileExtension(Wallet wallet);
|
||||
boolean isWalletExportScannable();
|
||||
|
|
|
@ -46,7 +46,7 @@ public class WalletLabels implements WalletImport, WalletExport {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException {
|
||||
public void exportWallet(Wallet wallet, OutputStream outputStream, String password) throws ExportException {
|
||||
List<Label> labels = new ArrayList<>();
|
||||
List<Wallet> allWallets = wallet.isMasterWallet() ? wallet.getAllWallets() : wallet.getMasterWallet().getAllWallets();
|
||||
for(Wallet exportWallet : allWallets) {
|
||||
|
|
|
@ -44,7 +44,7 @@ public class WalletTransactions implements WalletExport {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException {
|
||||
public void exportWallet(Wallet wallet, OutputStream outputStream, String password) throws ExportException {
|
||||
WalletTransactionsEntry walletTransactionsEntry = walletForm.getWalletTransactionsEntry();
|
||||
|
||||
ExchangeSource exchangeSource = Config.get().getExchangeSource();
|
||||
|
|
|
@ -114,7 +114,7 @@ public class TransactionsController extends WalletFormController implements Init
|
|||
AppServices.moveToActiveWindowScreen(window, 800, 450);
|
||||
File file = fileChooser.showSaveDialog(window);
|
||||
if(file != null) {
|
||||
FileWalletExportPane.FileWalletExportService exportService = new FileWalletExportPane.FileWalletExportService(new WalletTransactions(getWalletForm()), file, wallet);
|
||||
FileWalletExportPane.FileWalletExportService exportService = new FileWalletExportPane.FileWalletExportService(new WalletTransactions(getWalletForm()), file, wallet, null);
|
||||
exportService.setOnFailed(failedEvent -> {
|
||||
Throwable e = failedEvent.getSource().getException();
|
||||
log.error("Error exporting transactions as CSV", e);
|
||||
|
|
|
@ -36,7 +36,7 @@ public class CaravanMultisigTest extends IoTest {
|
|||
byte[] walletBytes = ByteStreams.toByteArray(getInputStream("caravan-multisig-export-1.json"));
|
||||
Wallet wallet = ccMultisig.importWallet(new ByteArrayInputStream(walletBytes), null);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ccMultisig.exportWallet(wallet, baos);
|
||||
ccMultisig.exportWallet(wallet, baos, null);
|
||||
byte[] exportedBytes = baos.toByteArray();
|
||||
String original = new String(walletBytes);
|
||||
String exported = new String(exportedBytes);
|
||||
|
|
|
@ -105,7 +105,7 @@ public class ColdcardMultisigTest extends IoTest {
|
|||
byte[] walletBytes = ByteStreams.toByteArray(getInputStream("cc-multisig-export-1.txt"));
|
||||
Wallet wallet = ccMultisig.importWallet(new ByteArrayInputStream(walletBytes), null);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ccMultisig.exportWallet(wallet, baos);
|
||||
ccMultisig.exportWallet(wallet, baos, null);
|
||||
byte[] exportedBytes = baos.toByteArray();
|
||||
String original = new String(walletBytes);
|
||||
String exported = new String(exportedBytes);
|
||||
|
@ -118,7 +118,7 @@ public class ColdcardMultisigTest extends IoTest {
|
|||
byte[] walletBytes = ByteStreams.toByteArray(getInputStream("cc-multisig-export-multideriv.txt"));
|
||||
Wallet wallet = ccMultisig.importWallet(new ByteArrayInputStream(walletBytes), null);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ccMultisig.exportWallet(wallet, baos);
|
||||
ccMultisig.exportWallet(wallet, baos, null);
|
||||
byte[] exportedBytes = baos.toByteArray();
|
||||
String original = new String(walletBytes);
|
||||
String exported = new String(exportedBytes);
|
||||
|
|
|
@ -38,7 +38,7 @@ public class ElectrumTest extends IoTest {
|
|||
byte[] walletBytes = ByteStreams.toByteArray(getInputStream("electrum-singlesig-wallet.json"));
|
||||
Wallet wallet = electrum.importWallet(new ByteArrayInputStream(walletBytes), null);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
electrum.exportWallet(wallet, baos);
|
||||
electrum.exportWallet(wallet, baos, null);
|
||||
|
||||
wallet = electrum.importWallet(new ByteArrayInputStream(baos.toByteArray()), null);
|
||||
Assert.assertTrue(wallet.isValid());
|
||||
|
@ -76,7 +76,7 @@ public class ElectrumTest extends IoTest {
|
|||
byte[] walletBytes = ByteStreams.toByteArray(getInputStream("electrum-multisig-wallet.json"));
|
||||
Wallet wallet = electrum.importWallet(new ByteArrayInputStream(walletBytes), null);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
electrum.exportWallet(wallet, baos);
|
||||
electrum.exportWallet(wallet, baos, null);
|
||||
|
||||
wallet = electrum.importWallet(new ByteArrayInputStream(baos.toByteArray()), null);
|
||||
Assert.assertTrue(wallet.isValid());
|
||||
|
@ -113,7 +113,7 @@ public class ElectrumTest extends IoTest {
|
|||
byte[] walletBytes = ByteStreams.toByteArray(getInputStream("electrum-singlesig-seed-wallet.json"));
|
||||
Wallet wallet = electrum.importWallet(new ByteArrayInputStream(walletBytes), null);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
electrum.exportWallet(wallet, baos);
|
||||
electrum.exportWallet(wallet, baos, null);
|
||||
Assert.assertEquals("e14c40c638e2c83d1f20e5ee9cd744bc2ba1ef64fa939926f3778fc8735e891f56852f687b32bbd044f272d2831137e3eeba61fd1f285fa73dcc97d9f2be3cd1", Utils.bytesToHex(wallet.getKeystores().get(0).getSeed().getSeedBytes()));
|
||||
|
||||
wallet = electrum.importWallet(new ByteArrayInputStream(baos.toByteArray()), null);
|
||||
|
|
|
@ -37,7 +37,7 @@ public class SpecterDIYTest extends IoTest {
|
|||
SpecterDIY specterDIY = new SpecterDIY();
|
||||
byte[] walletBytes = ByteStreams.toByteArray(getInputStream("specter-diy-export.txt"));
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
specterDIY.exportWallet(wallet, baos);
|
||||
specterDIY.exportWallet(wallet, baos, null);
|
||||
String original = new String(walletBytes);
|
||||
String exported = new String(baos.toByteArray());
|
||||
|
||||
|
|
Loading…
Reference in a new issue