/** * 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 com.google.bitcoin.crypto.KeyCrypterScrypt; import com.google.protobuf.ByteString; import org.bitcoinj.wallet.Protos; import org.bitcoinj.wallet.Protos.ScryptParameters; 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.model.bitcoin.WalletInfoData; import org.multibit.store.MultiBitWalletVersion; import org.multibit.viewsystem.swing.view.panels.AddPasswordPanel; 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 encrypts the private keys with the password. */ public class AddPasswordSubmitAction extends MultiBitSubmitAction implements WalletBusyListener { private static final Logger log = LoggerFactory.getLogger(AddPasswordSubmitAction.class); private static final long serialVersionUID = 1923492460598757765L; private AddPasswordPanel addPasswordPanel; private JPasswordField password1; private JPasswordField password2; private File privateKeysBackupFile; /** * Creates a new {@link AddPasswordSubmitAction}. */ public AddPasswordSubmitAction(BitcoinController bitcoinController, AddPasswordPanel addPasswordPanel, ImageIcon icon, JPasswordField password1, JPasswordField password2) { super(bitcoinController, "addPasswordSubmitAction.text", "addPasswordSubmitAction.tooltip", "addPasswordSubmitAction.mnemonicKey", icon); this.addPasswordPanel = addPasswordPanel; this.password1 = password1; this.password2 = password2; // This action is a WalletBusyListener. this.bitcoinController.registerWalletBusyListener(this); walletBusyChange(this.bitcoinController.getModel().getActivePerWalletModelData().isBusy()); } /** * Add a password to a wallet. */ @Override public void actionPerformed(ActionEvent e) { addPasswordPanel.clearMessages(); privateKeysBackupFile = null; char[] passwordToUse = null; // Get the passwords on the password fields. if (password1.getPassword() == null || password1.getPassword().length == 0) { // Notify the user must enter a password. addPasswordPanel.setMessage1(controller.getLocaliser() .getString("addPasswordPanel.enterPasswords")); return; } else { if (!Arrays.areEqual(password1.getPassword(), password2.getPassword())) { // Notify user passwords are different. addPasswordPanel.setMessage1(controller.getLocaliser().getString( "showExportPrivateKeysAction.passwordsAreDifferent")); return; } else { passwordToUse = password1.getPassword(); } } Wallet wallet = this.bitcoinController.getModel().getActiveWallet(); if (wallet != null) { if (this.bitcoinController.getModel().getActiveWalletWalletInfo() != null) { // Only an unencrypted protobuf wallet can have a password added to it. if (this.bitcoinController.getModel().getActiveWalletWalletInfo().getWalletVersion() != MultiBitWalletVersion.PROTOBUF) { addPasswordPanel.setMessage1(this.bitcoinController.getLocaliser().getString( "addPasswordPanel.addPasswordFailed", new String[]{"Wallet is not protobuf.2"})); return; } } WalletData perWalletModelData = null; WalletInfoData walletInfoData; try { // Double check wallet is not busy then declare that the active // wallet is busy with the task perWalletModelData = this.bitcoinController.getModel().getActivePerWalletModelData(); walletInfoData = this.bitcoinController.getModel().getActiveWalletWalletInfo(); if (!perWalletModelData.isBusy()) { perWalletModelData.setBusy(true); perWalletModelData.setBusyTaskKey("addPasswordSubmitAction.text"); super.bitcoinController.fireWalletBusyChange(true); KeyCrypter keyCrypterToUse; if (wallet.getKeyCrypter() == null) { byte[] salt = new byte[KeyCrypterScrypt.SALT_LENGTH]; super.bitcoinController.getMultiBitService().getSecureRandom().nextBytes(salt); Protos.ScryptParameters.Builder scryptParametersBuilder = Protos.ScryptParameters.newBuilder().setSalt(ByteString.copyFrom(salt)); ScryptParameters scryptParameters = scryptParametersBuilder.build(); keyCrypterToUse = new KeyCrypterScrypt(scryptParameters); } else { keyCrypterToUse = wallet.getKeyCrypter(); } wallet.encrypt(keyCrypterToUse, keyCrypterToUse.deriveKey(CharBuffer.wrap(passwordToUse))); walletInfoData.setWalletVersion(MultiBitWalletVersion.PROTOBUF_ENCRYPTED); perWalletModelData.setDirty(true); FileHandler fileHandler = new FileHandler(super.bitcoinController); fileHandler.savePerWalletModelData(perWalletModelData, true); // Backup the private keys. privateKeysBackupFile = fileHandler.backupPrivateKeys(CharBuffer.wrap(passwordToUse)); // Backup the wallet and wallet info. BackupManager.INSTANCE.backupPerWalletModelData(fileHandler,perWalletModelData); // Ensure that any unencrypted wallet backups are file encrypted with the wallet password. BackupManager.INSTANCE.fileLevelEncryptUnencryptedWalletBackups(perWalletModelData, CharBuffer.wrap(passwordToUse)); } } catch (KeyCrypterException ede) { ede.printStackTrace(); addPasswordPanel.setMessage1(controller.getLocaliser().getString("addPasswordPanel.addPasswordFailed", new String[] { ede.getMessage() })); return; } catch (IOException ede) { // Notify the user that the private key backup failed. addPasswordPanel.setMessage2(controller.getLocaliser().getString( "changePasswordPanel.keysBackupFailed", new String[] { ede.getMessage() })); return; } finally { // Declare that wallet is no longer busy with the task. if (perWalletModelData != null) { perWalletModelData.setBusyTaskKey(null); perWalletModelData.setBusy(false); } super.bitcoinController.fireWalletBusyChange(false); } } controller.fireDataChangedUpdateNow(); // Success. SwingUtilities.invokeLater(new Runnable() { @Override public void run() { addPasswordPanel.clearMessages(); addPasswordPanel.clearPasswords(); addPasswordPanel.setMessage1(controller.getLocaliser().getString("addPasswordPanel.addPasswordSuccess")); if (privateKeysBackupFile != null) { try { addPasswordPanel.setMessage2(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 (this.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[]{this.bitcoinController.getLocaliser().getString(this.bitcoinController.getModel().getActivePerWalletModelData().getBusyTaskKey())})); } else { // Enable putValue(SHORT_DESCRIPTION, this.bitcoinController.getLocaliser().getString("addPasswordSubmitAction.text")); } } }