passphrase not stored

This commit is contained in:
Craig Raw 2020-05-16 15:09:47 +02:00
parent 1bf8c85a65
commit 6b09dc0293
15 changed files with 93 additions and 111 deletions

2
drongo

@ -1 +1 @@
Subproject commit 9e5a7d0e8d31eb4e21d543081c23f7f0aaef9795
Subproject commit 312143cb611fefce4e75654266f90cfd3b37b09e

View file

@ -4,14 +4,13 @@ import com.google.common.base.Charsets;
import com.google.common.eventbus.Subscribe;
import com.google.common.io.ByteSource;
import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.crypto.ECIESKeyCrypter;
import com.sparrowwallet.drongo.crypto.ECKey;
import com.sparrowwallet.drongo.crypto.InvalidPasswordException;
import com.sparrowwallet.drongo.crypto.*;
import com.sparrowwallet.drongo.policy.PolicyType;
import com.sparrowwallet.drongo.protocol.ScriptType;
import com.sparrowwallet.drongo.protocol.Transaction;
import com.sparrowwallet.drongo.psbt.PSBT;
import com.sparrowwallet.drongo.psbt.PSBTParseException;
import com.sparrowwallet.drongo.wallet.Keystore;
import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.control.*;
import com.sparrowwallet.sparrow.event.*;
@ -235,24 +234,65 @@ public class AppController implements Initializable {
if(file != null) {
try {
Wallet wallet;
String password = null;
ECKey encryptionPubKey = WalletForm.NO_PASSWORD_KEY;
FileType fileType = IOUtils.getFileType(file);
if(FileType.JSON.equals(fileType)) {
wallet = Storage.getStorage().loadWallet(file);
} else if(FileType.BINARY.equals(fileType)) {
WalletPasswordDialog dlg = new WalletPasswordDialog(WalletPasswordDialog.PasswordRequirement.LOAD);
Optional<String> password = dlg.showAndWait();
if(!password.isPresent()) {
Optional<String> optionalPassword = dlg.showAndWait();
if(!optionalPassword.isPresent()) {
return;
}
ECKey encryptionFullKey = ECIESKeyCrypter.deriveECKey(password.get());
password = optionalPassword.get();
ECKey encryptionFullKey = ECIESKeyCrypter.deriveECKey(password);
wallet = Storage.getStorage().loadWallet(file, encryptionFullKey);
encryptionPubKey = ECKey.fromPublicOnly(encryptionFullKey);
} else {
throw new IOException("Unsupported file type");
}
if(wallet.containsSeeds()) {
//Derive xpub and master fingerprint from seed, potentially with passphrase
Wallet copy = wallet.copy();
if(wallet.isEncrypted()) {
if(password == null) {
throw new IllegalStateException("Wallet seeds are encrypted but wallet is not");
}
copy.decrypt(password);
}
for(Keystore copyKeystore : copy.getKeystores()) {
if(copyKeystore.hasSeed()) {
if(copyKeystore.getSeed().needsPassphrase()) {
KeystorePassphraseDialog passphraseDialog = new KeystorePassphraseDialog(copyKeystore);
Optional<String> optionalPassphrase = passphraseDialog.showAndWait();
if(optionalPassphrase.isPresent()) {
copyKeystore.getSeed().setPassphrase(optionalPassphrase.get());
} else {
return;
}
} else {
copyKeystore.getSeed().setPassphrase("");
}
}
}
for(int i = 0; i < wallet.getKeystores().size(); i++) {
Keystore keystore = wallet.getKeystores().get(i);
if(keystore.hasSeed()) {
Keystore copyKeystore = copy.getKeystores().get(i);
Keystore derivedKeystore = Keystore.fromSeed(copyKeystore.getSeed(), copyKeystore.getKeyDerivation().getDerivation());
keystore.setKeyDerivation(derivedKeystore.getKeyDerivation());
keystore.setExtendedPublicKey(derivedKeystore.getExtendedPublicKey());
keystore.getSeed().setPassphrase(copyKeystore.getSeed().getPassphrase());
}
}
}
Tab tab = addWalletTab(file, encryptionPubKey, wallet);
tabs.getSelectionModel().select(tab);
} catch (InvalidPasswordException e) {

View file

@ -55,19 +55,7 @@ public class FileWalletExportPane extends TitledDescriptionPane {
WalletPasswordDialog dlg = new WalletPasswordDialog(WalletPasswordDialog.PasswordRequirement.LOAD);
Optional<String> password = dlg.showAndWait();
if(password.isPresent()) {
copy.decrypt(password.get(), "");
for(Keystore keystore : copy.getKeystores()) {
if(keystore.hasSeed() && keystore.getSeed().usesPassphrase()) {
KeystorePassphraseDialog passphraseDialog = new KeystorePassphraseDialog(keystore);
Optional<String> passphrase = passphraseDialog.showAndWait();
if(passphrase.isPresent()) {
keystore.getSeed().setPassphrase(passphrase.get());
} else {
return;
}
}
}
copy.decrypt(password.get());
} else {
return;
}

View file

@ -28,7 +28,7 @@ public class Bip39 implements KeystoreMnemonicImport {
public Keystore getKeystore(List<ChildNumber> derivation, List<String> mnemonicWords, String passphrase) throws ImportException {
try {
Bip39MnemonicCode.INSTANCE.check(mnemonicWords);
DeterministicSeed seed = new DeterministicSeed(mnemonicWords, null, passphrase, System.currentTimeMillis(), DeterministicSeed.Type.BIP39);
DeterministicSeed seed = new DeterministicSeed(mnemonicWords, passphrase, System.currentTimeMillis(), DeterministicSeed.Type.BIP39);
return Keystore.fromSeed(seed, derivation);
} catch (Exception e) {
throw new ImportException(e);

View file

@ -34,7 +34,7 @@ public class ColdcardMultisig implements WalletImport, KeystoreFileImport, Walle
@Override
public Keystore getKeystore(ScriptType scriptType, InputStream inputStream, String password) throws ImportException {
InputStreamReader reader = new InputStreamReader(inputStream);
ColdcardKeystore cck = Storage.getStorage().getGson().fromJson(reader, ColdcardKeystore.class);
ColdcardKeystore cck = Storage.getGson().fromJson(reader, ColdcardKeystore.class);
Keystore keystore = new Keystore("Coldcard " + cck.xfp);
keystore.setSource(KeystoreSource.HW_AIRGAPPED);

View file

@ -43,7 +43,7 @@ public class ECIESInputStream extends FilterInputStream {
byte[] encryptedBytes = ByteStreams.toByteArray(in);
in.close();
ECIESKeyCrypter keyCrypter = new ECIESKeyCrypter();
byte[] decryptedBytes = keyCrypter.decrypt(new EncryptedData(encryptionMagic, encryptedBytes), decryptionKey);
byte[] decryptedBytes = keyCrypter.decrypt(new EncryptedData(encryptionMagic, encryptedBytes, null), decryptionKey);
in = new ByteArrayInputStream(decryptedBytes);
decrypted = true;
}

View file

@ -112,17 +112,15 @@ public class Electrum implements KeystoreFileImport, WalletImport, WalletExport
throw new ImportException("Electrum does not support exporting BIP39 derived seeds.");
} else if(ek.seed != null) {
keystore.setSource(KeystoreSource.SW_SEED);
String seed = ek.seed;
String mnemonic = ek.seed;
String passphrase = ek.passphrase;
if(password != null) {
seed = decrypt(seed, password);
mnemonic = decrypt(mnemonic, password);
passphrase = decrypt(passphrase, password);
}
keystore.setSeed(new DeterministicSeed(seed, null, passphrase, 0, DeterministicSeed.Type.ELECTRUM));
keystore.getSeed().setPassphrase(passphrase);
if(derivationPath == "m/0") {
keystore.setSeed(new DeterministicSeed(mnemonic, passphrase, 0, DeterministicSeed.Type.ELECTRUM));
if(derivationPath.equals("m/0")) {
derivationPath = "m/0'";
}
} else {

View file

@ -4,6 +4,7 @@ import com.google.gson.*;
import com.sparrowwallet.drongo.ExtendedKey;
import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.crypto.ECKey;
import com.sparrowwallet.drongo.wallet.Keystore;
import com.sparrowwallet.drongo.wallet.Wallet;
import java.io.*;
@ -20,12 +21,7 @@ public class Storage {
private final Gson gson;
private Storage() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(ExtendedKey.class, new ExtendedPublicKeySerializer());
gsonBuilder.registerTypeAdapter(ExtendedKey.class, new ExtendedPublicKeyDeserializer());
gsonBuilder.registerTypeAdapter(byte[].class, new ByteArraySerializer());
gsonBuilder.registerTypeAdapter(byte[].class, new ByteArrayDeserializer());
gson = gsonBuilder.setPrettyPrinting().disableHtmlEscaping().create();
gson = getGson();
}
public static Storage getStorage() {
@ -36,8 +32,21 @@ public class Storage {
return SINGLETON;
}
public Gson getGson() {
return gson;
public static Gson getGson() {
return getGson(true);
}
private static Gson getGson(boolean includeKeystoreSerializer) {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(ExtendedKey.class, new ExtendedPublicKeySerializer());
gsonBuilder.registerTypeAdapter(ExtendedKey.class, new ExtendedPublicKeyDeserializer());
gsonBuilder.registerTypeAdapter(byte[].class, new ByteArraySerializer());
gsonBuilder.registerTypeAdapter(byte[].class, new ByteArrayDeserializer());
if(includeKeystoreSerializer) {
gsonBuilder.registerTypeAdapter(Keystore.class, new KeystoreSerializer());
}
return gsonBuilder.setPrettyPrinting().disableHtmlEscaping().create();
}
public Wallet loadWallet(File file) throws IOException {
@ -150,4 +159,18 @@ public class Storage {
return Utils.hexToBytes(json.getAsJsonPrimitive().getAsString());
}
}
private static class KeystoreSerializer implements JsonSerializer<Keystore> {
@Override
public JsonElement serialize(Keystore keystore, Type typeOfSrc, JsonSerializationContext context) {
JsonObject jsonObject = (JsonObject)getGson(false).toJsonTree(keystore);
if(keystore.hasSeed()) {
jsonObject.remove("extendedPublicKey");
jsonObject.getAsJsonObject("keyDerivation").remove("masterFingerprint");
}
return jsonObject;
}
}
}

View file

@ -152,7 +152,6 @@ public class KeystoreController extends WalletFormController implements Initiali
type.setText(getTypeLabel(keystore));
boolean editable = (keystore.getSource() == KeystoreSource.SW_WATCH);
label.setEditable(editable);
fingerprint.setEditable(editable);
derivation.setEditable(editable);
xpub.setEditable(editable);

View file

@ -88,7 +88,7 @@ public class ColdcardMultisigTest extends IoTest {
Assert.assertEquals("m/48'/0'/0'/2'", wallet.getKeystores().get(0).getKeyDerivation().getDerivationPath());
Assert.assertEquals("xpub6EfEGa5isJbQFSswM5Uptw5BSq2Td1ZDJr3QUNUcMySpC7itZ3ccypVHtLPnvMzKQ2qxrAgH49vhVxRcaQLFbixAVRR8RACrYTp88Uv9h8Z", wallet.getKeystores().get(0).getExtendedPublicKey().toString());
Assert.assertEquals("ca9a2b19", wallet.getKeystores().get(2).getKeyDerivation().getMasterFingerprint());
Assert.assertEquals("m/48'/0'/0'/1'", wallet.getKeystores().get(2).getKeyDerivation().getDerivationPath());
Assert.assertEquals("m/47'/0'/0'/1'", wallet.getKeystores().get(2).getKeyDerivation().getDerivationPath());
Assert.assertTrue(wallet.isValid());
}

View file

@ -1,29 +0,0 @@
package com.sparrowwallet.sparrow.io;
import com.sparrowwallet.drongo.crypto.ECIESKeyCrypter;
import com.sparrowwallet.drongo.crypto.ECKey;
import com.sparrowwallet.drongo.policy.PolicyType;
import com.sparrowwallet.drongo.protocol.ScriptType;
import com.sparrowwallet.drongo.wallet.Wallet;
import org.junit.Assert;
import org.junit.Test;
import java.util.zip.InflaterInputStream;
public class ECIESInputStreamTest extends IoTest {
@Test
public void decrypt() throws ImportException {
Electrum electrum = new Electrum();
ECKey decryptionKey = ECIESKeyCrypter.deriveECKey("pass");
Wallet wallet = electrum.importWallet(new InflaterInputStream(new ECIESInputStream(getInputStream("electrum-encrypted"), decryptionKey)), null);
Assert.assertEquals(PolicyType.SINGLE, wallet.getPolicyType());
Assert.assertEquals(ScriptType.P2WPKH, wallet.getScriptType());
Assert.assertEquals(1, wallet.getDefaultPolicy().getNumSignaturesRequired());
Assert.assertEquals("pkh(electrum05aba071)", wallet.getDefaultPolicy().getMiniscript().getScript());
Assert.assertEquals("05aba071", wallet.getKeystores().get(0).getKeyDerivation().getMasterFingerprint());
Assert.assertEquals("m/0'", wallet.getKeystores().get(0).getKeyDerivation().getDerivationPath());
Assert.assertEquals("xpub67vv394epQsLhdjNGx7dfgURicP7XwBMuHPTVAMdXcXhDuC9VP8SqVvh2cYqKWm9xoUd6YynWK8JzRcXpmeuZFRH7i1kt8fR9GXoJSiHk1E", wallet.getKeystores().get(0).getExtendedPublicKey().toString());
Assert.assertTrue(wallet.isValid());
}
}

View file

@ -1,39 +0,0 @@
package com.sparrowwallet.sparrow.io;
import com.sparrowwallet.drongo.crypto.ECIESKeyCrypter;
import com.sparrowwallet.drongo.crypto.ECKey;
import com.sparrowwallet.drongo.policy.PolicyType;
import com.sparrowwallet.drongo.protocol.ScriptType;
import com.sparrowwallet.drongo.wallet.Wallet;
import org.junit.Assert;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;
public class ECIESOutputStreamTest extends IoTest {
@Test
public void encrypt() throws ImportException, ExportException {
Electrum electrum = new Electrum();
ECKey decryptionKey = ECIESKeyCrypter.deriveECKey("pass");
Wallet wallet = electrum.importWallet(new InflaterInputStream(new ECIESInputStream(getInputStream("electrum-encrypted"), decryptionKey)), null);
ECKey encyptionKey = ECKey.fromPublicOnly(decryptionKey);
ByteArrayOutputStream dummyFileOutputStream = new ByteArrayOutputStream();
electrum.exportWallet(wallet, new DeflaterOutputStream(new ECIESOutputStream(dummyFileOutputStream, encyptionKey)));
ByteArrayInputStream dummyFileInputStream = new ByteArrayInputStream(dummyFileOutputStream.toByteArray());
wallet = electrum.importWallet(new InflaterInputStream(new ECIESInputStream(dummyFileInputStream, decryptionKey)), null);
Assert.assertEquals(PolicyType.SINGLE, wallet.getPolicyType());
Assert.assertEquals(ScriptType.P2WPKH, wallet.getScriptType());
Assert.assertEquals(1, wallet.getDefaultPolicy().getNumSignaturesRequired());
Assert.assertEquals("pkh(electrum05aba071)", wallet.getDefaultPolicy().getMiniscript().getScript());
Assert.assertEquals("05aba071", wallet.getKeystores().get(0).getKeyDerivation().getMasterFingerprint());
Assert.assertEquals("m/0'", wallet.getKeystores().get(0).getKeyDerivation().getDerivationPath());
Assert.assertEquals("xpub67vv394epQsLhdjNGx7dfgURicP7XwBMuHPTVAMdXcXhDuC9VP8SqVvh2cYqKWm9xoUd6YynWK8JzRcXpmeuZFRH7i1kt8fR9GXoJSiHk1E", wallet.getKeystores().get(0).getExtendedPublicKey().toString());
Assert.assertTrue(wallet.isValid());
}
}

View file

@ -4,6 +4,7 @@ import com.google.common.io.ByteStreams;
import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.policy.PolicyType;
import com.sparrowwallet.drongo.protocol.ScriptType;
import com.sparrowwallet.drongo.wallet.MnemonicException;
import com.sparrowwallet.drongo.wallet.Wallet;
import org.junit.Assert;
import org.junit.Test;
@ -102,7 +103,7 @@ public class ElectrumTest extends IoTest {
}
@Test
public void testSinglesigSeedExport() throws ImportException, ExportException, IOException {
public void testSinglesigSeedExport() throws ImportException, ExportException, IOException, MnemonicException {
Electrum electrum = new Electrum();
byte[] walletBytes = ByteStreams.toByteArray(getInputStream("electrum-singlesig-seed-wallet.json"));
Wallet wallet = electrum.importWallet(new ByteArrayInputStream(walletBytes), null);

View file

@ -5,6 +5,7 @@ import com.sparrowwallet.drongo.crypto.ECIESKeyCrypter;
import com.sparrowwallet.drongo.crypto.ECKey;
import com.sparrowwallet.drongo.policy.PolicyType;
import com.sparrowwallet.drongo.protocol.ScriptType;
import com.sparrowwallet.drongo.wallet.MnemonicException;
import com.sparrowwallet.drongo.wallet.Wallet;
import org.junit.Assert;
import org.junit.Test;
@ -20,7 +21,7 @@ public class StorageTest extends IoTest {
}
@Test
public void loadSeedWallet() throws IOException {
public void loadSeedWallet() throws IOException, MnemonicException {
ECKey decryptionKey = ECIESKeyCrypter.deriveECKey("pass");
Wallet wallet = Storage.getStorage().loadWallet(getFile("sparrow-single-seed-wallet"), decryptionKey);
@ -34,7 +35,7 @@ public class StorageTest extends IoTest {
Assert.assertEquals("60bcd3a7", wallet.getKeystores().get(0).getKeyDerivation().getMasterFingerprint());
Assert.assertEquals("m/84'/0'/0'", wallet.getKeystores().get(0).getKeyDerivation().getDerivationPath());
Assert.assertEquals("xpub6BrhGFTWPd3DQaGP7p5zTQkE5nqVbaRs23HNae8jAoNJYS2NGa9Sgpeqv1dS5ygwD4sQfwqLCk5qXRK45FTgnqHRcrPnts3Qgh78BZrnoMn", wallet.getKeystores().get(0).getExtendedPublicKey().toString());
Assert.assertEquals("b0e161bff5f589e74b20d9cd260702a6a1e6e1ab3ba4ce764f388dd8f360a1ccdb21099a2f22757ca72f9bde3a34b97a31fb513fb8931c821b0d25798e450b6a57dc106973849ca586b50b2db2840adc", Utils.bytesToHex(wallet.getKeystores().get(0).getSeed().getEncryptedSeedData().getEncryptedBytes()));
Assert.assertEquals("a48767d6b58732a0cad17ed93e23022ec603a177e75461f2aed994713fbbe532b61f6c0758a8aedcf9b2b8102c01c6f3e3e212ca06f13644d4ac8dad66556e164b7eaf79d0b42eadecee8b735e97fc0a", Utils.bytesToHex(wallet.getKeystores().get(0).getSeed().getEncryptedData().getEncryptedBytes()));
Assert.assertNull(wallet.getKeystores().get(0).getSeed().getSeedBytes());
}

View file

@ -9,6 +9,6 @@ Format: P2WSH
# derivation: m/48'/0'/0'/2'
4B569672: xpub6FFEQVG6QR28giDuML74Y7EMPwqEiKftNjScLzg5WKM41bf6LMP2XspjBgNp28tvkNUZdokmTY4TcRbuGZBSMvNoUECrKW1y3TBPeQJVmAg
# derivation: m/48'/0'/0'/1'
# derivation: m/47'/0'/0'/1'
CA9A2B19: xpub6Eb6Z1xtmWRiWKgRpHf6dHiEagGd6FLiBXrnma1nFK4PGRYqSVqVyJaxna5Mb8etSP4ATKVAvKnXG1a9HZauoAawuSDJT5RgH2HqEVHZVHY