/** * Copyright 2012 multibit.org * * Licensed under the MIT license (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://opensource.org/licenses/mit-license.php * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.multibit.viewsystem.swing.action; import com.google.bitcoin.core.Wallet; import com.google.bitcoin.crypto.KeyCrypter; import com.google.bitcoin.crypto.KeyCrypterException; import org.multibit.controller.bitcoin.BitcoinController; import org.multibit.file.BackupManager; import org.multibit.file.FileHandler; import org.multibit.model.bitcoin.WalletBusyListener; import org.multibit.model.bitcoin.WalletData; import org.multibit.viewsystem.swing.view.panels.ChangePasswordPanel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.spongycastle.util.Arrays; import javax.swing.*; import java.awt.event.ActionEvent; import java.io.File; import java.io.IOException; import java.nio.CharBuffer; /** * This {@link Action} action decrypts private keys with the old password and then encrypts the private keys with the new password. */ public class ChangePasswordSubmitAction extends MultiBitSubmitAction implements WalletBusyListener { private static final Logger log = LoggerFactory.getLogger(ChangePasswordSubmitAction.class); private static final long serialVersionUID = 1923492460598757765L; private ChangePasswordPanel changePasswordPanel; private JPasswordField currentPassword; private JPasswordField newPassword; private JPasswordField repeatNewPassword; private File privateKeysBackupFile; /** * Creates a new {@link ChangePasswordSubmitAction}. */ public ChangePasswordSubmitAction(BitcoinController bitcoinController, ChangePasswordPanel changePasswordPanel, ImageIcon icon, JPasswordField currentPassword, JPasswordField newPassword, JPasswordField repeatNewPassword) { super(bitcoinController, "changePasswordSubmitAction.text", "changePasswordSubmitAction.tooltip", "changePasswordSubmitAction.mnemonicKey", icon); this.changePasswordPanel = changePasswordPanel; this.currentPassword = currentPassword; this.newPassword = newPassword; this.repeatNewPassword = repeatNewPassword; // This action is a WalletBusyListener. super.bitcoinController.registerWalletBusyListener(this); walletBusyChange(super.bitcoinController.getModel().getActivePerWalletModelData().isBusy()); } /** * Change the wallet password. */ @Override public void actionPerformed(ActionEvent e) { changePasswordPanel.clearMessages(); privateKeysBackupFile = null; char[] newPasswordToUse = null; char[] currentPasswordToUse = null; if (currentPassword.getPassword() == null || currentPassword.getPassword().length == 0) { // Notify must enter the current password. changePasswordPanel.setMessage1(controller.getLocaliser().getString("changePasswordPanel.enterCurrentPassword")); return; } currentPasswordToUse = currentPassword.getPassword(); // Get the new passwords on the password fields. if (newPassword.getPassword() == null || newPassword.getPassword().length == 0) { // Notify the user must enter a new password. changePasswordPanel.setMessage1(controller.getLocaliser().getString("changePasswordPanel.enterPasswords")); return; } else { if (!Arrays.areEqual(newPassword.getPassword(), repeatNewPassword.getPassword())) { // Notify user passwords are different. changePasswordPanel.setMessage1(controller.getLocaliser().getString( "showExportPrivateKeysAction.passwordsAreDifferent")); return; } else { newPasswordToUse = newPassword.getPassword(); } } Wallet wallet = super.bitcoinController.getModel().getActiveWallet(); if (wallet != null) { // Double check wallet is not busy then declare that the active // wallet is busy with the task. WalletData perWalletModelData = super.bitcoinController.getModel().getActivePerWalletModelData(); if (!perWalletModelData.isBusy()) { perWalletModelData.setBusy(true); perWalletModelData.setBusyTaskKey("changePasswordSubmitAction.text"); super.bitcoinController.fireWalletBusyChange(true); boolean decryptSuccess = false; KeyCrypter keyCrypterToUse = wallet.getKeyCrypter(); try { wallet.decrypt(keyCrypterToUse.deriveKey(CharBuffer.wrap(currentPasswordToUse))); decryptSuccess = true; } catch (KeyCrypterException kce) { // Notify the user that the decrypt failed. changePasswordPanel.setMessage1(controller.getLocaliser().getString("changePasswordPanel.changePasswordFailed", new String[] { kce.getMessage() })); // Declare that wallet is no longer busy with the task. perWalletModelData.setBusyTaskKey(null); perWalletModelData.setBusy(false); super.bitcoinController.fireWalletBusyChange(false); return; } if (decryptSuccess) { try { wallet.encrypt(keyCrypterToUse, keyCrypterToUse.deriveKey(CharBuffer.wrap(newPasswordToUse))); FileHandler fileHandler = new FileHandler(super.bitcoinController); fileHandler.savePerWalletModelData(super.bitcoinController.getModel().getActivePerWalletModelData(), true); // Backup the private keys. privateKeysBackupFile = fileHandler.backupPrivateKeys(CharBuffer.wrap(newPasswordToUse)); // Backup the wallet and wallet info BackupManager.INSTANCE.backupPerWalletModelData(fileHandler, perWalletModelData); } catch (KeyCrypterException kce) { // Notify the user that the encrypt failed. changePasswordPanel.setMessage1(controller.getLocaliser().getString( "changePasswordPanel.changePasswordFailed", new String[] { kce.getMessage() })); return; } catch (IOException ede) { // Notify the user that the private key backup failed. changePasswordPanel.setMessage2(controller.getLocaliser().getString( "changePasswordPanel.keysBackupFailed", new String[] { ede.getMessage() })); return; } finally { // Declare that wallet is no longer busy with the task. perWalletModelData.setBusyTaskKey(null); perWalletModelData.setBusy(false); super.bitcoinController.fireWalletBusyChange(false); } } else { // Declare that wallet is no longer busy with the task. perWalletModelData.setBusyTaskKey(null); perWalletModelData.setBusy(false); super.bitcoinController.fireWalletBusyChange(false); } } } // Success. SwingUtilities.invokeLater(new Runnable() { @Override public void run() { changePasswordPanel.clearMessages(); changePasswordPanel.clearPasswords(); changePasswordPanel.setMessage1(controller.getLocaliser().getString("changePasswordPanel.changePasswordSuccess")); if (privateKeysBackupFile != null) { try { changePasswordPanel.setMessage2(controller.getLocaliser().getString( "changePasswordPanel.oldBackupsMessage")); changePasswordPanel.setMessage3(controller.getLocaliser().getString( "changePasswordPanel.keysBackupSuccess", new Object[] { privateKeysBackupFile.getCanonicalPath() })); } catch (IOException e1) { log.debug(e1.getClass().getCanonicalName() + " " + e1.getMessage()); } } } }); } @Override public void walletBusyChange(boolean newWalletIsBusy) { // Update the enable status of the action to match the wallet busy status. if (super.bitcoinController.getModel().getActivePerWalletModelData().isBusy()) { // Wallet is busy with another operation that may change the private keys - Action is disabled. putValue(SHORT_DESCRIPTION, this.bitcoinController.getLocaliser().getString("multiBitSubmitAction.walletIsBusy", new Object[]{controller.getLocaliser().getString(this.bitcoinController.getModel().getActivePerWalletModelData().getBusyTaskKey())})); } else { // Enable putValue(SHORT_DESCRIPTION, controller.getLocaliser().getString("changePasswordSubmitAction.text")); } } }