/* * Commons eID Project. * Copyright (C) 2008-2013 FedICT. * Copyright (C) 2009-2015 e-Contract.be BVBA. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version * 3.0 as published by the Free Software Foundation. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, see * http://www.gnu.org/licenses/. */ package be.fedict.commons.eid.dialogs; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.DisplayMode; import java.awt.FlowLayout; import java.awt.Frame; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.util.Arrays; import java.util.Locale; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPasswordField; import javax.swing.UIManager; import javax.swing.border.Border; import be.fedict.commons.eid.client.PINPurpose; import be.fedict.commons.eid.client.spi.BeIDCardUI; import be.fedict.commons.eid.client.spi.UserCancelledException; import be.fedict.commons.eid.dialogs.Messages.MESSAGE_ID; /** * Default Implementation of BeIDCardUI Interface * * @author Frank Cornelis * @author Frank Marien * */ public class DefaultBeIDCardUI implements BeIDCardUI { public static final int MIN_PIN_SIZE = 4; public static final int MAX_PIN_SIZE = 12; public static final int PUK_SIZE = 6; private static final String OPERATION_CANCELLED = "operation cancelled."; // TODO can pinPadFrame and secureReaderTransactionFrame be on-screen at the // same time? if not can be one member var and one dispose method private Component parentComponent; private JFrame pinPadFrame; private JFrame secureReaderTransactionFrame; private Locale locale; private Messages messages; public DefaultBeIDCardUI() { this(null); } public DefaultBeIDCardUI(Messages messages) { this(null, messages); } public DefaultBeIDCardUI(final Component parentComponent, Messages messages) { if (GraphicsEnvironment.isHeadless()) { throw new UnsupportedOperationException( "DefaultBeIDCardUI is a GUI and hence requires an interactive GraphicsEnvironment"); } this.parentComponent = parentComponent; if (messages != null) { this.messages = messages; } else { this.messages = Messages.getInstance(); } } @Override public void advisePINBlocked() { JOptionPane.showMessageDialog(this.parentComponent, this.messages.getMessage(MESSAGE_ID.PIN_BLOCKED), "eID card blocked", JOptionPane.ERROR_MESSAGE); } @Override public void advisePINChanged() { JOptionPane.showMessageDialog(this.parentComponent, this.messages.getMessage(MESSAGE_ID.PIN_CHANGED), "eID PIN change", JOptionPane.INFORMATION_MESSAGE); } @Override public void advisePINPadChangePIN(final int retriesLeft) { showPINPadFrame(retriesLeft, "eID PIN change", this.messages.getMessage(MESSAGE_ID.PIN_PAD_CHANGE)); } @Override public void advisePINPadNewPINEntry(final int retriesLeft) { showPINPadFrame(retriesLeft, "eID PIN change", this.messages.getMessage(MESSAGE_ID.PIN_PAD_MODIFY_NEW)); } @Override public void advisePINPadNewPINEntryAgain(final int retriesLeft) { showPINPadFrame(retriesLeft, "eID PIN change", this.messages.getMessage(MESSAGE_ID.PIN_PAD_MODIFY_NEW_AGAIN)); } @Override public void advisePINPadOldPINEntry(final int retriesLeft) { showPINPadFrame(retriesLeft, "eID PIN change", this.messages.getMessage(MESSAGE_ID.PIN_PAD_MODIFY_OLD)); } @Override public void advisePINPadOperationEnd() { disposePINPadFrame(); } @Override public void advisePINPadPINEntry(final int retriesLeft, final PINPurpose purpose, String applicationName) { if (null == applicationName) { showPINPadFrame( retriesLeft, "PIN", this.messages.getMessage(MESSAGE_ID.PIN_REASON, purpose.getType()), this.messages.getMessage(MESSAGE_ID.PIN_PAD)); } else { showPINPadFrame( retriesLeft, "PIN", this.messages.getMessage(MESSAGE_ID.PIN_REASON, purpose.getType()), this.messages.getMessage(MESSAGE_ID.APPLICATION) + ": " + applicationName, this.messages.getMessage(MESSAGE_ID.PIN_PAD)); } } @Override public void advisePINPadPUKEntry(final int retriesLeft) { showPINPadFrame(retriesLeft, "eID PIN unblock", this.messages.getMessage(MESSAGE_ID.PUK_PAD)); } @Override public void advisePINUnblocked() { JOptionPane.showMessageDialog(this.parentComponent, this.messages.getMessage(MESSAGE_ID.PIN_UNBLOCKED), "eID PIN unblock", JOptionPane.INFORMATION_MESSAGE); } @Override public char[][] obtainOldAndNewPIN(final int retriesLeft) { final Box mainPanel = Box.createVerticalBox(); if (-1 != retriesLeft) { mainPanel.add(Box.createVerticalStrut(4)); final Box retriesPanel = createWarningBox(this.messages .getMessage(MESSAGE_ID.RETRIES_LEFT) + ": " + retriesLeft); mainPanel.add(retriesPanel); mainPanel.add(Box.createVerticalStrut(24)); } final JPasswordField oldPinField = new JPasswordField(MAX_PIN_SIZE); final Box oldPinPanel = Box.createHorizontalBox(); final JLabel oldPinLabel = new JLabel( this.messages.getMessage(MESSAGE_ID.CURRENT_PIN) + ":"); oldPinLabel.setLabelFor(oldPinField); oldPinPanel.add(oldPinLabel); oldPinPanel.add(Box.createHorizontalStrut(5)); oldPinPanel.add(oldPinField); mainPanel.add(oldPinPanel); mainPanel.add(Box.createVerticalStrut(5)); final JPasswordField newPinField = new JPasswordField(MAX_PIN_SIZE); final Box newPinPanel = Box.createHorizontalBox(); final JLabel newPinLabel = new JLabel( this.messages.getMessage(MESSAGE_ID.NEW_PIN) + ":"); newPinLabel.setLabelFor(newPinField); newPinPanel.add(newPinLabel); newPinPanel.add(Box.createHorizontalStrut(5)); newPinPanel.add(newPinField); mainPanel.add(newPinPanel); mainPanel.add(Box.createVerticalStrut(5)); final JPasswordField new2PinField = new JPasswordField(MAX_PIN_SIZE); { final Box new2PinPanel = Box.createHorizontalBox(); final JLabel new2PinLabel = new JLabel( this.messages.getMessage(MESSAGE_ID.NEW_PIN) + ":"); new2PinLabel.setLabelFor(new2PinField); new2PinPanel.add(new2PinLabel); new2PinPanel.add(Box.createHorizontalStrut(5)); new2PinPanel.add(new2PinField); mainPanel.add(new2PinPanel); } final int result = JOptionPane.showOptionDialog(this.parentComponent, mainPanel, "Change eID PIN", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, null, null); if (result != JOptionPane.OK_OPTION) { throw new RuntimeException(OPERATION_CANCELLED); } if (false == Arrays.equals(newPinField.getPassword(), new2PinField.getPassword())) { throw new RuntimeException("new PINs not equal"); } final char[] oldPin = new char[oldPinField.getPassword().length]; final char[] newPin = new char[newPinField.getPassword().length]; System.arraycopy(oldPinField.getPassword(), 0, oldPin, 0, oldPinField.getPassword().length); System.arraycopy(newPinField.getPassword(), 0, newPin, 0, newPinField.getPassword().length); Arrays.fill(oldPinField.getPassword(), (char) 0); Arrays.fill(newPinField.getPassword(), (char) 0); return new char[][]{oldPin, newPin}; } @Override public char[] obtainPIN(final int retriesLeft, final PINPurpose reason, String applicationName) throws UserCancelledException { // main panel JPanel mainPanel = new JPanel() { private static final long serialVersionUID = 1L; private static final int BORDER_SIZE = 20; @Override public Insets getInsets() { return new Insets(BORDER_SIZE, BORDER_SIZE, BORDER_SIZE, BORDER_SIZE); } }; final BoxLayout boxLayout = new BoxLayout(mainPanel, BoxLayout.PAGE_AXIS); mainPanel.setLayout(boxLayout); final Box reasonPanel = Box.createHorizontalBox(); final JLabel reasonLabel = new JLabel(this.messages.getMessage( MESSAGE_ID.PIN_REASON, reason.getType())); reasonPanel.add(reasonLabel); reasonPanel.add(Box.createHorizontalGlue()); mainPanel.add(reasonPanel); mainPanel.add(Box.createVerticalStrut(16)); if (null != applicationName) { Box applicationBox = Box.createHorizontalBox(); JLabel applicationLabel = new JLabel( this.messages.getMessage(MESSAGE_ID.APPLICATION) + ": " + applicationName); applicationBox.add(applicationLabel); applicationBox.add(Box.createHorizontalGlue()); mainPanel.add(applicationBox); mainPanel.add(Box.createVerticalStrut(16)); } if (-1 != retriesLeft) { addWarningBox(mainPanel, this.messages.getMessage(MESSAGE_ID.RETRIES_LEFT) + ": " + retriesLeft); } final Box passwordPanel = Box.createHorizontalBox(); final JLabel promptLabel = new JLabel( this.messages.getMessage(MESSAGE_ID.LABEL_PIN) + ": "); passwordPanel.add(promptLabel); passwordPanel.add(Box.createHorizontalStrut(5)); final JPasswordField passwordField = new JPasswordField(MAX_PIN_SIZE); promptLabel.setLabelFor(passwordField); passwordPanel.add(passwordField); passwordPanel.setBorder(createGenerousLowerBevelBorder()); mainPanel.add(passwordPanel); // button panel JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)) { private static final long serialVersionUID = 1L; @Override public Insets getInsets() { return new Insets(0, 0, 5, 5); } }; final JButton okButton = new JButton( this.messages.getMessage(MESSAGE_ID.OK)); okButton.setEnabled(false); buttonPanel.add(okButton); final JButton cancelButton = new JButton( this.messages.getMessage(MESSAGE_ID.CANCEL)); buttonPanel.add(cancelButton); // dialog box final JDialog dialog = new JDialog((Frame) null, this.messages.getMessage(MESSAGE_ID.ENTER_PIN), true); dialog.setAlwaysOnTop(true); dialog.setLayout(new BorderLayout()); dialog.getContentPane().add(mainPanel, BorderLayout.CENTER); dialog.getContentPane().add(buttonPanel, BorderLayout.SOUTH); final DialogResult dialogResult = new DialogResult(); okButton.addActionListener(new ActionListener() { @Override public void actionPerformed(final ActionEvent event) { dialogResult.result = DialogResult.Result.OK; dialog.dispose(); } }); cancelButton.addActionListener(new ActionListener() { @Override public void actionPerformed(final ActionEvent event) { dialogResult.result = DialogResult.Result.CANCEL; dialog.dispose(); } }); passwordField.addActionListener(new ActionListener() { @Override public void actionPerformed(final ActionEvent event) { final int pinSize = passwordField.getPassword().length; if (MIN_PIN_SIZE <= pinSize && pinSize <= MAX_PIN_SIZE) { dialogResult.result = DialogResult.Result.OK; dialog.dispose(); } } }); passwordField.addKeyListener(new KeyListener() { @Override public void keyPressed(final KeyEvent e) { } @Override public void keyReleased(final KeyEvent e) { final int pinSize = passwordField.getPassword().length; if (MIN_PIN_SIZE <= pinSize && pinSize <= MAX_PIN_SIZE) { okButton.setEnabled(true); } else { okButton.setEnabled(false); } } @Override public void keyTyped(final KeyEvent e) { } }); dialog.pack(); if (this.parentComponent != null) { dialog.setLocationRelativeTo(this.parentComponent); } else { GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment .getLocalGraphicsEnvironment(); GraphicsDevice graphicsDevice = graphicsEnvironment .getDefaultScreenDevice(); DisplayMode displayMode = graphicsDevice.getDisplayMode(); int screenWidth = displayMode.getWidth(); int screenHeight = displayMode.getHeight(); int dialogWidth = dialog.getWidth(); int dialogHeight = dialog.getHeight(); dialog.setLocation((screenWidth - dialogWidth) / 2, (screenHeight - dialogHeight) / 2); } dialog.setVisible(true); // setVisible will wait until some button or so has been pressed if (dialogResult.result == DialogResult.Result.OK) { return passwordField.getPassword(); } throw new UserCancelledException(); } @Override public char[][] obtainPUKCodes(final int retriesLeft) { final Box mainPanel = Box.createVerticalBox(); if (-1 != retriesLeft) { addWarningBox(mainPanel, this.messages.getMessage(MESSAGE_ID.RETRIES_LEFT) + ": " + retriesLeft); } final JPasswordField puk1Field = new JPasswordField(8); final Box puk1Panel = Box.createHorizontalBox(); final JLabel puk1Label = new JLabel("eID PUK1:"); puk1Label.setLabelFor(puk1Field); puk1Panel.add(puk1Label); puk1Panel.add(Box.createHorizontalStrut(5)); puk1Panel.add(puk1Field); mainPanel.add(puk1Panel); mainPanel.add(Box.createVerticalStrut(5)); final JPasswordField puk2Field = new JPasswordField(8); final Box puk2Panel = Box.createHorizontalBox(); final JLabel puk2Label = new JLabel("eID PUK2:"); puk2Label.setLabelFor(puk2Field); puk2Panel.add(puk2Label); puk2Panel.add(Box.createHorizontalStrut(5)); puk2Panel.add(puk2Field); mainPanel.add(puk2Panel); final int result = JOptionPane.showOptionDialog(this.parentComponent, mainPanel, "eID PIN unblock", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, null, null); if (result != JOptionPane.OK_OPTION) { throw new RuntimeException(OPERATION_CANCELLED); } if (puk1Field.getPassword().length != PUK_SIZE || puk2Field.getPassword().length != PUK_SIZE) { throw new RuntimeException("PUK size incorrect"); } final char[] puk1 = new char[puk1Field.getPassword().length]; final char[] puk2 = new char[puk2Field.getPassword().length]; System.arraycopy(puk1Field.getPassword(), 0, puk1, 0, puk1Field.getPassword().length); System.arraycopy(puk2Field.getPassword(), 0, puk2, 0, puk2Field.getPassword().length); Arrays.fill(puk1Field.getPassword(), (char) 0); Arrays.fill(puk2Field.getPassword(), (char) 0); return new char[][]{puk1, puk2}; } @Override public void adviseSecureReaderOperation() { if (null != this.secureReaderTransactionFrame) { disposeSecureReaderFrame(); } this.secureReaderTransactionFrame = new JFrame( "Transaction Confirmation"); this.secureReaderTransactionFrame.setAlwaysOnTop(true); JPanel panel = new JPanel() { private static final long serialVersionUID = 1L; @Override public Insets getInsets() { return new Insets(10, 30, 10, 30); } }; final BoxLayout boxLayout = new BoxLayout(panel, BoxLayout.PAGE_AXIS); panel.setLayout(boxLayout); panel.add(new JLabel( "Check the transaction message on the secure card reader.")); this.secureReaderTransactionFrame.getContentPane().add(panel); this.secureReaderTransactionFrame.pack(); if (this.parentComponent != null) { this.secureReaderTransactionFrame .setLocationRelativeTo(this.parentComponent); } else { GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment .getLocalGraphicsEnvironment(); GraphicsDevice graphicsDevice = graphicsEnvironment .getDefaultScreenDevice(); DisplayMode displayMode = graphicsDevice.getDisplayMode(); int screenWidth = displayMode.getWidth(); int screenHeight = displayMode.getHeight(); int dialogWidth = this.secureReaderTransactionFrame.getWidth(); int dialogHeight = this.secureReaderTransactionFrame.getHeight(); this.secureReaderTransactionFrame.setLocation( (screenWidth - dialogWidth) / 2, (screenHeight - dialogHeight) / 2); } this.secureReaderTransactionFrame.setVisible(true); } @Override public void adviseSecureReaderOperationEnd() { disposeSecureReaderFrame(); } /* * ********************************************************************************************************************** */ private Box addWarningBox(final JComponent parent, final String warningMessage) { parent.add(Box.createVerticalStrut(4)); final Box retriesPanel = createWarningBox(warningMessage); parent.add(retriesPanel); parent.add(Box.createVerticalStrut(24)); return retriesPanel; } private Box createWarningBox(final String warningText) { final Box warningBox = Box.createHorizontalBox(); final JLabel warningLabel = new JLabel(warningText); warningLabel.setForeground(Color.RED); final Icon warningIcon = UIManager.getIcon("OptionPane.warningIcon"); if (warningIcon != null) { warningLabel.setIcon(warningIcon); } warningBox.add(warningLabel); warningBox.add(Box.createHorizontalGlue()); warningBox.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createLineBorder(Color.red, 1), BorderFactory.createEmptyBorder(8, 8, 8, 8))); return warningBox; } private Border createGenerousLowerBevelBorder() { return BorderFactory.createCompoundBorder( BorderFactory.createLoweredBevelBorder(), BorderFactory.createEmptyBorder(16, 16, 16, 16)); } private void showPINPadFrame(final int retriesLeft, final String title, final String... messages) { if (null != this.pinPadFrame) { disposePINPadFrame(); } this.pinPadFrame = new JFrame(title); this.pinPadFrame.setAlwaysOnTop(true); this.pinPadFrame.setResizable(false); JPanel panel = new JPanel(); final BoxLayout boxLayout = new BoxLayout(panel, BoxLayout.PAGE_AXIS); panel.setLayout(boxLayout); panel.setBorder(BorderFactory.createEmptyBorder(32, 32, 32, 32)); if (messages.length > 0) { final JLabel label = new JLabel(messages[0]); label.setAlignmentX((float) 0.5); panel.add(label); } if (-1 != retriesLeft) { panel.add(Box.createVerticalStrut(24)); final Box warningBox = this.createWarningBox(this.messages .getMessage(MESSAGE_ID.RETRIES_LEFT) + ": " + retriesLeft); panel.add(warningBox); panel.add(Box.createVerticalStrut(24)); } for (int i = 1; i < messages.length; i++) { final JLabel label = new JLabel(messages[i]); label.setAlignmentX((float) 0.5); panel.add(label); } this.pinPadFrame.getContentPane().add(panel); this.pinPadFrame.pack(); if (this.parentComponent != null) { this.pinPadFrame.setLocationRelativeTo(this.parentComponent); } else { GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment .getLocalGraphicsEnvironment(); GraphicsDevice graphicsDevice = graphicsEnvironment .getDefaultScreenDevice(); DisplayMode displayMode = graphicsDevice.getDisplayMode(); int screenWidth = displayMode.getWidth(); int screenHeight = displayMode.getHeight(); int dialogWidth = this.pinPadFrame.getWidth(); int dialogHeight = this.pinPadFrame.getHeight(); this.pinPadFrame.setLocation((screenWidth - dialogWidth) / 2, (screenHeight - dialogHeight) / 2); } this.pinPadFrame.setVisible(true); } private void disposePINPadFrame() { if (null != this.pinPadFrame) { this.pinPadFrame.dispose(); this.pinPadFrame = null; } } /* * */ private void disposeSecureReaderFrame() { if (null != this.secureReaderTransactionFrame) { this.secureReaderTransactionFrame.dispose(); this.secureReaderTransactionFrame = null; } } /* * */ private static class DialogResult { enum Result { OK, CANCEL }; public Result result = null; } @Override public void setLocale(Locale newLocale) { this.locale = newLocale; this.messages = Messages.getInstance(newLocale); } @Override public Locale getLocale() { return locale; } }