From f1510de360880f00edc36fb128f9e5c3c1799e5f Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Mon, 14 Jun 2021 14:54:40 +0200 Subject: [PATCH] update encrypted seeds and private keys when wallet password changes --- drongo | 2 +- .../sparrowwallet/sparrow/AppController.java | 2 +- .../event/KeystoreEncryptionChangedEvent.java | 19 ++++++ .../sparrow/io/db/DbPersistence.java | 20 +++++- .../sparrow/io/db/KeystoreDao.java | 32 ++++++++- .../sparrow/wallet/SettingsWalletForm.java | 68 +++++++++++++++---- .../sparrow/wallet/WalletForm.java | 7 ++ 7 files changed, 132 insertions(+), 18 deletions(-) create mode 100644 src/main/java/com/sparrowwallet/sparrow/event/KeystoreEncryptionChangedEvent.java diff --git a/drongo b/drongo index 485e8c82..f407547c 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit 485e8c825b3beb9dc4bda3f3c2667264915dfd28 +Subproject commit f407547c4766fd81cea29768e275c250b06cf968 diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java index 3ad0027f..981d2a8e 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppController.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java @@ -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 optionalPassword = dlg.showAndWait(); if(optionalPassword.isEmpty()) { return; diff --git a/src/main/java/com/sparrowwallet/sparrow/event/KeystoreEncryptionChangedEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/KeystoreEncryptionChangedEvent.java new file mode 100644 index 00000000..05f98f66 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/event/KeystoreEncryptionChangedEvent.java @@ -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 changedKeystores; + + public KeystoreEncryptionChangedEvent(Wallet wallet, Wallet pastWallet, String walletId, List changedKeystores) { + super(wallet, pastWallet, walletId); + this.changedKeystores = changedKeystores; + } + + public List getChangedKeystores() { + return changedKeystores; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/io/db/DbPersistence.java b/src/main/java/com/sparrowwallet/sparrow/io/db/DbPersistence.java index caf48994..a030f870 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/db/DbPersistence.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/db/DbPersistence.java @@ -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 historyNodes = new ArrayList<>(); @@ -594,6 +608,7 @@ public class DbPersistence implements Persistence { public final List labelEntries = new ArrayList<>(); public final List utxoStatuses = new ArrayList<>(); public final List labelKeystores = new ArrayList<>(); + public final List 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()); } } } diff --git a/src/main/java/com/sparrowwallet/sparrow/io/db/KeystoreDao.java b/src/main/java/com/sparrowwallet/sparrow/io/db/KeystoreDao.java index 53435371..2ac00997 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/db/KeystoreDao.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/db/KeystoreDao.java @@ -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()); + } + } + } } diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/SettingsWalletForm.java b/src/main/java/com/sparrowwallet/sparrow/wallet/SettingsWalletForm.java index f89b5fcf..49e0e571 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/SettingsWalletForm.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/SettingsWalletForm.java @@ -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 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 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 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 getLabelChangedKeystores(Wallet original, Wallet changed) { + List 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 getEncryptionChangedKeystores(Wallet original, Wallet changed) { + List 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; + } } diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java index 785e6b4e..304e07e7 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java @@ -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())) {