Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open fully encrypted wallets #747

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ BITCOIN_CORE_H = \
wallet/crypter.h \
wallet/db.h \
wallet/dump.h \
wallet/encrypted_db.h \
wallet/external_signer_scriptpubkeyman.h \
wallet/feebumper.h \
wallet/fees.h \
Expand Down Expand Up @@ -516,7 +517,7 @@ libbitcoin_wallet_a_SOURCES = \
$(BITCOIN_CORE_H)

if USE_SQLITE
libbitcoin_wallet_a_SOURCES += wallet/sqlite.cpp
libbitcoin_wallet_a_SOURCES += wallet/sqlite.cpp wallet/encrypted_db.cpp
endif
if USE_BDB
libbitcoin_wallet_a_SOURCES += wallet/bdb.cpp wallet/salvage.cpp
Expand Down
7 changes: 5 additions & 2 deletions src/interfaces/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -321,10 +321,10 @@ class WalletLoader : public ChainClient
{
public:
//! Create new wallet.
virtual util::Result<std::unique_ptr<Wallet>> createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, std::vector<bilingual_str>& warnings) = 0;
virtual util::Result<std::unique_ptr<Wallet>> createWallet(const std::string& name, const SecureString& passphrase, const SecureString& db_passphrase, uint64_t wallet_creation_flags, std::vector<bilingual_str>& warnings) = 0;

//! Load existing wallet.
virtual util::Result<std::unique_ptr<Wallet>> loadWallet(const std::string& name, std::vector<bilingual_str>& warnings) = 0;
virtual util::Result<std::unique_ptr<Wallet>> loadWallet(const std::string& name, std::vector<bilingual_str>& warnings, const SecureString& db_passphrase) = 0;

//! Return default wallet directory.
virtual std::string getWalletDir() = 0;
Expand All @@ -344,6 +344,9 @@ class WalletLoader : public ChainClient
using LoadWalletFn = std::function<void(std::unique_ptr<Wallet> wallet)>;
virtual std::unique_ptr<Handler> handleLoadWallet(LoadWalletFn fn) = 0;

//! Return whether the named wallet has an encrypted database
virtual bool isWalletDBEncrypted(const std::string& name) = 0;

//! Return pointer to internal context, useful for testing.
virtual wallet::WalletContext* context() { return nullptr; }
};
Expand Down
62 changes: 39 additions & 23 deletions src/qt/askpassphrasedialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
#include <QMessageBox>
#include <QPushButton>

AskPassphraseDialog::AskPassphraseDialog(Mode _mode, QWidget *parent, SecureString* passphrase_out) :
AskPassphraseDialog::AskPassphraseDialog(Mode _mode, QWidget *parent, SecureString* passphrase_out, QString warning_text) :
QDialog(parent, GUIUtil::dialog_flags),
ui(new Ui::AskPassphraseDialog),
mode(_mode),
Expand All @@ -43,13 +43,20 @@ AskPassphraseDialog::AskPassphraseDialog(Mode _mode, QWidget *parent, SecureStri
switch(mode)
{
case Encrypt: // Ask passphrase x2
ui->warningLabel->setText(tr("Enter the new passphrase for the wallet.<br/>Please use a passphrase of <b>ten or more random characters</b>, or <b>eight or more words</b>."));
if (warning_text.isEmpty()) {
warning_text = tr("Enter the new passphrase for encrypting the private keys in the wallet.");
}
ui->warningLabel->setText(warning_text + tr("<br/>Please use a passphrase of <b>ten or more random characters</b>, or <b>eight or more words</b>."));
ui->passLabel1->hide();
ui->passEdit1->hide();
setWindowTitle(tr("Encrypt wallet"));
break;
case Unlock: // Ask passphrase
ui->warningLabel->setText(tr("This operation needs your wallet passphrase to unlock the wallet."));
if (warning_text.isEmpty()) {
ui->warningLabel->setText(tr("This operation needs your wallet passphrase to unlock the wallet."));
} else {
ui->warningLabel->setText(warning_text);
}
ui->passLabel2->hide();
ui->passEdit2->hide();
ui->passLabel3->hide();
Expand All @@ -58,7 +65,11 @@ AskPassphraseDialog::AskPassphraseDialog(Mode _mode, QWidget *parent, SecureStri
break;
case ChangePass: // Ask old passphrase + new passphrase x2
setWindowTitle(tr("Change passphrase"));
ui->warningLabel->setText(tr("Enter the old passphrase and new passphrase for the wallet."));
if (warning_text.isEmpty()) {
ui->warningLabel->setText(tr("Enter the old passphrase and new passphrase for the wallet."));
} else {
ui->warningLabel->setText(warning_text);
}
break;
}
textChanged();
Expand All @@ -84,8 +95,6 @@ void AskPassphraseDialog::setModel(WalletModel *_model)
void AskPassphraseDialog::accept()
{
SecureString oldpass, newpass1, newpass2;
if (!model && mode != Encrypt)
return;
oldpass.reserve(MAX_PASSPHRASE_SIZE);
newpass1.reserve(MAX_PASSPHRASE_SIZE);
newpass2.reserve(MAX_PASSPHRASE_SIZE);
Expand Down Expand Up @@ -151,29 +160,36 @@ void AskPassphraseDialog::accept()
}
} break;
case Unlock:
try {
if (!model->setWalletLocked(false, oldpass)) {
// Check if the passphrase has a null character (see #27067 for details)
if (oldpass.find('\0') == std::string::npos) {
QMessageBox::critical(this, tr("Wallet unlock failed"),
tr("The passphrase entered for the wallet decryption was incorrect."));
if (m_passphrase_out) {
m_passphrase_out->assign(oldpass);
QDialog::accept(); // Success
} else {
try {
assert(model);
if (!model->setWalletLocked(false, oldpass)) {
// Check if the passphrase has a null character (see #27067 for details)
if (oldpass.find('\0') == std::string::npos) {
QMessageBox::critical(this, tr("Wallet unlock failed"),
tr("The passphrase entered for the wallet decryption was incorrect."));
} else {
QMessageBox::critical(this, tr("Wallet unlock failed"),
tr("The passphrase entered for the wallet decryption is incorrect. "
"It contains a null character (ie - a zero byte). "
"If the passphrase was set with a version of this software prior to 25.0, "
"please try again with only the characters up to — but not including — "
"the first null character. If this is successful, please set a new "
"passphrase to avoid this issue in the future."));
}
} else {
QMessageBox::critical(this, tr("Wallet unlock failed"),
tr("The passphrase entered for the wallet decryption is incorrect. "
"It contains a null character (ie - a zero byte). "
"If the passphrase was set with a version of this software prior to 25.0, "
"please try again with only the characters up to — but not including — "
"the first null character. If this is successful, please set a new "
"passphrase to avoid this issue in the future."));
QDialog::accept(); // Success
}
} else {
QDialog::accept(); // Success
} catch (const std::runtime_error& e) {
QMessageBox::critical(this, tr("Wallet unlock failed"), e.what());
}
} catch (const std::runtime_error& e) {
QMessageBox::critical(this, tr("Wallet unlock failed"), e.what());
}
break;
case ChangePass:
assert(model);
if(newpass1 == newpass2)
{
if(model->changePassphrase(oldpass, newpass1))
Expand Down
2 changes: 1 addition & 1 deletion src/qt/askpassphrasedialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class AskPassphraseDialog : public QDialog
ChangePass, /**< Ask old passphrase + new passphrase twice */
};

explicit AskPassphraseDialog(Mode mode, QWidget *parent, SecureString* passphrase_out = nullptr);
explicit AskPassphraseDialog(Mode mode, QWidget *parent, SecureString* passphrase_out = nullptr, QString warning_text = "");
~AskPassphraseDialog();

void accept() override;
Expand Down
7 changes: 7 additions & 0 deletions src/qt/createwalletdialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ CreateWalletDialog::CreateWalletDialog(QWidget* parent) :
ui->descriptor_checkbox->setChecked(false);
ui->external_signer_checkbox->setEnabled(false);
ui->external_signer_checkbox->setChecked(false);
ui->encrypt_db_checkbox->setEnabled(false);
ui->encrypt_db_checkbox->setChecked(false);
#endif

#ifndef USE_BDB
Expand Down Expand Up @@ -144,6 +146,11 @@ bool CreateWalletDialog::isEncryptWalletChecked() const
return ui->encrypt_wallet_checkbox->isChecked();
}

bool CreateWalletDialog::isEncryptDBChecked() const
{
return ui->encrypt_db_checkbox->isChecked();
}

bool CreateWalletDialog::isDisablePrivateKeysChecked() const
{
return ui->disable_privkeys_checkbox->isChecked();
Expand Down
1 change: 1 addition & 0 deletions src/qt/createwalletdialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class CreateWalletDialog : public QDialog

QString walletName() const;
bool isEncryptWalletChecked() const;
bool isEncryptDBChecked() const;
bool isDisablePrivateKeysChecked() const;
bool isMakeBlankWalletChecked() const;
bool isDescriptorWalletChecked() const;
Expand Down
12 changes: 11 additions & 1 deletion src/qt/forms/createwalletdialog.ui
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>364</width>
<height>249</height>
<height>316</height>
</rect>
</property>
<property name="windowTitle">
Expand Down Expand Up @@ -122,6 +122,16 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="encrypt_db_checkbox">
<property name="toolTip">
<string>Encrypt the wallet's database. The database will be encrypted with a passphrase of your choice. Wallets with an encrypted database cannot be loaded automatically on startup.</string>
</property>
<property name="text">
<string>Encrypt Database</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
Expand Down
59 changes: 51 additions & 8 deletions src/qt/walletcontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -220,17 +220,17 @@ CreateWalletActivity::~CreateWalletActivity()
delete m_passphrase_dialog;
}

void CreateWalletActivity::askPassphrase()
void CreateWalletActivity::askPassphrase(SecureString* passphrase_out, std::function<void()> next_func, QString warning_text)
{
m_passphrase_dialog = new AskPassphraseDialog(AskPassphraseDialog::Encrypt, m_parent_widget, &m_passphrase);
m_passphrase_dialog = new AskPassphraseDialog(AskPassphraseDialog::Encrypt, m_parent_widget, passphrase_out, warning_text);
m_passphrase_dialog->setWindowModality(Qt::ApplicationModal);
m_passphrase_dialog->show();

connect(m_passphrase_dialog, &QObject::destroyed, [this] {
m_passphrase_dialog = nullptr;
});
connect(m_passphrase_dialog, &QDialog::accepted, [this] {
createWallet();
connect(m_passphrase_dialog, &QDialog::accepted, [next_func] {
next_func();
});
connect(m_passphrase_dialog, &QDialog::rejected, [this] {
Q_EMIT finished();
Expand Down Expand Up @@ -262,7 +262,7 @@ void CreateWalletActivity::createWallet()
}

QTimer::singleShot(500ms, worker(), [this, name, flags] {
auto wallet{node().walletLoader().createWallet(name, m_passphrase, flags, m_warning_message)};
auto wallet{node().walletLoader().createWallet(name, m_passphrase, m_db_passphrase, flags, m_warning_message)};

if (wallet) {
m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(*wallet));
Expand Down Expand Up @@ -314,7 +314,24 @@ void CreateWalletActivity::create()
});
connect(m_create_wallet_dialog, &QDialog::accepted, [this] {
if (m_create_wallet_dialog->isEncryptWalletChecked()) {
askPassphrase();
if (m_create_wallet_dialog->isEncryptDBChecked()) {
// When both are checked, we need to first get the passphrase for wallet encryption
// then the passphrase for db encryption, then make the wallet, hence this chain of binds
askPassphrase(
&m_passphrase,
std::bind(
&CreateWalletActivity::askPassphrase,
this,
&m_db_passphrase,
[this]() { createWallet(); },
tr("Enter the new passphrase for encrypting all records in the wallet database.")
)
);
} else {
askPassphrase(&m_passphrase, std::bind(&CreateWalletActivity::createWallet, this));
}
} else if (m_create_wallet_dialog->isEncryptDBChecked()) {
askPassphrase(&m_db_passphrase, std::bind(&CreateWalletActivity::createWallet, this), tr("Enter the new passphrase for encrypting all records in the wallet database."));
} else {
createWallet();
}
Expand All @@ -339,7 +356,33 @@ void OpenWalletActivity::finish()
Q_EMIT finished();
}

void OpenWalletActivity::open(const std::string& path)
void OpenWalletActivity::askPassphrase(const std::string& name)
{
m_passphrase_dialog = new AskPassphraseDialog(AskPassphraseDialog::Unlock, m_parent_widget, &m_db_passphrase);
m_passphrase_dialog->setWindowModality(Qt::ApplicationModal);
m_passphrase_dialog->show();

connect(m_passphrase_dialog, &QObject::destroyed, [this] {
m_passphrase_dialog = nullptr;
});
connect(m_passphrase_dialog, &QDialog::accepted, [this, &name] {
openWallet(name);
});
connect(m_passphrase_dialog, &QDialog::rejected, [this] {
Q_EMIT finished();
});
}

void OpenWalletActivity::open(const std::string& name)
{
if (node().walletLoader().isWalletDBEncrypted(name)) {
askPassphrase(name);
} else {
openWallet(name);
}
}

void OpenWalletActivity::openWallet(const std::string& path)
{
QString name = path.empty() ? QString("["+tr("default wallet")+"]") : QString::fromStdString(path);

Expand All @@ -351,7 +394,7 @@ void OpenWalletActivity::open(const std::string& path)
tr("Opening Wallet <b>%1</b>…").arg(name.toHtmlEscaped()));

QTimer::singleShot(0, worker(), [this, path] {
auto wallet{node().walletLoader().loadWallet(path, m_warning_message)};
auto wallet{node().walletLoader().loadWallet(path, m_warning_message, m_db_passphrase)};

if (wallet) {
m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(*wallet));
Expand Down
8 changes: 7 additions & 1 deletion src/qt/walletcontroller.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,12 @@ class CreateWalletActivity : public WalletControllerActivity
void created(WalletModel* wallet_model);

private:
void askPassphrase();
void askPassphrase(SecureString* passphrase_out, std::function<void()> next_func, QString warning_text = "");
void createWallet();
void finish();

SecureString m_passphrase;
SecureString m_db_passphrase;
CreateWalletDialog* m_create_wallet_dialog{nullptr};
AskPassphraseDialog* m_passphrase_dialog{nullptr};
};
Expand All @@ -147,6 +148,11 @@ class OpenWalletActivity : public WalletControllerActivity

private:
void finish();
void openWallet(const std::string& path);
void askPassphrase(const std::string& name);

SecureString m_db_passphrase;
AskPassphraseDialog* m_passphrase_dialog{nullptr};
};

class LoadWalletsActivity : public WalletControllerActivity
Expand Down
23 changes: 19 additions & 4 deletions src/wallet/crypter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,34 @@ bool CCrypter::SetKeyFromPassphrase(const SecureString& strKeyData, const std::v

bool CCrypter::SetKey(const CKeyingMaterial& chNewKey, const std::vector<unsigned char>& chNewIV)
{
if (chNewKey.size() != WALLET_CRYPTO_KEY_SIZE || chNewIV.size() != WALLET_CRYPTO_IV_SIZE)
return SetKey(chNewKey) && SetIV(chNewIV);
}

bool CCrypter::SetKey(const CKeyingMaterial& chNewKey)
{
if (chNewKey.size() != WALLET_CRYPTO_KEY_SIZE)
return false;

memcpy(vchKey.data(), chNewKey.data(), chNewKey.size());
memcpy(vchIV.data(), chNewIV.data(), chNewIV.size());

fKeySet = true;
return true;
}

bool CCrypter::SetIV(const std::vector<unsigned char>& chNewIV)
{
if (chNewIV.size() != WALLET_CRYPTO_IV_SIZE)
return false;

memcpy(vchIV.data(), chNewIV.data(), chNewIV.size());

m_iv_set = true;
return true;
}

bool CCrypter::Encrypt(const CKeyingMaterial& vchPlaintext, std::vector<unsigned char> &vchCiphertext) const
{
if (!fKeySet)
if (!fKeySet && !m_iv_set)
return false;

// max ciphertext len for a n bytes of plaintext is
Expand All @@ -89,7 +104,7 @@ bool CCrypter::Encrypt(const CKeyingMaterial& vchPlaintext, std::vector<unsigned

bool CCrypter::Decrypt(const std::vector<unsigned char>& vchCiphertext, CKeyingMaterial& vchPlaintext) const
{
if (!fKeySet)
if (!fKeySet && !m_iv_set)
return false;

// plaintext will always be equal to or lesser than length of ciphertext
Expand Down
Loading