/ src / qt / askpassphrasedialog.cpp
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  }