/** * 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 java.io.File; import java.util.Collection; import java.util.UUID; import junit.framework.TestCase; import org.junit.Test; import org.multibit.controller.Controller; import org.multibit.controller.bitcoin.BitcoinController; import org.multibit.controller.BitcoinControllerTest; import org.multibit.controller.SimpleWalletBusyListener; import org.multibit.file.PrivateKeyAndDate; import org.multibit.file.PrivateKeysHandler; import org.multibit.message.Message; import org.multibit.message.MessageManager; import org.multibit.viewsystem.swing.view.components.FontSizer; import org.multibit.viewsystem.swing.view.panels.ExportPrivateKeysPanel; import com.google.bitcoin.core.Utils; import com.google.bitcoin.crypto.EncryptedPrivateKey; import com.google.bitcoin.crypto.KeyCrypter; import com.google.bitcoin.crypto.KeyCrypterException; import org.multibit.CreateControllers; public class ExportPrivateKeysSubmitActionTest extends TestCase { private static final String EXPECTED_ENTER_THE_WALLET_PASSWORD = "Enter the wallet password"; private static final String EXPECTED_YOU_MUST_SELECT_AN_OUTPUT_FILE = "You must select an output file"; private static final String EXPECTED_ENTER_THE_EXPORT_FILE_PASSWORD = "Enter the password you want to use for the export file"; private static final String EXPECTED_PASSWORDS_DO_NOT_MATCH = "The password and repeat password do not match"; private static final String EXPECTED_COULD_NOT_DECRYPT_INPUT_STRING = "Could not decrypt input string"; private static final String EXPECTED_THE_PRIVATE_KEYS_WERE_EXPORTED = "The private keys were exported."; private static final String EXPECTED_THE_EXPORT_FILE_COULD_BE_READ_IN_CORRECTLY = "The export file could be read in correctly and the private keys match the wallet contents"; public static final CharSequence EXPORT_FILE_PASSWORD = "the quick brown fox jumps over the lazy dog 0123456789"; public static final CharSequence WALLET_PASSWORD = "the unbelievable lightness of being"; public static final CharSequence WRONG_PASSWORD = "this is the wrong password"; @Test public void testExportPrivateKeysWithNonEncryptedWallet() throws Exception { // Create MultiBit controller. final CreateControllers.Controllers controllers = CreateControllers.createControllers(); BitcoinController controller = controllers.bitcoinController; // Create a new wallet and put it in the model as the active wallet. ActionTestUtils.createNewActiveWallet(controller, "testExportPrivateKeysWithNonEncryptedWallet", false, null); // Hook up a wallet busy listener. SimpleWalletBusyListener walletBusyListener = new SimpleWalletBusyListener(); controller.registerWalletBusyListener(walletBusyListener); // Create a new ExportPrivateKeysSubmitAction to test. FontSizer.INSTANCE.initialise(controller); ExportPrivateKeysPanel exportPanel = new ExportPrivateKeysPanel(controller, null); ExportPrivateKeysSubmitAction exportAction = exportPanel.getExportPrivateKeySubmitAction(); assertNotNull("exportAction was not created successfully", exportAction); assertEquals("Wrong number of keys at wallet creation", 1, controller.getModel().getActiveWallet().getKeychain().size()); assertTrue("Wallet password is enabled when it should not be", !exportPanel.isWalletPasswordFieldEnabled()); // Execute - this is with an unencrypted wallet and default settings. exportAction.actionPerformed(null); assertEquals("Wrong message1 after default export execute", EXPECTED_YOU_MUST_SELECT_AN_OUTPUT_FILE, exportPanel.getMessageText1()); assertEquals("Wrong message2 after default export execute", "", exportPanel.getMessageText2().trim()); // Set the output file name. String outputFilename1 = controller.getModel().getActiveWalletFilename() + "-" + UUID.randomUUID().toString() + ".key"; exportPanel.setOutputFilename(outputFilename1); // Execute - this should now complain that no export file password is set (as password protect export file is selected by default). exportAction.actionPerformed(null); assertEquals("Wrong message1 after no password set execute", EXPECTED_ENTER_THE_EXPORT_FILE_PASSWORD, exportPanel.getMessageText1()); assertEquals("Wrong message2 after no password set execute", "", exportPanel.getMessageText2().trim()); // Set the first export file password. exportPanel.setExportPassword(EXPORT_FILE_PASSWORD); // Execute = this is with one only of the export file passwords set. exportAction.actionPerformed(null); assertEquals("Wrong message1 after no password set execute", EXPECTED_PASSWORDS_DO_NOT_MATCH, exportPanel.getMessageText1()); assertEquals("Wrong message2 after no password set execute", "", exportPanel.getMessageText2().trim()); // Set the repeat export file password. exportPanel.setRepeatExportPassword(EXPORT_FILE_PASSWORD); // Check the export file currently does not exist. assertTrue("Encrypted export file exists when it should not", !(new File(outputFilename1)).exists()); // Execute = this should actually write the encrypted export file. exportAction.actionPerformed(null); BitcoinControllerTest.waitForWalletNotBusy(walletBusyListener); assertTrue("Encrypted export file does not exist when it should", (new File(outputFilename1)).exists()); assertEquals("Wrong message1 after encrypted export is good to go", EXPECTED_THE_PRIVATE_KEYS_WERE_EXPORTED, exportPanel.getMessageText1()); assertEquals("Wrong message2 after encrypted export is good to go", EXPECTED_THE_EXPORT_FILE_COULD_BE_READ_IN_CORRECTLY, exportPanel.getMessageText2().trim()); // Try to read in the encrypted exported private key file with no password - this should fail. PrivateKeysHandler privateKeysHandler = new PrivateKeysHandler(controller.getModel().getNetworkParameters()); Collection<PrivateKeyAndDate> privateKeyAndDates = null; try { privateKeyAndDates = privateKeysHandler.readInPrivateKeys(new File(outputFilename1), null); fail("An encrypted export file was read in with no password. Fail."); } catch (KeyCrypterException kce) { // This is what should happen. assertTrue("Unexpected exception thrown when decoding export file with no password", kce.getMessage().indexOf(EXPECTED_COULD_NOT_DECRYPT_INPUT_STRING) > -1); } // Try to read in the encrypted exported private key file with the wrong password - this should fail. try { privateKeyAndDates = privateKeysHandler.readInPrivateKeys(new File(outputFilename1), WRONG_PASSWORD); fail("An encrypted export file was read in with the wrong password. Fail."); } catch (KeyCrypterException kce) { // This is what should happen. assertTrue("Unexpected exception thrown when decoding export file with wrong password", kce.getMessage().indexOf(EXPECTED_COULD_NOT_DECRYPT_INPUT_STRING) > -1); } // Read in the encrypted exported private key file with the correct password. privateKeysHandler = new PrivateKeysHandler(controller.getModel().getNetworkParameters()); privateKeyAndDates = privateKeysHandler.readInPrivateKeys(new File(outputFilename1), EXPORT_FILE_PASSWORD); assertEquals("Wrong number of keys read in from encrypted export file", 1, privateKeyAndDates.size()); assertEquals("Wrong private key read in from encrypted export file", Utils.bytesToHexString(controller.getModel().getActiveWallet().getKeychain().iterator().next().getPrivKeyBytes()), Utils.bytesToHexString(privateKeyAndDates.iterator().next().getKey().getPrivKeyBytes())); // Set the export file password protect radio to output unencrypted. exportPanel.getDoNotPasswordProtect().setSelected(true); // Set the output file name. String outputFilename2 = controller.getModel().getActiveWalletFilename() + "-" + UUID.randomUUID().toString() + ".key"; exportPanel.setOutputFilename(outputFilename2); // Check the export file currently does not exist. assertTrue("Unencrypted export file exists when it should not", !(new File(outputFilename2)).exists()); // Execute = this should actually write the unencrypted export file. exportAction.actionPerformed(null); BitcoinControllerTest.waitForWalletNotBusy(walletBusyListener); assertTrue("Unencrypted export file does not exist when it should", (new File(outputFilename2)).exists()); assertEquals("Wrong message1 after unencrypted export is good to go", EXPECTED_THE_PRIVATE_KEYS_WERE_EXPORTED, exportPanel.getMessageText1()); assertEquals("Wrong message2 after unencrypted export is good to go", EXPECTED_THE_EXPORT_FILE_COULD_BE_READ_IN_CORRECTLY, exportPanel.getMessageText2().trim()); // Read in the unencrypted exported private key file. privateKeyAndDates = privateKeysHandler.readInPrivateKeys(new File(outputFilename2), null); assertEquals("Wrong number of keys read in from unencrypted export file", 1, privateKeyAndDates.size()); assertEquals("Wrong private key read in from unencrypted export file", Utils.bytesToHexString(controller.getModel().getActiveWallet().getKeychain().iterator().next().getPrivKeyBytes()), Utils.bytesToHexString(privateKeyAndDates.iterator().next().getKey().getPrivKeyBytes())); } @Test public void testExportPrivateKeysWithEncryptedWallet() throws Exception { // Create MultiBit controller. final CreateControllers.Controllers controllers = CreateControllers.createControllers(); BitcoinController controller = controllers.bitcoinController; // Create a new encrypted wallet and put it in the model as the active wallet. ActionTestUtils.createNewActiveWallet(controller, "testExportPrivateKeysWithEncryptedWallet", true, WALLET_PASSWORD); // Hook up a wallet busy listener. SimpleWalletBusyListener walletBusyListener = new SimpleWalletBusyListener(); controller.registerWalletBusyListener(walletBusyListener); // Remember the private keys for the key - for comparision later. // Copy the private key bytes for checking later. EncryptedPrivateKey encryptedPrivateKey = controller.getModel().getActiveWallet().getKeychain().get(0).getEncryptedPrivateKey(); KeyCrypter keyCrypter = controller.getModel().getActiveWallet().getKeyCrypter(); byte[] originalPrivateKeyBytes = keyCrypter.decrypt(encryptedPrivateKey, keyCrypter.deriveKey(WALLET_PASSWORD)); // Create a new ExportPrivateKeysSubmitAction to test. FontSizer.INSTANCE.initialise(controller); ExportPrivateKeysPanel exportPanel = new ExportPrivateKeysPanel(controller, null); ExportPrivateKeysSubmitAction exportAction = exportPanel.getExportPrivateKeySubmitAction(); assertNotNull("exportAction was not created successfully", exportAction); assertEquals("Wrong number of keys at wallet creation", 1, controller.getModel().getActiveWallet().getKeychain().size()); assertTrue("Wallet password is not enabled when it should be", exportPanel.isWalletPasswordFieldEnabled()); // Execute - this is with an encrypted wallet and default settings. (No wallet password set). exportAction.actionPerformed(null); assertEquals("Wrong message1 after default export execute", EXPECTED_ENTER_THE_WALLET_PASSWORD, exportPanel.getMessageText1()); assertEquals("Wrong message2 after default export execute", "", exportPanel.getMessageText2().trim()); // Set the wallet password. exportPanel.setWalletPassword(WALLET_PASSWORD); // Execute - this should now complain that no export file is set. exportAction.actionPerformed(null); assertEquals("Wrong message1 after password set execute", EXPECTED_YOU_MUST_SELECT_AN_OUTPUT_FILE, exportPanel.getMessageText1()); assertEquals("Wrong message2 after password set execute", "", exportPanel.getMessageText2().trim()); // Set the output file name. String outputFilename1 = controller.getModel().getActiveWalletFilename() + "-" + UUID.randomUUID().toString() + ".key"; exportPanel.setOutputFilename(outputFilename1); // Execute - this should now complain that no export file password is set (as password protect export file is selected by default). exportAction.actionPerformed(null); assertEquals("Wrong message1 after no password set execute", EXPECTED_ENTER_THE_EXPORT_FILE_PASSWORD, exportPanel.getMessageText1()); assertEquals("Wrong message2 after no password set execute", "", exportPanel.getMessageText2().trim()); // Set the first export file password. exportPanel.setExportPassword(EXPORT_FILE_PASSWORD); // Execute = this is with one only of the export file passwords set. exportAction.actionPerformed(null); assertEquals("Wrong message1 after no password set execute", EXPECTED_PASSWORDS_DO_NOT_MATCH, exportPanel.getMessageText1()); assertEquals("Wrong message2 after no password set execute", "", exportPanel.getMessageText2().trim()); // Set the repeat export file password. exportPanel.setRepeatExportPassword(EXPORT_FILE_PASSWORD); // Check the export file currently does not exist. assertTrue("Encrypted export file exists when it should not", !(new File(outputFilename1)).exists()); // Execute = this should actually write the encrypted export file. exportAction.actionPerformed(null); BitcoinControllerTest.waitForWalletNotBusy(walletBusyListener); assertTrue("Encrypted export file does not exist when it should", (new File(outputFilename1)).exists()); assertEquals("Wrong message1 after encrypted export is good to go", EXPECTED_THE_PRIVATE_KEYS_WERE_EXPORTED, exportPanel.getMessageText1()); assertEquals("Wrong message2 after encrypted export is good to go", EXPECTED_THE_EXPORT_FILE_COULD_BE_READ_IN_CORRECTLY, exportPanel.getMessageText2().trim()); // Try to read in the encrypted exported private key file with no export file password - this should fail. PrivateKeysHandler privateKeysHandler = new PrivateKeysHandler(controller.getModel().getNetworkParameters()); Collection<PrivateKeyAndDate> privateKeyAndDates = null; try { privateKeyAndDates = privateKeysHandler.readInPrivateKeys(new File(outputFilename1), null); fail("An encrypted export file was read in with no export file password. Fail."); } catch (KeyCrypterException kce) { // This is what should happen. assertTrue("Unexpected exception thrown when decoding export file with no export file password", kce.getMessage().indexOf(EXPECTED_COULD_NOT_DECRYPT_INPUT_STRING) > -1); } // Try to read in the encrypted exported private key file with the wrong export file password - this should fail. try { privateKeyAndDates = privateKeysHandler.readInPrivateKeys(new File(outputFilename1), WRONG_PASSWORD); fail("An encrypted export file was read in with the wrong export file password. Fail."); } catch (KeyCrypterException kce) { // This is what should happen. assertTrue("Unexpected exception thrown when decoding export file with wrong export file password", kce.getMessage().indexOf(EXPECTED_COULD_NOT_DECRYPT_INPUT_STRING) > -1); } // Read in the encrypted exported private key file with the correct export file password. privateKeysHandler = new PrivateKeysHandler(controller.getModel().getNetworkParameters()); privateKeyAndDates = privateKeysHandler.readInPrivateKeys(new File(outputFilename1), EXPORT_FILE_PASSWORD); assertEquals("Wrong number of keys read in from encrypted export file", 1, privateKeyAndDates.size()); assertEquals("Wrong private key read in from encrypted export file", Utils.bytesToHexString(originalPrivateKeyBytes), Utils.bytesToHexString(privateKeyAndDates.iterator().next().getKey().getPrivKeyBytes())); // Set the export file password protect radio to output unencrypted. exportPanel.getDoNotPasswordProtect().setSelected(true); // Set the wallet password. exportPanel.setWalletPassword(WALLET_PASSWORD); // Set the output file name. String outputFilename2 = controller.getModel().getActiveWalletFilename() + "-" + UUID.randomUUID().toString() + ".key"; exportPanel.setOutputFilename(outputFilename2); // Check the export file currently does not exist. assertTrue("Unencrypted export file exists when it should not", !(new File(outputFilename2)).exists()); // Execute = this should actually write the unencrypted export file. exportAction.actionPerformed(null); BitcoinControllerTest.waitForWalletNotBusy(walletBusyListener); assertTrue("Unencrypted export file does not exist when it should", (new File(outputFilename2)).exists()); assertEquals("Wrong message1 after unencrypted export is good to go", EXPECTED_THE_PRIVATE_KEYS_WERE_EXPORTED, exportPanel.getMessageText1()); assertEquals("Wrong message2 after unencrypted export is good to go", EXPECTED_THE_EXPORT_FILE_COULD_BE_READ_IN_CORRECTLY, exportPanel.getMessageText2().trim()); // Read in the unencrypted exported private key file. privateKeyAndDates = privateKeysHandler.readInPrivateKeys(new File(outputFilename2), null); assertEquals("Wrong number of keys read in from unencrypted export file", 1, privateKeyAndDates.size()); assertEquals("Wrong private key read in from unencrypted export file", Utils.bytesToHexString(originalPrivateKeyBytes), Utils.bytesToHexString(privateKeyAndDates.iterator().next().getKey().getPrivKeyBytes())); } @Test public void testNoWalletSelected() throws Exception { // Create MultiBit controller. final CreateControllers.Controllers controllers = CreateControllers.createControllers(); BitcoinController controller = controllers.bitcoinController; // This test runs against an empty PerWalletModelDataList. assertTrue("There was an active wallet when there should not be", controller.getModel().thereIsNoActiveWallet()); // Create a new ExportPrivateKeysSubmitAction to test. FontSizer.INSTANCE.initialise(controller); ExportPrivateKeysPanel exportPrivateKeysPanel = new ExportPrivateKeysPanel(controller, null); ExportPrivateKeysSubmitAction exportPrivateKeysSubmitAction = exportPrivateKeysPanel.getExportPrivateKeySubmitAction(); assertNotNull("exportPrivateKeysSubmitAction was not created successfully", exportPrivateKeysSubmitAction); // Execute. exportPrivateKeysSubmitAction.actionPerformed(null); Object[] messages = MessageManager.INSTANCE.getMessages().toArray(); assertTrue("There were no messages but there should have been", messages != null && messages.length > 0); assertEquals("Wrong message after receive bitcoin confirm with no active wallet", ResetTransactionsSubmitActionTest.EXPECTED_NO_WALLET_IS_SELECTED, ((Message)messages[messages.length - 1]).getText()); } }