askpassphrasedialog.cpp
1 // Copyright (c) 2011-present The Bitcoin Core developers 2 // Distributed under the MIT software license, see the accompanying 3 // file COPYING or http://www.opensource.org/licenses/mit-license.php. 4 5 #include <qt/askpassphrasedialog.h> 6 #include <qt/forms/ui_askpassphrasedialog.h> 7 8 #include <qt/guiconstants.h> 9 #include <qt/guiutil.h> 10 #include <qt/walletmodel.h> 11 12 #include <support/allocators/secure.h> 13 14 #include <QKeyEvent> 15 #include <QMessageBox> 16 #include <QPushButton> 17 18 AskPassphraseDialog::AskPassphraseDialog(Mode _mode, QWidget *parent, SecureString* passphrase_out) : 19 QDialog(parent, GUIUtil::dialog_flags), 20 ui(new Ui::AskPassphraseDialog), 21 mode(_mode), 22 m_passphrase_out(passphrase_out) 23 { 24 ui->setupUi(this); 25 26 ui->passEdit1->setMinimumSize(ui->passEdit1->sizeHint()); 27 ui->passEdit2->setMinimumSize(ui->passEdit2->sizeHint()); 28 ui->passEdit3->setMinimumSize(ui->passEdit3->sizeHint()); 29 30 ui->passEdit1->setMaxLength(MAX_PASSPHRASE_SIZE); 31 ui->passEdit2->setMaxLength(MAX_PASSPHRASE_SIZE); 32 ui->passEdit3->setMaxLength(MAX_PASSPHRASE_SIZE); 33 34 // Setup Caps Lock detection. 35 ui->passEdit1->installEventFilter(this); 36 ui->passEdit2->installEventFilter(this); 37 ui->passEdit3->installEventFilter(this); 38 39 switch(mode) 40 { 41 case Encrypt: // Ask passphrase x2 42 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>.")); 43 ui->passLabel1->hide(); 44 ui->passEdit1->hide(); 45 setWindowTitle(tr("Encrypt wallet")); 46 break; 47 case UnlockMigration: 48 case Unlock: // Ask passphrase 49 ui->warningLabel->setText(tr("This operation needs your wallet passphrase to unlock the wallet.")); 50 ui->passLabel2->hide(); 51 ui->passEdit2->hide(); 52 ui->passLabel3->hide(); 53 ui->passEdit3->hide(); 54 setWindowTitle(tr("Unlock wallet")); 55 break; 56 case ChangePass: // Ask old passphrase + new passphrase x2 57 setWindowTitle(tr("Change passphrase")); 58 ui->warningLabel->setText(tr("Enter the old passphrase and new passphrase for the wallet.")); 59 break; 60 } 61 textChanged(); 62 connect(ui->toggleShowPasswordButton, &QPushButton::toggled, this, &AskPassphraseDialog::toggleShowPassword); 63 connect(ui->passEdit1, &QLineEdit::textChanged, this, &AskPassphraseDialog::textChanged); 64 connect(ui->passEdit2, &QLineEdit::textChanged, this, &AskPassphraseDialog::textChanged); 65 connect(ui->passEdit3, &QLineEdit::textChanged, this, &AskPassphraseDialog::textChanged); 66 67 GUIUtil::handleCloseWindowShortcut(this); 68 } 69 70 AskPassphraseDialog::~AskPassphraseDialog() 71 { 72 secureClearPassFields(); 73 delete ui; 74 } 75 76 void AskPassphraseDialog::setModel(WalletModel *_model) 77 { 78 this->model = _model; 79 } 80 81 void AskPassphraseDialog::accept() 82 { 83 SecureString oldpass, newpass1, newpass2; 84 if (!model && mode != Encrypt && mode != UnlockMigration) 85 return; 86 oldpass.reserve(MAX_PASSPHRASE_SIZE); 87 newpass1.reserve(MAX_PASSPHRASE_SIZE); 88 newpass2.reserve(MAX_PASSPHRASE_SIZE); 89 90 oldpass.assign(std::string_view{ui->passEdit1->text().toStdString()}); 91 newpass1.assign(std::string_view{ui->passEdit2->text().toStdString()}); 92 newpass2.assign(std::string_view{ui->passEdit3->text().toStdString()}); 93 94 secureClearPassFields(); 95 96 switch(mode) 97 { 98 case Encrypt: { 99 if(newpass1.empty() || newpass2.empty()) 100 { 101 // Cannot encrypt with empty passphrase 102 break; 103 } 104 QMessageBox msgBoxConfirm(QMessageBox::Question, 105 tr("Confirm wallet encryption"), 106 tr("Warning: If you encrypt your wallet and lose your passphrase, you will <b>LOSE ALL OF YOUR BITCOINS</b>!") + "<br><br>" + tr("Are you sure you wish to encrypt your wallet?"), 107 QMessageBox::Cancel | QMessageBox::Yes, this); 108 msgBoxConfirm.button(QMessageBox::Yes)->setText(tr("Continue")); 109 msgBoxConfirm.button(QMessageBox::Cancel)->setText(tr("Back")); 110 msgBoxConfirm.setDefaultButton(QMessageBox::Cancel); 111 QMessageBox::StandardButton retval = (QMessageBox::StandardButton)msgBoxConfirm.exec(); 112 if(retval == QMessageBox::Yes) 113 { 114 if(newpass1 == newpass2) 115 { 116 QString encryption_reminder = tr("Remember that encrypting your wallet cannot fully protect " 117 "your bitcoins from being stolen by malware infecting your computer."); 118 if (m_passphrase_out) { 119 m_passphrase_out->assign(newpass1); 120 QMessageBox msgBoxWarning(QMessageBox::Warning, 121 tr("Wallet to be encrypted"), 122 "<qt>" + 123 tr("Your wallet is about to be encrypted. ") + encryption_reminder + " " + 124 tr("Are you sure you wish to encrypt your wallet?") + 125 "</b></qt>", 126 QMessageBox::Cancel | QMessageBox::Yes, this); 127 msgBoxWarning.setDefaultButton(QMessageBox::Cancel); 128 QMessageBox::StandardButton retval = (QMessageBox::StandardButton)msgBoxWarning.exec(); 129 if (retval == QMessageBox::Cancel) { 130 QDialog::reject(); 131 return; 132 } 133 } else { 134 assert(model != nullptr); 135 if (model->setWalletEncrypted(newpass1)) { 136 QMessageBox::warning(this, tr("Wallet encrypted"), 137 "<qt>" + 138 tr("Your wallet is now encrypted. ") + encryption_reminder + 139 "<br><br><b>" + 140 tr("IMPORTANT: Any previous backups you have made of your wallet file " 141 "should be replaced with the newly generated, encrypted wallet file. " 142 "For security reasons, previous backups of the unencrypted wallet file " 143 "will become useless as soon as you start using the new, encrypted wallet.") + 144 "</b></qt>"); 145 } else { 146 QMessageBox::critical(this, tr("Wallet encryption failed"), 147 tr("Wallet encryption failed due to an internal error. Your wallet was not encrypted.")); 148 } 149 } 150 QDialog::accept(); // Success 151 } 152 else 153 { 154 QMessageBox::critical(this, tr("Wallet encryption failed"), 155 tr("The supplied passphrases do not match.")); 156 } 157 } 158 } break; 159 case Unlock: 160 try { 161 if (!model->setWalletLocked(false, oldpass)) { 162 // Check if the passphrase has a null character (see #27067 for details) 163 if (oldpass.find('\0') == std::string::npos) { 164 QMessageBox::critical(this, tr("Wallet unlock failed"), 165 tr("The passphrase entered for the wallet decryption was incorrect.")); 166 } else { 167 QMessageBox::critical(this, tr("Wallet unlock failed"), 168 tr("The passphrase entered for the wallet decryption is incorrect. " 169 "It contains a null character (ie - a zero byte). " 170 "If the passphrase was set with a version of this software prior to 25.0, " 171 "please try again with only the characters up to — but not including — " 172 "the first null character. If this is successful, please set a new " 173 "passphrase to avoid this issue in the future.")); 174 } 175 } else { 176 if (m_passphrase_out) { 177 m_passphrase_out->assign(oldpass); 178 } 179 QDialog::accept(); // Success 180 } 181 } catch (const std::runtime_error& e) { 182 QMessageBox::critical(this, tr("Wallet unlock failed"), e.what()); 183 } 184 break; 185 case UnlockMigration: 186 Assume(m_passphrase_out)->assign(oldpass); 187 QDialog::accept(); 188 break; 189 case ChangePass: 190 if(newpass1 == newpass2) 191 { 192 if(model->changePassphrase(oldpass, newpass1)) 193 { 194 QMessageBox::information(this, tr("Wallet encrypted"), 195 tr("Wallet passphrase was successfully changed.")); 196 QDialog::accept(); // Success 197 } 198 else 199 { 200 // Check if the old passphrase had a null character (see #27067 for details) 201 if (oldpass.find('\0') == std::string::npos) { 202 QMessageBox::critical(this, tr("Passphrase change failed"), 203 tr("The passphrase entered for the wallet decryption was incorrect.")); 204 } else { 205 QMessageBox::critical(this, tr("Passphrase change failed"), 206 tr("The old passphrase entered for the wallet decryption is incorrect. " 207 "It contains a null character (ie - a zero byte). " 208 "If the passphrase was set with a version of this software prior to 25.0, " 209 "please try again with only the characters up to — but not including — " 210 "the first null character.")); 211 } 212 } 213 } 214 else 215 { 216 QMessageBox::critical(this, tr("Wallet encryption failed"), 217 tr("The supplied passphrases do not match.")); 218 } 219 break; 220 } 221 } 222 223 void AskPassphraseDialog::textChanged() 224 { 225 // Validate input, set Ok button to enabled when acceptable 226 bool acceptable = false; 227 switch(mode) 228 { 229 case Encrypt: // New passphrase x2 230 acceptable = !ui->passEdit2->text().isEmpty() && !ui->passEdit3->text().isEmpty(); 231 break; 232 case UnlockMigration: 233 case Unlock: // Old passphrase x1 234 acceptable = !ui->passEdit1->text().isEmpty(); 235 break; 236 case ChangePass: // Old passphrase x1, new passphrase x2 237 acceptable = !ui->passEdit1->text().isEmpty() && !ui->passEdit2->text().isEmpty() && !ui->passEdit3->text().isEmpty(); 238 break; 239 } 240 ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(acceptable); 241 } 242 243 bool AskPassphraseDialog::event(QEvent *event) 244 { 245 // Detect Caps Lock key press. 246 if (event->type() == QEvent::KeyPress) { 247 QKeyEvent *ke = static_cast<QKeyEvent *>(event); 248 if (ke->key() == Qt::Key_CapsLock) { 249 fCapsLock = !fCapsLock; 250 } 251 if (fCapsLock) { 252 ui->capsLabel->setText(tr("Warning: The Caps Lock key is on!")); 253 } else { 254 ui->capsLabel->clear(); 255 } 256 } 257 return QWidget::event(event); 258 } 259 260 void AskPassphraseDialog::toggleShowPassword(bool show) 261 { 262 ui->toggleShowPasswordButton->setDown(show); 263 const auto mode = show ? QLineEdit::Normal : QLineEdit::Password; 264 ui->passEdit1->setEchoMode(mode); 265 ui->passEdit2->setEchoMode(mode); 266 ui->passEdit3->setEchoMode(mode); 267 } 268 269 bool AskPassphraseDialog::eventFilter(QObject *object, QEvent *event) 270 { 271 /* Detect Caps Lock. 272 * There is no good OS-independent way to check a key state in Qt, but we 273 * can detect Caps Lock by checking for the following condition: 274 * Shift key is down and the result is a lower case character, or 275 * Shift key is not down and the result is an upper case character. 276 */ 277 if (event->type() == QEvent::KeyPress) { 278 QKeyEvent *ke = static_cast<QKeyEvent *>(event); 279 QString str = ke->text(); 280 if (str.length() != 0) { 281 const QChar *psz = str.unicode(); 282 bool fShift = (ke->modifiers() & Qt::ShiftModifier) != 0; 283 if ((fShift && *psz >= 'a' && *psz <= 'z') || (!fShift && *psz >= 'A' && *psz <= 'Z')) { 284 fCapsLock = true; 285 ui->capsLabel->setText(tr("Warning: The Caps Lock key is on!")); 286 } else if (psz->isLetter()) { 287 fCapsLock = false; 288 ui->capsLabel->clear(); 289 } 290 } 291 } 292 return QDialog::eventFilter(object, event); 293 } 294 295 static void SecureClearQLineEdit(QLineEdit* edit) 296 { 297 // Attempt to overwrite text so that they do not linger around in memory 298 edit->setText(QString(" ").repeated(edit->text().size())); 299 edit->clear(); 300 } 301 302 void AskPassphraseDialog::secureClearPassFields() 303 { 304 SecureClearQLineEdit(ui->passEdit1); 305 SecureClearQLineEdit(ui->passEdit2); 306 SecureClearQLineEdit(ui->passEdit3); 307 }