diff --git a/components/LineEdit.qml b/components/LineEdit.qml index 0274103d0f..0cee70a338 100644 --- a/components/LineEdit.qml +++ b/components/LineEdit.qml @@ -106,6 +106,7 @@ ColumnLayout { property alias labelHorizontalAlignment: inputLabel.horizontalAlignment property bool showingHeader: inputLabel.text !== "" || copyButton property int inputHeight: 39 + property bool isSecret: false signal labelLinkActivated(); // input label, rich text signal signal editingFinished(); @@ -228,6 +229,26 @@ ColumnLayout { item.KeyNavigation.tab.forceActiveFocus() } } + Keys.onPressed: { + if (isSecret) { + if (event.key === Qt.Key_Left || event.key === Qt.Key_Home) { + event.accepted = true; + } + if (event.key === Qt.Key_Backspace || event.key === Qt.Key_Delete) { + memwipe.wipeQString(item.text); + clear(); + event.accepted = true; + } + } + } + MouseArea { + anchors.fill: parent + onClicked: { + if (isSecret) { + item.forceActiveFocus(); + } + } + } Layout.fillWidth: true Layout.preferredHeight: inputHeight diff --git a/components/PasswordDialog.qml b/components/PasswordDialog.qml index 4de44ae5b0..bf36d28de8 100644 --- a/components/PasswordDialog.qml +++ b/components/PasswordDialog.qml @@ -225,6 +225,7 @@ Item { Keys.onEnterPressed: root.onOk() Keys.onReturnPressed: root.onOk() Keys.onEscapePressed: root.onCancel() + isSecret: true } // padding @@ -261,6 +262,7 @@ Item { Keys.onEnterPressed: root.onOk() Keys.onReturnPressed: root.onOk() Keys.onEscapePressed: root.onCancel() + isSecret: true } // padding diff --git a/js/Utils.js b/js/Utils.js index cf8015af5f..05bf1f0bab 100644 --- a/js/Utils.js +++ b/js/Utils.js @@ -31,7 +31,7 @@ function isNumeric(n) { function showSeedPage() { // Shows `Settings->Seed & keys`. Prompts a password dialog. passwordDialog.onAcceptedCallback = function() { - if(walletPassword === passwordDialog.password){ + if(currentWallet.verifyPassword(passwordDialog.password, persistentSettings.kdfRounds)){ if(currentWallet.seedLanguage == "") { console.log("No seed language set. Using English as default"); currentWallet.setSeedLanguage("English"); @@ -43,6 +43,7 @@ function showSeedPage() { } } passwordDialog.onRejectedCallback = function() { + memwipe.wipeQString(passwordDialog.password); leftPanel.selectItem(middlePanel.state); } passwordDialog.open(); diff --git a/main.qml b/main.qml index ffbf014ed4..345a14862e 100644 --- a/main.qml +++ b/main.qml @@ -43,6 +43,7 @@ import moneroComponents.PendingTransaction 1.0 import moneroComponents.NetworkType 1.0 import moneroComponents.Settings 1.0 import moneroComponents.P2PoolManager 1.0 +import moneroComponents.Memwipe 1.0 import "components" import "components" as MoneroComponents @@ -71,7 +72,6 @@ ApplicationWindow { property var currentWallet; property bool disconnected: currentWallet ? currentWallet.disconnected : false property var transaction; - property var walletPassword property int restoreHeight:0 property bool daemonSynced: false property bool walletSynced: false @@ -133,9 +133,12 @@ ApplicationWindow { } function lock() { - passwordDialog.onRejectedCallback = function() { appWindow.showWizard(); } + passwordDialog.onRejectedCallback = function() { + memwipe.wipeQString(passwordDialog.password); + appWindow.showWizard(); + } passwordDialog.onAcceptedCallback = function() { - if(walletPassword === passwordDialog.password) + if (currentWallet.verifyPassword(passwordDialog.password, persistentSettings.kdfRounds)) passwordDialog.close(); else passwordDialog.showError(qsTr("Wrong password") + translationManager.emptyString); @@ -218,16 +221,19 @@ ApplicationWindow { function openWallet(prevState) { passwordDialog.onAcceptedCallback = function() { - walletPassword = passwordDialog.password; initialize(); } passwordDialog.onRejectedCallback = function() { + memwipe.wipeQString(passwordDialog.password); if (prevState) { appWindow.viewState = prevState; } if (wizard.wizardState == "wizardOpenWallet1") { wizard.wizardStateView.wizardOpenWallet1View.pageRoot.forceActiveFocus(); } + else if (wizard.wizardState == "wizardCreateWallet5") { + showWizard() + } }; passwordDialog.open(usefulName(persistentSettings.wallet_path)); } @@ -264,13 +270,12 @@ ApplicationWindow { var wallet_path = persistentSettings.wallet_path; if(isIOS) wallet_path = appWindow.accountsDir + wallet_path; - // console.log("opening wallet at: ", wallet_path, "with password: ", appWindow.walletPassword); console.log("opening wallet at: ", wallet_path, ", network type: ", persistentSettings.nettype == NetworkType.MAINNET ? "mainnet" : persistentSettings.nettype == NetworkType.TESTNET ? "testnet" : "stagenet"); this.onWalletOpening(); walletManager.openWalletAsync( wallet_path, - walletPassword, + passwordDialog.password, persistentSettings.nettype, persistentSettings.kdfRounds); } @@ -375,7 +380,9 @@ ApplicationWindow { persistentSettings.getWalletProxyAddress()); // save wallet keys in case wallet settings have been changed in the init - currentWallet.setPassword(walletPassword); + currentWallet.setPassword(passwordDialog.password, passwordDialog.password); + memwipe.wipeQString(passwordDialog.password); + currentWallet.storeAsync(function(){}); } function isTrustedDaemon() { @@ -529,7 +536,6 @@ ApplicationWindow { case "basic_string::_M_replace_aux": case "std::bad_alloc": walletManager.clearWalletCache(wallet.path); - walletPassword = passwordDialog.password; appWindow.initialize(); console.error("Repairing wallet cache with error: ", wallet.errorString); appWindow.showStatusMessage(qsTr("Repairing incompatible wallet cache. Resyncing wallet."),6); @@ -1592,13 +1598,13 @@ ApplicationWindow { } close(); passwordDialog.onAcceptedCallback = function() { - if(walletPassword === passwordDialog.password){ + if (currentWallet.verifyPassword(passwordDialog.password, persistentSettings.kdfRounds)) { handleAccepted() } else { passwordDialog.showError(qsTr("Wrong password") + translationManager.emptyString); } } - passwordDialog.onRejectedCallback = null; + passwordDialog.onRejectedCallback = function() { memwipe.wipeQString(passwordDialog.password); } if(!persistentSettings.askPasswordBeforeSending) { handleAccepted() } else { @@ -1706,8 +1712,7 @@ ApplicationWindow { onRejectedCallback(); } onAcceptedNewPassword: { - if (currentWallet.setPassword(passwordDialog.password)) { - appWindow.walletPassword = passwordDialog.password; + if (currentWallet.setPassword(/* old_password */ "", passwordDialog.password)) { informationPopup.title = qsTr("Information") + translationManager.emptyString; informationPopup.text = qsTr("Password changed successfully") + translationManager.emptyString; informationPopup.icon = StandardIcon.Information; @@ -1718,6 +1723,8 @@ ApplicationWindow { } informationPopup.onCloseCallback = null; informationPopup.open(); + memwipe.wipeQString(passwordDialog.password); + memwipe.wipeQString(passwordDialog.passwordConfirm); } onRejectedNewPassword: {} Keys.enabled: !passwordDialog.visible && informationPopup.visible @@ -2259,7 +2266,7 @@ ApplicationWindow { if(inactivity < (persistentSettings.lockOnUserInActivityInterval * 60)) return; passwordDialog.onAcceptedCallback = function() { - if(walletPassword === passwordDialog.password){ + if (currentWallet.verifyPassword(passwordDialog.password, persistentSettings.kdfRounds)) { passwordDialog.close(); if (inputDialogVisible) inputDialog.open(inputDialog.inputText) if (successfulTxPopupVisible) successfulTxPopup.open(successfulTxPopup.transactionID) @@ -2269,7 +2276,10 @@ ApplicationWindow { } } - passwordDialog.onRejectedCallback = function() { appWindow.showWizard(); } + passwordDialog.onRejectedCallback = function() { + memwipe.wipeQString(passwordDialog.password); + appWindow.showWizard(); + } if (inputDialogVisible) inputDialog.close() remoteNodeDialog.close(); informationPopup.close() @@ -2411,4 +2421,8 @@ ApplicationWindow { id: walletManager proxyAddress: persistentSettings.getProxyAddress() } + + Memwipe { + id: memwipe + } } diff --git a/monero b/monero index f1311d4237..950ae3b657 160000 --- a/monero +++ b/monero @@ -1 +1 @@ -Subproject commit f1311d4237404ab7da76241dbf10e92a65132cc4 +Subproject commit 950ae3b657b5a6819a4961334e0e86b59de45107 diff --git a/pages/settings/SettingsInfo.qml b/pages/settings/SettingsInfo.qml index ad152d63a9..48dfdcf21f 100644 --- a/pages/settings/SettingsInfo.qml +++ b/pages/settings/SettingsInfo.qml @@ -195,10 +195,6 @@ Rectangle { } if (!isNaN(_restoreHeight)) { if(_restoreHeight >= 0) { - currentWallet.walletCreationHeight = _restoreHeight - // Restore height is saved in .keys file. Set password to trigger rewrite. - currentWallet.setPassword(appWindow.walletPassword) - // Show confirmation dialog confirmationDialog.title = qsTr("Rescan wallet cache") + translationManager.emptyString; confirmationDialog.text = qsTr("Are you sure you want to rebuild the wallet cache?\n" @@ -210,13 +206,30 @@ Rectangle { ); confirmationDialog.icon = StandardIcon.Question confirmationDialog.onAcceptedCallback = function() { - appWindow.closeWallet(function() { - walletManager.clearWalletCache(persistentSettings.wallet_path); - walletManager.openWalletAsync(persistentSettings.wallet_path, appWindow.walletPassword, - persistentSettings.nettype, persistentSettings.kdfRounds); - }); - } + passwordDialog.onAcceptedCallback = function() { + if(currentWallet.verifyPassword(passwordDialog.password, persistentSettings.kdfRounds, /* do_wipe */ false)){ + currentWallet.walletCreationHeight = _restoreHeight + // Restore height is saved in .keys file. Set password to trigger rewrite. + currentWallet.setPassword(passwordDialog.password, passwordDialog.password); + + appWindow.closeWallet(function() { + walletManager.clearWalletCache(persistentSettings.wallet_path); + walletManager.openWalletAsync(persistentSettings.wallet_path, passwordDialog.password, + persistentSettings.nettype, persistentSettings.kdfRounds); + }); + + passwordDialog.close(); + } else { + memwipe.wipeQString(passwordDialog.password); + passwordDialog.showError(qsTr("Wrong password") + translationManager.emptyString); + } + } + passwordDialog.onRejectedCallback = function() { + memwipe.wipeQString(passwordDialog.password); + }; + passwordDialog.open(usefulName(persistentSettings.wallet_path)); + } confirmationDialog.onRejectedCallback = null; confirmationDialog.open() return; diff --git a/pages/settings/SettingsLayout.qml b/pages/settings/SettingsLayout.qml index c2dfc7e662..4fd2c9f11d 100644 --- a/pages/settings/SettingsLayout.qml +++ b/pages/settings/SettingsLayout.qml @@ -99,13 +99,13 @@ Rectangle { onClicked: { if (persistentSettings.askPasswordBeforeSending) { passwordDialog.onAcceptedCallback = function() { - if (appWindow.walletPassword === passwordDialog.password){ + if (currentWallet.verifyPassword(passwordDialog.password, persistentSettings.kdfRounds)) { persistentSettings.askPasswordBeforeSending = false; } else { passwordDialog.showError(qsTr("Wrong password")); } } - passwordDialog.onRejectedCallback = null; + passwordDialog.onRejectedCallback = function() { memwipe.wipeQString(passwordDialog.password); } passwordDialog.open() } else { persistentSettings.askPasswordBeforeSending = true; diff --git a/pages/settings/SettingsWallet.qml b/pages/settings/SettingsWallet.qml index f43d64e2ec..7dec8ef22f 100644 --- a/pages/settings/SettingsWallet.qml +++ b/pages/settings/SettingsWallet.qml @@ -74,17 +74,22 @@ Rectangle { onClicked: { var newPath = currentWallet.path + "_viewonly"; - if (currentWallet.createViewOnly(newPath, appWindow.walletPassword)) { - console.log("view only wallet created in " + newPath); - informationPopup.title = qsTr("Success") + translationManager.emptyString; - informationPopup.text = qsTr('The view only wallet has been created with the same password as the current wallet. You can open it by closing this current wallet, clicking the "Open wallet from file" option, and selecting the view wallet in: \n%1\nYou can change the password in the wallet settings.').arg(newPath); - informationPopup.open() - informationPopup.onCloseCallback = null - } else { - informationPopup.title = qsTr("Error") + translationManager.emptyString; - informationPopup.text = currentWallet.errorString; - informationPopup.open() + passwordDialog.onAcceptedCallback = function() { + if (currentWallet.createViewOnly(newPath, passwordDialog.password)) { + console.log("view only wallet created in " + newPath); + informationPopup.title = qsTr("Success") + translationManager.emptyString; + informationPopup.text = qsTr('The view only wallet has been created. You can open it by closing this current wallet, clicking the "Open wallet from file" option, and selecting the view wallet in: \n%1').arg(newPath); + informationPopup.open() + informationPopup.onCloseCallback = null + } else { + informationPopup.title = qsTr("Error") + translationManager.emptyString; + informationPopup.text = currentWallet.errorString; + informationPopup.open() + } + memwipe.wipeQString(passwordDialog.password); } + passwordDialog.onRejectedCallback = function() { memwipe.wipeQString(passwordDialog.password); } + passwordDialog.open() } } @@ -165,18 +170,13 @@ Rectangle { onClicked: { passwordDialog.onAcceptedCallback = function() { - if(appWindow.walletPassword === passwordDialog.password){ + if (currentWallet.verifyPassword(passwordDialog.password, persistentSettings.kdfRounds)) { passwordDialog.openNewPasswordDialog() } else { - informationPopup.title = qsTr("Error") + translationManager.emptyString; - informationPopup.text = qsTr("Wrong password") + translationManager.emptyString; - informationPopup.open() - informationPopup.onCloseCallback = function() { - passwordDialog.open() - } + passwordDialog.showError(qsTr("Wrong password") + translationManager.emptyString); } } - passwordDialog.onRejectedCallback = null; + passwordDialog.onRejectedCallback = function() { memwipe.wipeQString(passwordDialog.password); } passwordDialog.open() } } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b5718df3a2..32473185a3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -21,6 +21,7 @@ file(GLOB SOURCE_FILES "libwalletqt/AddressBook.cpp" "libwalletqt/Subaddress.cpp" "libwalletqt/SubaddressAccount.cpp" + "libwalletqt/Memwipe.cpp" "libwalletqt/UnsignedTransaction.cpp" "libwalletqt/WalletManager.h" "libwalletqt/Wallet.h" @@ -33,6 +34,7 @@ file(GLOB SOURCE_FILES "libwalletqt/AddressBook.h" "libwalletqt/Subaddress.h" "libwalletqt/SubaddressAccount.h" + "libwalletqt/Memwipe.h" "libwalletqt/UnsignedTransaction.h" "daemon/*.h" "daemon/*.cpp" diff --git a/src/libwalletqt/Memwipe.cpp b/src/libwalletqt/Memwipe.cpp new file mode 100644 index 0000000000..9c7444cabf --- /dev/null +++ b/src/libwalletqt/Memwipe.cpp @@ -0,0 +1,35 @@ +// Copyright (c) 2014-2025, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "Memwipe.h" +#include "memwipe.h" + +void Memwipe::wipeQString(const QString &q_str) +{ + memwipe(const_cast(q_str.constData()), q_str.length()*2); +} diff --git a/src/libwalletqt/Memwipe.h b/src/libwalletqt/Memwipe.h new file mode 100644 index 0000000000..3e0a425d84 --- /dev/null +++ b/src/libwalletqt/Memwipe.h @@ -0,0 +1,42 @@ +// Copyright (c) 2014-2025, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef QMEMWIPE_H +#define QMEMWIPE_H + +#include +#include + +class Memwipe : public QObject +{ + Q_OBJECT +public: + Q_INVOKABLE void wipeQString(const QString &q_str); +}; + +#endif // QMEMWIPE_H diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp index cec4ff63c8..0ad95a9e23 100644 --- a/src/libwalletqt/Wallet.cpp +++ b/src/libwalletqt/Wallet.cpp @@ -57,7 +57,10 @@ #include #include +#include "memwipe.h" #include "qt/ScopeGuard.h" +#include "qt/utils.h" +#include "wipeable_string.h" namespace { static const int DAEMON_BLOCKCHAIN_HEIGHT_CACHE_TTL_SECONDS = 5; @@ -209,9 +212,12 @@ QString Wallet::errorString() const return QString::fromStdString(m_walletImpl->errorString()); } -bool Wallet::setPassword(const QString &password) +bool Wallet::setPassword(const QString &old_password, const QString &new_password) { - return m_walletImpl->setPassword(password.toStdString()); + return m_walletImpl->setPassword(old_password.toLocal8Bit().constData(), + old_password.length(), + new_password.toLocal8Bit().constData(), + new_password.length()); } QString Wallet::address(quint32 accountIndex, quint32 addressIndex) const @@ -230,7 +236,9 @@ void Wallet::storeAsync(const QJSValue &callback, const QString &path /* = "" */ [this, path] { QMutexLocker locker(&m_asyncMutex); - return QJSValueList({m_walletImpl->store(path.toStdString())}); + // Empty `password` is provided for creating/restoring a wallet where the password is not set yet + // and it will be ignored, if empty `path` is given. (We only call this method with non-empty `path` once in WizardController.qml `writeWallet()`) + return QJSValueList({m_walletImpl->store(path.toStdString(), /* password = */ Monero::optional>())}); }, callback); if (!future.first) @@ -1087,6 +1095,14 @@ void Wallet::onWalletPassphraseNeeded(bool on_device) emit this->walletPassphraseNeeded(on_device); } +bool Wallet::verifyPassword(const QString &password, quint64 kdf_rounds, bool do_wipe /* = true */) +{ + bool r = m_walletImpl->verifyPassword(password.toLocal8Bit().constData(), password.length(), kdf_rounds); + if (do_wipe) + memwipe(const_cast(password.constData()), password.length()*2); + return r; +} + void Wallet::onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort) { if (m_walletListener != nullptr) diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h index 398d19c622..2100399bb4 100644 --- a/src/libwalletqt/Wallet.h +++ b/src/libwalletqt/Wallet.h @@ -139,7 +139,7 @@ class Wallet : public QObject, public PassprasePrompter QString errorString() const; //! changes the password using existing parameters (path, seed, seed lang) - Q_INVOKABLE bool setPassword(const QString &password); + Q_INVOKABLE bool setPassword(const QString &old_password, const QString &new_password); //! returns wallet's public address Q_INVOKABLE QString address(quint32 accountIndex, quint32 addressIndex) const; @@ -355,6 +355,8 @@ class Wallet : public QObject, public PassprasePrompter Q_INVOKABLE void onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort=false); virtual void onWalletPassphraseNeeded(bool on_device) override; + Q_INVOKABLE bool verifyPassword(const QString &password, quint64 kdf_rounds, bool do_wipe = true); + // TODO: setListenter() when it implemented in API signals: // emitted on every event happened with wallet diff --git a/src/libwalletqt/WalletListenerImpl.cpp b/src/libwalletqt/WalletListenerImpl.cpp index d2ccc24442..3a85943cee 100644 --- a/src/libwalletqt/WalletListenerImpl.cpp +++ b/src/libwalletqt/WalletListenerImpl.cpp @@ -95,3 +95,19 @@ Monero::optional WalletListenerImpl::onDevicePassphraseRequest(bool qDebug() << __FUNCTION__; return m_phelper.onDevicePassphraseRequest(on_device); } + +void WalletListenerImpl::onReorg(std::uint64_t height, std::uint64_t blocks_detached, std::size_t transfers_detached) +{ + qDebug() << __FUNCTION__; +} + +Monero::optional WalletListenerImpl::onGetPassword(const char *reason) +{ + qDebug() << __FUNCTION__; + return Monero::optional(); +} + +void WalletListenerImpl::onPoolTxRemoved(const std::string &txid) +{ + qDebug() << __FUNCTION__; +} diff --git a/src/libwalletqt/WalletListenerImpl.h b/src/libwalletqt/WalletListenerImpl.h index 46f38fdf32..dbb89fd646 100644 --- a/src/libwalletqt/WalletListenerImpl.h +++ b/src/libwalletqt/WalletListenerImpl.h @@ -60,6 +60,12 @@ class WalletListenerImpl : public Monero::WalletListener, public PassphraseRecei virtual Monero::optional onDevicePassphraseRequest(bool & on_device) override; + void onReorg(std::uint64_t height, std::uint64_t blocks_detached, std::size_t transfers_detached) override; + + Monero::optional onGetPassword(const char *reason) override; + + void onPoolTxRemoved(const std::string &txid) override; + private: Wallet * m_wallet; PassphraseHelper m_phelper; diff --git a/src/libwalletqt/WalletManager.cpp b/src/libwalletqt/WalletManager.cpp index ae03357b5e..acdb6df184 100644 --- a/src/libwalletqt/WalletManager.cpp +++ b/src/libwalletqt/WalletManager.cpp @@ -29,6 +29,7 @@ #include "WalletManager.h" #include "Wallet.h" #include "wallet/api/wallet2_api.h" +#include "wipeable_string.h" #include "zxcvbn-c/zxcvbn.h" #include "QRCodeImageProvider.h" #include @@ -57,6 +58,9 @@ class WalletPassphraseListenerImpl : public Monero::WalletListener, public Pass virtual void newBlock(uint64_t height) override { (void) height; }; virtual void updated() override {}; virtual void refreshed() override {}; + void onReorg(std::uint64_t height, std::uint64_t blocks_detached, std::size_t transfers_detached) override + { (void) height; (void) blocks_detached; (void) transfers_detached; } + void onPoolTxRemoved(const std::string &txid) override { (void) txid; } virtual void onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort) override { @@ -82,6 +86,12 @@ class WalletPassphraseListenerImpl : public Monero::WalletListener, public Pass emit m_mgr->deviceButtonPressed(); } + Monero::optional onGetPassword(const char *reason) override + { + qDebug() << __FUNCTION__; + return Monero::optional(); + } + private: WalletManager * m_mgr; PassphraseHelper m_phelper; diff --git a/src/main/main.cpp b/src/main/main.cpp index 38901d3ff1..3377796260 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -60,6 +60,7 @@ #include "model/SubaddressModel.h" #include "SubaddressAccount.h" #include "model/SubaddressAccountModel.h" +#include "Memwipe.h" #include "Logger.h" #include "MainApp.h" #include "qt/downloader.h" @@ -382,6 +383,7 @@ Verify update binary using 'shasum'-compatible (SHA256 algo) output signed by tw qmlRegisterType("moneroComponents.Network", 1, 0, "Network"); qmlRegisterType("moneroComponents.WalletKeysFilesModel", 1, 0, "WalletKeysFilesModel"); qmlRegisterType("moneroComponents.WalletManager", 1, 0, "WalletManager"); + qmlRegisterType("moneroComponents.Memwipe", 1, 0, "Memwipe"); // Temporary Qt.labs.settings replacement qmlRegisterType("moneroComponents.Settings", 1, 0, "MoneroSettings"); diff --git a/wizard/WizardAskPassword.qml b/wizard/WizardAskPassword.qml index ca9c94c3d0..1a33b20c1d 100644 --- a/wizard/WizardAskPassword.qml +++ b/wizard/WizardAskPassword.qml @@ -104,6 +104,7 @@ ColumnLayout { labelFontSize: 14 password: true labelText: qsTr("Password") + translationManager.emptyString + isSecret: true } ColumnLayout { @@ -174,6 +175,7 @@ ColumnLayout { firstUserInput = false; } } + isSecret: true } RowLayout { diff --git a/wizard/WizardController.qml b/wizard/WizardController.qml index 1bc5ca96cb..c655ccef47 100644 --- a/wizard/WizardController.qml +++ b/wizard/WizardController.qml @@ -58,6 +58,11 @@ Rectangle { wizardController.walletOptionsName = defaultAccountName; wizardController.walletOptionsLocation = ''; } + memwipe.wipeQString(wizardController.walletOptionsPassword); + memwipe.wipeQString(wizardController.walletOptionsSeed); + memwipe.wipeQString(wizardController.walletOptionsSeedOffset); + memwipe.wipeQString(wizardController.walletOptionsRecoverViewkey); + memwipe.wipeQString(wizardController.walletOptionsRecoverSpendkey); wizardController.walletOptionsPassword = ''; wizardController.walletOptionsSeed = ''; wizardController.walletOptionsSeedOffset = ''; @@ -379,7 +384,8 @@ Rectangle { oshelper.removeTemporaryWallet(wizardController.tmpWalletFilename) // protecting wallet with password - wizardController.m_wallet.setPassword(wizardController.walletOptionsPassword); + wizardController.m_wallet.setPassword(/* old_password */ "", wizardController.walletOptionsPassword); + memwipe.wipeQString(wizardController.walletOptionsPassword); // save to persistent settings persistentSettings.account_name = wizardController.walletOptionsName @@ -547,7 +553,6 @@ Rectangle { persistentSettings.is_recovering_from_device = false; appWindow.restoreHeight = 0; - appWindow.walletPassword = ""; if(typeof fn == 'object') persistentSettings.wallet_path = walletManager.urlToLocalPath(fn); diff --git a/wizard/WizardCreateWallet3.qml b/wizard/WizardCreateWallet3.qml index dfb51f1652..b040421164 100644 --- a/wizard/WizardCreateWallet3.qml +++ b/wizard/WizardCreateWallet3.qml @@ -75,6 +75,7 @@ Rectangle { } onNextClicked: { wizardController.walletOptionsPassword = passwordFields.password; + memwipe.wipeQString(passwordFields.passwordConfirm); if (appWindow.walletMode < 2) { wizardStateView.state = "wizardCreateWallet5"; diff --git a/wizard/WizardRestoreWallet2.qml b/wizard/WizardRestoreWallet2.qml index f66950d523..d5f8cf7f1e 100644 --- a/wizard/WizardRestoreWallet2.qml +++ b/wizard/WizardRestoreWallet2.qml @@ -74,6 +74,7 @@ Rectangle { } onNextClicked: { wizardController.walletOptionsPassword = passwordFields.password; + memwipe.wipeQString(passwordFields.passwordConfirm); if(appWindow.walletMode === 0 || appWindow.walletMode === 1){ wizardStateView.state = "wizardRestoreWallet4";