update encrypted seeds and private keys when wallet password changes

This commit is contained in:
Craig Raw 2021-06-14 14:54:40 +02:00
parent cfac2768ae
commit f1510de360
7 changed files with 132 additions and 18 deletions

2
drongo

@ -1 +1 @@
Subproject commit 485e8c825b3beb9dc4bda3f3c2667264915dfd28
Subproject commit f407547c4766fd81cea29768e275c250b06cf968

View file

@ -766,7 +766,7 @@ public class AppController implements Initializable {
WalletBackupAndKey walletBackupAndKey = storage.loadUnencryptedWallet();
openWallet(storage, walletBackupAndKey, this, forceSameWindow);
} else {
WalletPasswordDialog dlg = new WalletPasswordDialog(file.getName(), WalletPasswordDialog.PasswordRequirement.LOAD);
WalletPasswordDialog dlg = new WalletPasswordDialog(storage.getWalletName(null), WalletPasswordDialog.PasswordRequirement.LOAD);
Optional<SecureString> optionalPassword = dlg.showAndWait();
if(optionalPassword.isEmpty()) {
return;

View file

@ -0,0 +1,19 @@
package com.sparrowwallet.sparrow.event;
import com.sparrowwallet.drongo.wallet.Keystore;
import com.sparrowwallet.drongo.wallet.Wallet;
import java.util.List;
public class KeystoreEncryptionChangedEvent extends WalletSettingsChangedEvent {
private List<Keystore> changedKeystores;
public KeystoreEncryptionChangedEvent(Wallet wallet, Wallet pastWallet, String walletId, List<Keystore> changedKeystores) {
super(wallet, pastWallet, walletId);
this.changedKeystores = changedKeystores;
}
public List<Keystore> getChangedKeystores() {
return changedKeystores;
}
}

View file

@ -269,6 +269,13 @@ public class DbPersistence implements Persistence {
}
}
if(!dirtyPersistables.encryptionKeystores.isEmpty()) {
KeystoreDao keystoreDao = handle.attach(KeystoreDao.class);
for(Keystore keystore : dirtyPersistables.encryptionKeystores) {
keystoreDao.updateKeystoreEncryption(keystore);
}
}
dirtyPersistablesMap.remove(wallet);
} finally {
walletDao.setSchema(DEFAULT_SCHEMA);
@ -346,7 +353,7 @@ public class DbPersistence implements Persistence {
String newPassword = getFilePassword(encryptionPubKey);
String currentPassword = getDatasourcePassword();
//The password only needs to be changed if the datasource is null - either we have loaded the wallet from a datasource, or it is a new wallet and the datasource is still to be created
//The password only needs to be changed if the datasource is not null - if we have not loaded the wallet from a datasource, it is a new wallet and the database is still to be created
if(dataSource != null && !Objects.equals(currentPassword, newPassword)) {
if(!dataSource.isClosed()) {
dataSource.close();
@ -587,6 +594,13 @@ public class DbPersistence implements Persistence {
}
}
@Subscribe
public void keystoreEncryptionChanged(KeystoreEncryptionChangedEvent event) {
if(persistsFor(event.getWallet())) {
dirtyPersistablesMap.computeIfAbsent(event.getWallet(), key -> new DirtyPersistables()).encryptionKeystores.addAll(event.getChangedKeystores());
}
}
private static class DirtyPersistables {
public boolean clearHistory;
public final List<WalletNode> historyNodes = new ArrayList<>();
@ -594,6 +608,7 @@ public class DbPersistence implements Persistence {
public final List<Entry> labelEntries = new ArrayList<>();
public final List<BlockTransactionHashIndex> utxoStatuses = new ArrayList<>();
public final List<Keystore> labelKeystores = new ArrayList<>();
public final List<Keystore> encryptionKeystores = new ArrayList<>();
public String toString() {
return "Dirty Persistables" +
@ -604,7 +619,8 @@ public class DbPersistence implements Persistence {
"\nAddress labels:" + labelEntries.stream().filter(entry -> entry instanceof NodeEntry).map(entry -> ((NodeEntry)entry).getNode().toString() + " " + entry.getLabel()).collect(Collectors.toList()) +
"\nUTXO labels:" + labelEntries.stream().filter(entry -> entry instanceof HashIndexEntry).map(entry -> ((HashIndexEntry)entry).getHashIndex().toString()).collect(Collectors.toList()) +
"\nUTXO statuses:" + utxoStatuses +
"\nKeystore labels:" + labelKeystores.stream().map(Keystore::getLabel).collect(Collectors.toList());
"\nKeystore labels:" + labelKeystores.stream().map(Keystore::getLabel).collect(Collectors.toList()) +
"\nKeystore encryptions:" + encryptionKeystores.stream().map(Keystore::getLabel).collect(Collectors.toList());
}
}
}

View file

@ -28,17 +28,23 @@ public interface KeystoreDao {
@GetGeneratedKeys("id")
long insertMasterPrivateExtendedKey(byte[] privateKey, byte[] chainCode, byte[] initialisationVector, byte[] encryptedBytes, byte[] keySalt, Integer deriver, Integer crypter, long creationTimeSeconds);
@SqlUpdate("update masterPrivateExtendedKey set privateKey = ?, chainCode = ?, initialisationVector = ?, encryptedBytes = ?, keySalt = ?, deriver = ?, crypter = ?, creationTimeSeconds = ? where id = ?")
void updateMasterPrivateExtendedKey(byte[] privateKey, byte[] chainCode, byte[] initialisationVector, byte[] encryptedBytes, byte[] keySalt, Integer deriver, Integer crypter, long creationTimeSeconds, long id);
@SqlUpdate("insert into seed (type, mnemonicString, initialisationVector, encryptedBytes, keySalt, deriver, crypter, needsPassphrase, creationTimeSeconds) values (?, ?, ?, ?, ?, ?, ?, ?, ?)")
@GetGeneratedKeys("id")
long insertSeed(int type, String mnemonicString, byte[] initialisationVector, byte[] encryptedBytes, byte[] keySalt, Integer deriver, Integer crypter, boolean needsPassphrase, long creationTimeSeconds);
@SqlUpdate("update seed set type = ?, mnemonicString = ?, initialisationVector = ?, encryptedBytes = ?, keySalt = ?, deriver = ?, crypter = ?, needsPassphrase = ?, creationTimeSeconds = ? where id = ?")
void updateSeed(int type, String mnemonicString, byte[] initialisationVector, byte[] encryptedBytes, byte[] keySalt, Integer deriver, Integer crypter, boolean needsPassphrase, long creationTimeSeconds, long id);
@SqlUpdate("update keystore set label = ? where id = ?")
void updateLabel(String label, long id);
default void addKeystores(Wallet wallet) {
for(int i = 0; i < wallet.getKeystores().size(); i++) {
Keystore keystore = wallet.getKeystores().get(i);
if(keystore.getMasterPrivateExtendedKey() != null) {
if(keystore.hasMasterPrivateExtendedKey()) {
MasterPrivateExtendedKey mpek = keystore.getMasterPrivateExtendedKey();
if(mpek.isEncrypted()) {
EncryptedData data = mpek.getEncryptedData();
@ -50,7 +56,7 @@ public interface KeystoreDao {
}
}
if(keystore.getSeed() != null) {
if(keystore.hasSeed()) {
DeterministicSeed seed = keystore.getSeed();
if(seed.isEncrypted()) {
EncryptedData data = seed.getEncryptedData();
@ -71,4 +77,26 @@ public interface KeystoreDao {
keystore.setId(id);
}
}
default void updateKeystoreEncryption(Keystore keystore) {
if(keystore.hasMasterPrivateExtendedKey()) {
MasterPrivateExtendedKey mpek = keystore.getMasterPrivateExtendedKey();
if(mpek.isEncrypted()) {
EncryptedData data = mpek.getEncryptedData();
updateMasterPrivateExtendedKey(null, null, data.getInitialisationVector(), data.getEncryptedBytes(), data.getKeySalt(), data.getEncryptionType().getDeriver().ordinal(), data.getEncryptionType().getCrypter().ordinal(), mpek.getCreationTimeSeconds(), mpek.getId());
} else {
updateMasterPrivateExtendedKey(mpek.getPrivateKey().getPrivKeyBytes(), mpek.getPrivateKey().getChainCode(), null, null, null, null, null, mpek.getCreationTimeSeconds(), mpek.getId());
}
}
if(keystore.hasSeed()) {
DeterministicSeed seed = keystore.getSeed();
if(seed.isEncrypted()) {
EncryptedData data = seed.getEncryptedData();
updateSeed(seed.getType().ordinal(), null, data.getInitialisationVector(), data.getEncryptedBytes(), data.getKeySalt(), data.getEncryptionType().getDeriver().ordinal(), data.getEncryptionType().getCrypter().ordinal(), seed.needsPassphrase(), seed.getCreationTimeSeconds(), seed.getId());
} else {
updateSeed(seed.getType().ordinal(), seed.getMnemonicString().asString(), null, null, null, null, null, seed.needsPassphrase(), seed.getCreationTimeSeconds(), seed.getId());
}
}
}
}

View file

@ -1,10 +1,13 @@
package com.sparrowwallet.sparrow.wallet;
import com.sparrowwallet.drongo.policy.Policy;
import com.sparrowwallet.drongo.wallet.DeterministicSeed;
import com.sparrowwallet.drongo.wallet.Keystore;
import com.sparrowwallet.drongo.wallet.MasterPrivateExtendedKey;
import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.AppServices;
import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.event.KeystoreEncryptionChangedEvent;
import com.sparrowwallet.sparrow.event.KeystoreLabelsChangedEvent;
import com.sparrowwallet.sparrow.event.WalletAddressesChangedEvent;
import com.sparrowwallet.sparrow.event.WalletPasswordChangedEvent;
@ -82,20 +85,18 @@ public class SettingsWalletForm extends WalletForm {
EventManager.get().post(new WalletAddressesChangedEvent(wallet, addressChange ? null : pastWallet, getWalletId()));
} else {
List<Keystore> changedKeystores = new ArrayList<>();
for(int i = 0; i < wallet.getKeystores().size(); i++) {
Keystore keystore = wallet.getKeystores().get(i);
Keystore keystoreCopy = walletCopy.getKeystores().get(i);
if(!Objects.equals(keystore.getLabel(), keystoreCopy.getLabel())) {
keystore.setLabel(keystoreCopy.getLabel());
changedKeystores.add(keystore);
}
List<Keystore> labelChangedKeystores = getLabelChangedKeystores(wallet, walletCopy);
if(!labelChangedKeystores.isEmpty()) {
EventManager.get().post(new KeystoreLabelsChangedEvent(wallet, pastWallet, getWalletId(), labelChangedKeystores));
}
if(!changedKeystores.isEmpty()) {
EventManager.get().post(new KeystoreLabelsChangedEvent(wallet, pastWallet, getWalletId(), changedKeystores));
} else {
//Can only be a password update at this point
List<Keystore> encryptionChangedKeystores = getEncryptionChangedKeystores(wallet, walletCopy);
if(!encryptionChangedKeystores.isEmpty()) {
EventManager.get().post(new KeystoreEncryptionChangedEvent(wallet, pastWallet, getWalletId(), encryptionChangedKeystores));
}
if(labelChangedKeystores.isEmpty() && encryptionChangedKeystores.isEmpty()) {
//Can only be a wallet password change on a wallet without private keys
EventManager.get().post(new WalletPasswordChangedEvent(wallet, pastWallet, getWalletId()));
}
}
@ -186,4 +187,47 @@ public class SettingsWalletForm extends WalletForm {
private Integer getNumSignaturesRequired(Policy policy) {
return policy == null ? null : policy.getNumSignaturesRequired();
}
private List<Keystore> getLabelChangedKeystores(Wallet original, Wallet changed) {
List<Keystore> changedKeystores = new ArrayList<>();
for(int i = 0; i < original.getKeystores().size(); i++) {
Keystore originalKeystore = original.getKeystores().get(i);
Keystore changedKeystore = changed.getKeystores().get(i);
if(!Objects.equals(originalKeystore.getLabel(), changedKeystore.getLabel())) {
originalKeystore.setLabel(changedKeystore.getLabel());
changedKeystores.add(originalKeystore);
}
}
return changedKeystores;
}
private List<Keystore> getEncryptionChangedKeystores(Wallet original, Wallet changed) {
List<Keystore> changedKeystores = new ArrayList<>();
for(int i = 0; i < original.getKeystores().size(); i++) {
Keystore originalKeystore = original.getKeystores().get(i);
Keystore changedKeystore = changed.getKeystores().get(i);
if(originalKeystore.hasSeed() && changedKeystore.hasSeed()) {
if(!Objects.equals(originalKeystore.getSeed().getEncryptedData(), changedKeystore.getSeed().getEncryptedData())) {
DeterministicSeed changedSeed = changedKeystore.getSeed().copy();
changedSeed.setId(originalKeystore.getSeed().getId());
originalKeystore.setSeed(changedSeed);
changedKeystores.add(originalKeystore);
}
}
if(originalKeystore.hasMasterPrivateExtendedKey() && changedKeystore.hasMasterPrivateExtendedKey()) {
if(!Objects.equals(originalKeystore.getMasterPrivateExtendedKey().getEncryptedData(), changedKeystore.getMasterPrivateExtendedKey().getEncryptedData())) {
MasterPrivateExtendedKey changedMpek = changedKeystore.getMasterPrivateExtendedKey().copy();
changedMpek.setId(originalKeystore.getMasterPrivateExtendedKey().getId());
originalKeystore.setMasterPrivateExtendedKey(changedMpek);
changedKeystores.add(originalKeystore);
}
}
}
return changedKeystores;
}
}

View file

@ -330,6 +330,13 @@ public class WalletForm {
}
}
@Subscribe
public void keystoreEncryptionChanged(KeystoreEncryptionChangedEvent event) {
if(event.getWalletId().equals(getWalletId())) {
Platform.runLater(() -> EventManager.get().post(new WalletDataChangedEvent(wallet)));
}
}
@Subscribe
public void walletPasswordChanged(WalletPasswordChangedEvent event) {
if(event.getWalletId().equals(getWalletId())) {