/** * 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.MultiBitBlockChain; import com.google.bitcoin.crypto.KeyCrypterException; import org.bitcoinj.wallet.Protos.Wallet.EncryptionType; import org.multibit.controller.bitcoin.BitcoinController; import org.multibit.file.PrivateKeysHandler; import org.multibit.file.Verification; import org.multibit.model.bitcoin.WalletBusyListener; import org.multibit.model.bitcoin.WalletData; import org.multibit.utils.ImageLoader; import org.multibit.viewsystem.swing.MultiBitFrame; import org.multibit.viewsystem.swing.view.panels.ExportPrivateKeysPanel; 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} exports the active wallets private keys. */ public class ExportPrivateKeysSubmitAction extends MultiBitSubmitAction implements WalletBusyListener { private static final Logger log = LoggerFactory.getLogger(ExportPrivateKeysSubmitAction.class); private static final long serialVersionUID = 1923492460598757765L; private ExportPrivateKeysPanel exportPrivateKeysPanel; private MultiBitFrame mainFrame; private PrivateKeysHandler privateKeysHandler; private JPasswordField walletPassword; private JPasswordField exportFilePassword; private JPasswordField exportFileRepeatPassword; /** * Creates a new {@link ExportPrivateKeysSubmitAction}. */ public ExportPrivateKeysSubmitAction(BitcoinController bitcoinController, ExportPrivateKeysPanel exportPrivateKeysPanel, ImageIcon icon, JPasswordField walletPassword, JPasswordField exportFilePassword, JPasswordField exportFileRepeatPassword, MultiBitFrame mainFrame) { super(bitcoinController, "showExportPrivateKeysAction.text.camel", "showExportPrivateKeysAction.tooltip", "showExportPrivateKeysAction.mnemonicKey", icon); this.exportPrivateKeysPanel = exportPrivateKeysPanel; this.walletPassword = walletPassword; this.exportFilePassword = exportFilePassword; this.exportFileRepeatPassword = exportFileRepeatPassword; this.mainFrame = mainFrame; // This action is a WalletBusyListener. super.bitcoinController.registerWalletBusyListener(this); walletBusyChange(super.bitcoinController.getModel().getActivePerWalletModelData().isBusy()); } /** * Export the private keys to a file. */ @Override public void actionPerformed(ActionEvent e) { if (abort()) { return; } exportPrivateKeysPanel.clearMessages(); // See if a wallet password is required and present. if (super.bitcoinController.getModel().getActiveWallet() != null && super.bitcoinController.getModel().getActiveWallet().getEncryptionType() == EncryptionType.ENCRYPTED_SCRYPT_AES) { if (walletPassword.getPassword() == null || walletPassword.getPassword().length == 0) { exportPrivateKeysPanel.setMessage1(controller.getLocaliser().getString( "showExportPrivateKeysAction.youMustEnterTheWalletPassword")); return; } try { // See if the password is the correct wallet password. if (!super.bitcoinController.getModel().getActiveWallet().checkPassword(CharBuffer.wrap(walletPassword.getPassword()))) { // The password supplied is incorrect. exportPrivateKeysPanel.setMessage1(controller.getLocaliser().getString( "createNewReceivingAddressSubmitAction.passwordIsIncorrect")); exportPrivateKeysPanel.setMessage2(" "); return; } } catch (KeyCrypterException kce) { exportPrivateKeysPanel.setMessage1(controller.getLocaliser().getString( "createNewReceivingAddressSubmitAction.passwordIsIncorrect")); exportPrivateKeysPanel.setMessage2(" "); } } // Get the required output file. String exportPrivateKeysFilename = exportPrivateKeysPanel.getOutputFilename(); // Check an output file was selected. if (exportPrivateKeysFilename == null || "".equals(exportPrivateKeysFilename)) { exportPrivateKeysPanel.setMessage1(controller.getLocaliser().getString( "showExportPrivateKeysAction.youMustSelectAnOutputFile")); return; } File exportPrivateKeysFile = new File(exportPrivateKeysFilename); privateKeysHandler = new PrivateKeysHandler(super.bitcoinController.getModel().getNetworkParameters()); boolean performEncryptionOfExportFile = false; CharSequence exportPasswordToUse = null; if (exportPrivateKeysPanel.requiresEncryption()) { // Get the passwords on the export file password fields. if (exportFilePassword.getPassword() == null || exportFilePassword.getPassword().length == 0) { // Notify must enter a password. exportPrivateKeysPanel.setMessage1(controller.getLocaliser() .getString("showExportPrivateKeysAction.enterPasswords")); return; } else { if (!Arrays.areEqual(exportFilePassword.getPassword(), exportFileRepeatPassword.getPassword())) { // Notify user passwords are different. exportPrivateKeysPanel.setMessage1(controller.getLocaliser().getString( "showExportPrivateKeysAction.passwordsAreDifferent")); return; } else { // Perform encryption. performEncryptionOfExportFile = true; exportPasswordToUse = CharBuffer.wrap(exportFilePassword.getPassword()); } } } // Check on file overwrite. if (exportPrivateKeysFile.exists()) { String yesText = controller.getLocaliser().getString("showOpenUriView.yesText"); String noText = controller.getLocaliser().getString("showOpenUriView.noText"); String questionText = controller.getLocaliser().getString("showExportPrivateKeysAction.thisFileExistsOverwrite", new Object[] { exportPrivateKeysFile.getName() }); String questionTitle = controller.getLocaliser().getString("showExportPrivateKeysAction.thisFileExistsOverwriteTitle"); int selection = JOptionPane.showOptionDialog(mainFrame, questionText, questionTitle, JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, ImageLoader.createImageIcon(ImageLoader.QUESTION_MARK_ICON_FILE), new String[] { yesText, noText }, noText); if (selection != JOptionPane.YES_OPTION) { return; } } // 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("showExportPrivateKeysAction.text.camel"); exportPrivateKeysPanel.setMessage1(controller.getLocaliser().getString( "exportPrivateKeysSubmitAction.exportingPrivateKeys")); exportPrivateKeysPanel.setMessage2(""); super.bitcoinController.fireWalletBusyChange(true); CharSequence walletPasswordToUse = null; if (walletPassword.getPassword() != null) { walletPasswordToUse = CharBuffer.wrap(walletPassword.getPassword()); } exportPrivateKeysInBackground(exportPrivateKeysFile, performEncryptionOfExportFile, exportPasswordToUse, walletPasswordToUse); } } /** * Export the private keys in a background Swing worker thread. */ private void exportPrivateKeysInBackground(final File exportPrivateKeysFile, final boolean performEncryptionOfExportFile, final CharSequence exportPasswordToUse, final CharSequence walletPassword) { final WalletData finalPerWalletModelData = super.bitcoinController.getModel().getActivePerWalletModelData(); final ExportPrivateKeysPanel finalExportPanel = exportPrivateKeysPanel; final BitcoinController finalBitcoinController = super.bitcoinController; SwingWorker<Boolean, Void> worker = new SwingWorker<Boolean, Void>() { private String uiMessage1 = null; private String uiMessage2 = null; @Override protected Boolean doInBackground() throws Exception { Boolean successMeasure = Boolean.FALSE; MultiBitBlockChain blockChain = null; if (finalBitcoinController.getMultiBitService() != null) { blockChain = finalBitcoinController.getMultiBitService().getChain(); } try { privateKeysHandler.exportPrivateKeys(exportPrivateKeysFile, finalBitcoinController.getModel().getActivePerWalletModelData() .getWallet(), blockChain, performEncryptionOfExportFile, exportPasswordToUse, walletPassword); // Success. uiMessage1 = controller.getLocaliser().getString("showExportPrivateKeysAction.privateKeysExportSuccess"); // Perform a verification on the exported file to see if it // is correct. Verification verification = privateKeysHandler.verifyExportFile(exportPrivateKeysFile, finalBitcoinController.getModel() .getActivePerWalletModelData().getWallet(), blockChain, exportPasswordToUse, walletPassword); uiMessage2 = controller.getLocaliser().getString(verification.getMessageKey(), verification.getMessageData()); successMeasure = true; } catch (IOException ioe) { logError(ioe); } return successMeasure; } private void logError(Exception e) { log.error(e.getClass().getName() + " " + e.getMessage()); e.printStackTrace(); uiMessage1 = controller.getLocaliser().getString("importPrivateKeysSubmitAction.privateKeysImportFailure", new Object[] { e.getMessage() }); uiMessage2 = ""; } @Override protected void done() { try { Boolean wasSuccessful = get(); if (finalExportPanel != null && uiMessage1 != null) { finalExportPanel.setMessage1(uiMessage1); } if (finalExportPanel != null && uiMessage2 != null) { finalExportPanel.setMessage2(uiMessage2); } // Clear the passwords if the export was successful and the user is still // looking at the same wallet as at start. if (wasSuccessful && finalPerWalletModelData.getWalletFilename().equals(finalBitcoinController.getModel().getActiveWalletFilename())) { finalExportPanel.clearPasswords(); } } catch (Exception e) { // Not really used but caught so that SwingWorker shuts down cleanly. log.error(e.getClass() + " " + e.getMessage()); } finally { // Declare that wallet is no longer busy with the task. finalPerWalletModelData.setBusyTaskKey(null); finalPerWalletModelData.setBusy(false); finalBitcoinController.fireWalletBusyChange(false); } } }; log.debug("Exporting private keys in background SwingWorker thread"); worker.execute(); } @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, controller.getLocaliser().getString("multiBitSubmitAction.walletIsBusy", new Object[]{controller.getLocaliser().getString(this.bitcoinController.getModel().getActivePerWalletModelData().getBusyTaskKey())})); setEnabled(false); } else { // Enable putValue(SHORT_DESCRIPTION, controller.getLocaliser().getString("exportPrivateKeysSubmitAction.text")); setEnabled(true); } } }