askpassphrasedialog.cpp raw

   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  }
 308