/**
* Copyright 2011 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.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.ComponentOrientation;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.EnumMap;
import java.util.Map;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.border.Border;
import javax.xml.bind.DatatypeConverter;
import org.multibit.controller.Controller;
import org.multibit.controller.bitcoin.BitcoinController;
import org.multibit.file.BackupManager;
import org.multibit.file.FileHandler;
import org.multibit.file.WalletLoadException;
import org.multibit.file.WalletSaveException;
import org.multibit.message.Message;
import org.multibit.message.MessageManager;
import org.multibit.model.bitcoin.BitcoinModel;
import org.multibit.model.bitcoin.WalletData;
import org.multibit.model.bitcoin.WalletInfoData;
import org.multibit.store.MultiBitWalletVersion;
import org.multibit.store.WalletVersionException;
import org.multibit.viewsystem.swing.MultiBitFrame;
import org.multibit.viewsystem.swing.view.WalletFileFilter;
import org.multibit.viewsystem.swing.view.components.FontSizer;
import org.multibit.viewsystem.swing.view.panels.HelpContentsPanel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.bouncycastle.util.Arrays;
import threshold.mr04.Alice;
import threshold.mr04.SignatureTest;
import threshold.mr04.data.PublicParameters;
import com.google.bitcoin.core.MakeCertificate;
import com.google.bitcoin.core.RemoteECKey;
import com.google.bitcoin.core.Utils;
import com.google.bitcoin.core.Wallet;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.EncodeHintType;
/**
* This {@link Action} creates a new wallet.
*/
public class CreateRemoteWalletSubmitAction extends AbstractAction {
private static final Logger log = LoggerFactory.getLogger(CreateRemoteWalletSubmitAction.class);
private static final long serialVersionUID = 1923492460523457765L;
private static final int TAM_QRCODE = 300;
private static String KEYSTORE_FILENAME = "mykeystore.bks";
private static String KEYSTORE_PASSWORD = "password";
private final Controller controller;
private final BitcoinController bitcoinController;
private MultiBitFrame mainFrame;
private Font adjustedFont;
/**
* Creates a new {@link CreateRemoteWalletSubmitAction}.
*/
public CreateRemoteWalletSubmitAction(BitcoinController bitcoinController, ImageIcon icon, MultiBitFrame mainFrame) {
super(bitcoinController.getLocaliser().getString("createNewRemoteWalletAction.text"), icon);
this.bitcoinController = bitcoinController;
this.controller = this.bitcoinController;
this.mainFrame = mainFrame;
MnemonicUtil mnemonicUtil = new MnemonicUtil(controller.getLocaliser());
putValue(SHORT_DESCRIPTION, HelpContentsPanel.createTooltipTextForMenuItem(controller.getLocaliser().getString("createNewWalletAction.tooltip")));
putValue(MNEMONIC_KEY, mnemonicUtil.getMnemonic("createNewWalletAction.text"));
}
/**
* Create new wallet.
*/
@Override
public void actionPerformed(ActionEvent e) {
if (mainFrame != null) {
mainFrame.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
}
setEnabled(false);
try {
// Create a file save dialog.
JFileChooser.setDefaultLocale(controller.getLocaliser().getLocale());
JFileChooser fileChooser = new JFileChooser();
fileChooser.setLocale(controller.getLocaliser().getLocale());
fileChooser.setDialogTitle(controller.getLocaliser().getString("createNewWalletAction.tooltip"));
adjustedFont = FontSizer.INSTANCE.getAdjustedDefaultFont();
if (adjustedFont != null) {
setFileChooserFont(new Container[] {fileChooser});
}
fileChooser.applyComponentOrientation(ComponentOrientation.getOrientation(controller.getLocaliser().getLocale()));
if (this.bitcoinController.getModel().getActiveWalletFilename() != null) {
fileChooser.setCurrentDirectory(new File(this.bitcoinController.getModel().getActiveWalletFilename()));
}
fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
fileChooser.setFileFilter(new WalletFileFilter(controller));
String defaultFileName = fileChooser.getCurrentDirectory().getAbsoluteFile() + File.separator
+ controller.getLocaliser().getString("saveWalletAsView.untitled") + "." + BitcoinModel.WALLET_FILE_EXTENSION;
fileChooser.setSelectedFile(new File(defaultFileName));
fileChooser.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
int returnVal = fileChooser.showSaveDialog(mainFrame);
String newWalletFilename = null;
if (returnVal == JFileChooser.APPROVE_OPTION) {
File file = fileChooser.getSelectedFile();
if (file != null) {
newWalletFilename = file.getAbsolutePath();
createNewWallet(newWalletFilename);
}
}
} finally {
setEnabled(true);
if (mainFrame != null) {
mainFrame.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
}
}
public void createNewWallet(String newWalletFilename) {
String message;
if (new File(newWalletFilename).isDirectory()) {
message = controller.getLocaliser().getString("createNewWalletAction.walletFileIsADirectory",
new Object[] { newWalletFilename });
log.debug(message);
MessageManager.INSTANCE.addMessage(new Message(message));
return;
}
// If the filename has no extension, put on the wallet extension.
if (!newWalletFilename.contains(".")) {
newWalletFilename = newWalletFilename + "." + BitcoinModel.WALLET_FILE_EXTENSION;
}
File newWalletFile = new File(newWalletFilename);
boolean theWalletWasNotOpenedSuccessfully = false;
try {
// If file exists, load the existing wallet.
if (newWalletFile.exists()) {
WalletData perWalletModelData = this.bitcoinController.getFileHandler().loadFromFile(newWalletFile);
if (perWalletModelData != null) {
// Use the existing wallet.
this.bitcoinController.addWalletFromFilename(newWalletFile.getAbsolutePath());
this.bitcoinController.getModel().setActiveWalletByFilename(newWalletFilename);
controller.getModel().setUserPreference(BitcoinModel.GRAB_FOCUS_FOR_ACTIVE_WALLET, "true");
controller.fireRecreateAllViews(true);
controller.fireDataChangedUpdateNow();
}
} else {
// Create a new wallet - protobuf.2 initially for backwards compatibility.
Wallet newWallet = new Wallet(this.bitcoinController.getModel().getNetworkParameters());
SecureRandom prGen = new SecureRandom();
byte[] oneTimePass = new byte[256];
prGen.nextBytes(oneTimePass);
SignatureTest t = new SignatureTest(2);
PublicParameters params = new PublicParameters(SignatureTest.CURVE, t.nHat, t.kPrime, t.h1, t.h2,
t.alicesPallierPubKey, t.otherPallierPubKey);
Alice alice = new Alice(t.aliceShare, t.publicKey, new SecureRandom(), t.paillier, params);
File keystore = new File(KEYSTORE_FILENAME);
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
KeyStore ks = KeyStore.getInstance("BKS");
if (!keystore.exists()) {
MakeCertificate.generateSelfSignedCertificate("compTLSCert", keystore, KEYSTORE_PASSWORD);
}
ks.load(new FileInputStream(keystore), KEYSTORE_PASSWORD.toCharArray());
X509Certificate cert = (X509Certificate) ks.getCertificate("compTLSCert");
byte[] certBytes = cert.getEncoded();
System.out.println("cert is " + certBytes.length + " bytes");
byte[] fullBytes = Arrays.copyOf(oneTimePass, oneTimePass.length + certBytes.length);
System.arraycopy(certBytes, 0, fullBytes, oneTimePass.length, certBytes.length);
String fullString = DatatypeConverter.printBase64Binary(fullBytes);
QRCodeWriter writer = new QRCodeWriter();
Map<EncodeHintType, Object> hints = new EnumMap<EncodeHintType, Object>(EncodeHintType.class);
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
hints.put(EncodeHintType.MARGIN, 0);
BitMatrix matrix = writer.encode(fullString, BarcodeFormat.QR_CODE, 300, 300, hints);
BufferedImage image = new BufferedImage(TAM_QRCODE, TAM_QRCODE, BufferedImage.TYPE_INT_RGB);
image.createGraphics();
Graphics2D graphics = (Graphics2D) image.getGraphics();
graphics.setColor(Color.WHITE);
graphics.fillRect(0, 0, TAM_QRCODE, TAM_QRCODE);
graphics.setColor(Color.BLACK);
for (int i = matrix.getTopLeftOnBit()[0]; i < TAM_QRCODE; i++) {
for (int j = matrix.getTopLeftOnBit()[1]; j < TAM_QRCODE; j++) {
if (matrix.get(i, j)) {
graphics.fillRect(i, j, 1, 1);
}
}
}
ByteArrayOutputStream os = new ByteArrayOutputStream();
ImageIO.write(image, "png", os);
Icon icon = new ImageIcon(image);
JLabel iconLabel = new JLabel(icon);
JPanel iconPanel = new JPanel(new GridBagLayout());
iconPanel.add(iconLabel);
JPanel mainPanel = new JPanel(new BorderLayout());
JLabel label = new JLabel("Capture this image to pair with phone. Warning: This will never be shown again");
Border paddingBorder = BorderFactory.createEmptyBorder(30,10,10,10);
label.setBorder(paddingBorder);
mainPanel.add(label);
mainPanel.add(iconPanel, BorderLayout.NORTH);
log.debug("Showing wallet qr panel");
JOptionPane.showMessageDialog(null, mainPanel, "Two Factor", JOptionPane.PLAIN_MESSAGE);
log.debug("Showed wallet qr panel");
log.debug("Trying to create ECKey");
RemoteECKey newKey = new RemoteECKey(alice, params, t.bobShare, t.publicKey, oneTimePass, keystore, KEYSTORE_PASSWORD);
log.debug("Finished trying to create ECKey");
String filename = Utils.bytesToHexString(newKey.getPubKeyHash());
FileOutputStream fileOut = new FileOutputStream("/Users/hkalodner/btfa_work/" + filename + ".key");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(newKey);
out.close();
fileOut.close();
log.debug("Created ECKey");
newWallet.addKey(newKey);
WalletData perWalletModelData = new WalletData();
perWalletModelData.setWalletInfo(new WalletInfoData(newWalletFilename, newWallet, MultiBitWalletVersion.PROTOBUF));
perWalletModelData.setWallet(newWallet);
perWalletModelData.setWalletFilename(newWalletFilename);
perWalletModelData.setWalletDescription(controller.getLocaliser().getString(
"createNewWalletSubmitAction.defaultDescription"));
this.bitcoinController.getFileHandler().savePerWalletModelData(perWalletModelData, true);
// Start using the new file as the wallet.
this.bitcoinController.addWalletFromFilename(newWalletFile.getAbsolutePath());
this.bitcoinController.getModel().setActiveWalletByFilename(newWalletFilename);
controller.getModel().setUserPreference(BitcoinModel.GRAB_FOCUS_FOR_ACTIVE_WALLET, "true");
// Save the user properties to disk.
FileHandler.writeUserPreferences(this.bitcoinController);
log.debug("User preferences with new wallet written successfully");
// Backup the wallet and wallet info.
BackupManager.INSTANCE.backupPerWalletModelData(bitcoinController.getFileHandler(), perWalletModelData);
controller.fireRecreateAllViews(true);
controller.fireDataChangedUpdateNow();
}
} catch (WalletLoadException e) {
e.printStackTrace();
message = controller.getLocaliser().getString("createNewWalletAction.walletCouldNotBeCreated",
new Object[] { newWalletFilename, e.getMessage() });
log.error(message);
MessageManager.INSTANCE.addMessage(new Message(message));
theWalletWasNotOpenedSuccessfully = true;
} catch (WalletSaveException e) {
e.printStackTrace();
message = controller.getLocaliser().getString("createNewWalletAction.walletCouldNotBeCreated",
new Object[] { newWalletFilename, e.getMessage() });
log.error(message);
MessageManager.INSTANCE.addMessage(new Message(message));
theWalletWasNotOpenedSuccessfully = true;
} catch (WalletVersionException e) {
e.printStackTrace();
message = controller.getLocaliser().getString("createNewWalletAction.walletCouldNotBeCreated",
new Object[] { newWalletFilename, e.getMessage() });
log.error(message);
MessageManager.INSTANCE.addMessage(new Message(message));
theWalletWasNotOpenedSuccessfully = true;
} catch (IOException e) {
e.printStackTrace();
message = controller.getLocaliser().getString("createNewWalletAction.walletCouldNotBeCreated",
new Object[] { newWalletFilename, e.getMessage() });
log.error(message);
MessageManager.INSTANCE.addMessage(new Message(message));
theWalletWasNotOpenedSuccessfully = true;
} catch (KeyStoreException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (CertificateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (WriterException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
if (theWalletWasNotOpenedSuccessfully) {
WalletData loopData = this.bitcoinController.getModel().getPerWalletModelDataByWalletFilename(newWalletFilename);
if (loopData != null) {
// Clear the backup wallet filename - this prevents it being automatically overwritten.
if (loopData.getWalletInfo() != null) {
loopData.getWalletInfo().put(BitcoinModel.WALLET_BACKUP_FILE, "");
}
}
}
}
private void setFileChooserFont(Component[] comp) {
for (int x = 0; x < comp.length; x++) {
if (comp[x] instanceof Container)
setFileChooserFont(((Container) comp[x]).getComponents());
try {
comp[x].setFont(adjustedFont);
} catch (Exception e) {
}// do nothing
}
}
}