/* * This file is part of Bitsquare. * * Bitsquare is free software: you can redistribute it and/or modify it * under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or (at * your option) any later version. * * Bitsquare is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public * License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Bitsquare. If not, see <http://www.gnu.org/licenses/>. */ package io.bitsquare.gui.main.account.content.password; import io.bitsquare.btc.TradeWalletService; import io.bitsquare.btc.WalletService; import io.bitsquare.common.util.Tuple2; import io.bitsquare.common.util.Tuple3; import io.bitsquare.crypto.ScryptUtil; import io.bitsquare.gui.common.view.ActivatableView; import io.bitsquare.gui.common.view.FxmlView; import io.bitsquare.gui.components.BusyAnimation; import io.bitsquare.gui.components.PasswordTextField; import io.bitsquare.gui.components.TitledGroupBg; import io.bitsquare.gui.main.overlays.popups.Popup; import io.bitsquare.gui.util.Layout; import io.bitsquare.gui.util.validation.InputValidator; import io.bitsquare.gui.util.validation.PasswordValidator; import javafx.beans.value.ChangeListener; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.layout.GridPane; import org.bitcoinj.core.Wallet; import org.bitcoinj.crypto.KeyCrypterScrypt; import javax.inject.Inject; import static com.google.inject.internal.util.$Preconditions.checkArgument; import static io.bitsquare.gui.util.FormBuilder.*; @FxmlView public class PasswordView extends ActivatableView<GridPane, Void> { private final PasswordValidator passwordValidator; private final WalletService walletService; private final TradeWalletService tradeWalletService; private PasswordTextField passwordField; private PasswordTextField repeatedPasswordField; private Button pwButton; private TitledGroupBg headline; private int gridRow = 0; private Label repeatedPasswordLabel; private ChangeListener<String> passwordFieldChangeListener; private ChangeListener<String> repeatedPasswordFieldChangeListener; /////////////////////////////////////////////////////////////////////////////////////////// // Constructor, lifecycle /////////////////////////////////////////////////////////////////////////////////////////// @Inject private PasswordView(PasswordValidator passwordValidator, WalletService walletService, TradeWalletService tradeWalletService) { this.passwordValidator = passwordValidator; this.walletService = walletService; this.tradeWalletService = tradeWalletService; } @Override public void initialize() { headline = addTitledGroupBg(root, gridRow, 2, ""); passwordField = addLabelPasswordTextField(root, gridRow, "Enter password:", Layout.FIRST_ROW_DISTANCE).second; passwordField.setValidator(passwordValidator); passwordFieldChangeListener = (observable, oldValue, newValue) -> validatePasswords(); Tuple2<Label, PasswordTextField> tuple2 = addLabelPasswordTextField(root, ++gridRow, "Repeat password:"); repeatedPasswordLabel = tuple2.first; repeatedPasswordField = tuple2.second; repeatedPasswordField.setValidator(passwordValidator); repeatedPasswordFieldChangeListener = (observable, oldValue, newValue) -> validatePasswords(); Tuple3<Button, BusyAnimation, Label> tuple = addButtonBusyAnimationLabel(root, ++gridRow, "", 15); pwButton = tuple.first; BusyAnimation busyAnimation = tuple.second; Label deriveStatusLabel = tuple.third; pwButton.setDisable(true); setText(); pwButton.setOnAction(e -> { String password = passwordField.getText(); checkArgument(password.length() < 50, "Password must be less then 50 characters."); pwButton.setDisable(true); deriveStatusLabel.setText("Derive key from password"); busyAnimation.play(); KeyCrypterScrypt keyCrypterScrypt; Wallet wallet = walletService.getWallet(); if (wallet.isEncrypted()) keyCrypterScrypt = (KeyCrypterScrypt) wallet.getKeyCrypter(); else keyCrypterScrypt = ScryptUtil.getKeyCrypterScrypt(); ScryptUtil.deriveKeyWithScrypt(keyCrypterScrypt, password, aesKey -> { deriveStatusLabel.setText(""); busyAnimation.stop(); if (wallet.isEncrypted()) { if (wallet.checkAESKey(aesKey)) { walletService.decryptWallet(aesKey); tradeWalletService.setAesKey(null); new Popup() .feedback("Wallet successfully decrypted and password protection removed.") .show(); passwordField.setText(""); repeatedPasswordField.setText(""); walletService.backupWallet(); } else { pwButton.setDisable(false); new Popup() .warning("You entered the wrong password.\n\n" + "Please try entering your password again, carefully checking for typos or spelling errors.") .show(); } } else { // we save the key for the trade wallet as we don't require passwords here walletService.encryptWallet(keyCrypterScrypt, aesKey); tradeWalletService.setAesKey(aesKey); new Popup() .feedback("Wallet successfully encrypted and password protection enabled.") .show(); passwordField.setText(""); repeatedPasswordField.setText(""); walletService.clearBackup(); walletService.backupWallet(); } setText(); }); }); addTitledGroupBg(root, ++gridRow, 1, "Information", Layout.GROUP_DISTANCE); addMultilineLabel(root, gridRow, "With password protection you need to enter your password when" + " withdrawing bitcoin out of your wallet or " + "if you want to view or restore a wallet from seed words as well as at application startup.", Layout.FIRST_ROW_AND_GROUP_DISTANCE); } private void setText() { if (walletService.getWallet().isEncrypted()) { pwButton.setText("Remove password"); headline.setText("Remove password protection for wallet"); repeatedPasswordField.setVisible(false); repeatedPasswordField.setManaged(false); repeatedPasswordLabel.setVisible(false); repeatedPasswordLabel.setManaged(false); } else { pwButton.setText("Set password"); headline.setText("Set password protection for wallet"); repeatedPasswordField.setVisible(true); repeatedPasswordField.setManaged(true); repeatedPasswordLabel.setVisible(true); repeatedPasswordLabel.setManaged(true); } } @Override protected void activate() { passwordField.textProperty().addListener(passwordFieldChangeListener); repeatedPasswordField.textProperty().addListener(repeatedPasswordFieldChangeListener); } @Override protected void deactivate() { passwordField.textProperty().removeListener(passwordFieldChangeListener); repeatedPasswordField.textProperty().removeListener(repeatedPasswordFieldChangeListener); } private void validatePasswords() { passwordValidator.setExternalValidationResult(null); InputValidator.ValidationResult result = passwordValidator.validate(passwordField.getText()); if (result.isValid) { if (walletService.getWallet().isEncrypted()) { pwButton.setDisable(false); return; } else { result = passwordValidator.validate(repeatedPasswordField.getText()); if (result.isValid) { if (passwordField.getText().equals(repeatedPasswordField.getText())) { pwButton.setDisable(false); return; } else { passwordValidator.setExternalValidationResult(new InputValidator.ValidationResult(false, "The 2 passwords do not match.")); } } } } pwButton.setDisable(true); } }