From b74bc7969935b7a871d1f87a3ee396fae16485a7 Mon Sep 17 00:00:00 2001 From: Maarten Billemont Date: Sun, 8 Jun 2014 16:39:26 -0400 Subject: [PATCH] A basic cross-platform GUI for the Master Password generation algorithm. --- .../Java/masterpassword-gui/pom.xml | 79 ++++++++ .../lyndir/lhunath/masterpassword/GUI.java | 87 +++++++++ .../lhunath/masterpassword/PasswordFrame.java | 152 +++++++++++++++ .../lhunath/masterpassword/UnlockFrame.java | 175 ++++++++++++++++++ .../lyndir/lhunath/masterpassword/User.java | 22 +++ .../masterpassword/util/Components.java | 20 ++ .../src/main/resources/logback.xml | 15 ++ .../src/main/resources/media | 1 + MasterPassword/Java/pom.xml | 1 + .../project.pbxproj | 4 +- 10 files changed, 554 insertions(+), 2 deletions(-) create mode 100644 MasterPassword/Java/masterpassword-gui/pom.xml create mode 100644 MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/GUI.java create mode 100644 MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/PasswordFrame.java create mode 100644 MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/UnlockFrame.java create mode 100644 MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/User.java create mode 100644 MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/util/Components.java create mode 100644 MasterPassword/Java/masterpassword-gui/src/main/resources/logback.xml create mode 120000 MasterPassword/Java/masterpassword-gui/src/main/resources/media diff --git a/MasterPassword/Java/masterpassword-gui/pom.xml b/MasterPassword/Java/masterpassword-gui/pom.xml new file mode 100644 index 00000000..646ae8c5 --- /dev/null +++ b/MasterPassword/Java/masterpassword-gui/pom.xml @@ -0,0 +1,79 @@ + + + + 4.0.0 + + + + com.lyndir.lhunath.masterpassword + masterpassword + GIT-SNAPSHOT + + + Master Password GUI + A GUI interface to the Master Password algorithm + + com.lyndir.lhunath.masterpassword + masterpassword-gui + jar + + + + + + src/main/resources + false + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.2 + + + package + + shade + + + + + com.lyndir.lhunath.masterpassword.GUI + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + + + + + + + + + + com.lyndir.lhunath.masterpassword + masterpassword-algorithm + GIT-SNAPSHOT + + + + ch.qos.logback + logback-classic + + + + + diff --git a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/GUI.java b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/GUI.java new file mode 100644 index 00000000..42ac90cc --- /dev/null +++ b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/GUI.java @@ -0,0 +1,87 @@ +/* + * Copyright 2008, Maarten Billemont + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 com.lyndir.lhunath.masterpassword; + +import com.apple.eawt.*; +import java.io.IOException; +import javax.swing.*; + + +/** + *

Jun 10, 2008

+ * + * @author mbillemo + */ +public class GUI implements UnlockFrame.SignInCallback { + + private UnlockFrame unlockFrame = new UnlockFrame( this ); + private PasswordFrame passwordFrame; + + public static void main(final String[] args) + throws IOException { + + new GUI().open(); + } + + public GUI() { + + try { + getClass().getClassLoader().loadClass( "com.apple.eawt.Application" ); + Application application = Application.getApplication(); + application.addAppEventListener( new AppForegroundListener() { + + @Override + public void appMovedToBackground(AppEvent.AppForegroundEvent arg0) { + } + + @Override + public void appRaisedToForeground(AppEvent.AppForegroundEvent arg0) { + open(); + } + } ); + application.addAppEventListener( new AppReOpenedListener() { + @Override + public void appReOpened(AppEvent.AppReOpenedEvent arg0) { + open(); + } + } ); + } + catch (ClassNotFoundException | NoClassDefFoundError ignored) { + } + } + + private void open() { + SwingUtilities.invokeLater( new Runnable() { + @Override + public void run() { + if (passwordFrame == null) { + unlockFrame.setVisible( true ); + } else { + passwordFrame.setVisible( true ); + } + } + } ); + } + + @Override + public boolean signedIn(final String userName, final String masterPassword) { + final byte[] key = MasterPassword.keyForPassword( masterPassword, userName ); + passwordFrame = new PasswordFrame( new User( userName, key ) ); + + open(); + return true; + } +} diff --git a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/PasswordFrame.java b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/PasswordFrame.java new file mode 100644 index 00000000..0acb737b --- /dev/null +++ b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/PasswordFrame.java @@ -0,0 +1,152 @@ +package com.lyndir.lhunath.masterpassword; + +import static com.lyndir.lhunath.opal.system.util.StringUtils.*; + +import com.lyndir.lhunath.masterpassword.util.Components; +import java.awt.*; +import java.awt.datatransfer.StringSelection; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import javax.swing.*; +import javax.swing.border.*; +import javax.swing.event.*; + + +/** + * @author lhunath, 2014-06-08 + */ +public class PasswordFrame extends JFrame implements DocumentListener { + + private static final ExecutorService executor = Executors.newSingleThreadExecutor(); + + private final User user; + private final JPanel root; + private final JTextField siteNameField; + private final JComboBox siteTypeField; + private final JSpinner siteCounterField; + private final JLabel passwordLabel; + + public PasswordFrame(User user) + throws HeadlessException { + super( "Master Password" ); + this.user = user; + + JLabel label; + + setDefaultCloseOperation( DISPOSE_ON_CLOSE ); + setContentPane( root = new JPanel( new BorderLayout( 20, 20 ) ) ); + root.setBorder( new EmptyBorder( 20, 20, 20, 20 ) ); + + // User + add( new JLabel( strf( "Generating passwords for: %s", user.getName() ) ), BorderLayout.NORTH ); + + // Site + JPanel sitePanel = new JPanel(); + sitePanel.setLayout( new BoxLayout( sitePanel, BoxLayout.PAGE_AXIS ) ); + sitePanel.setBorder( new CompoundBorder( new EtchedBorder( EtchedBorder.RAISED ), new EmptyBorder( 8, 8, 8, 8 ) ) ); + add( sitePanel, BorderLayout.CENTER ); + + // Site Name + sitePanel.add( new JLabel( "Site Name:", JLabel.LEADING ) ); + sitePanel.add( siteNameField = new JTextField() { + @Override + public Dimension getMaximumSize() { + return new Dimension( Integer.MAX_VALUE, getPreferredSize().height ); + } + } ); + siteNameField.getDocument().addDocumentListener( this ); + siteNameField.addActionListener( new ActionListener() { + @Override + public void actionPerformed(final ActionEvent e) { + updatePassword( new PasswordCallback() { + @Override + public void passwordGenerated(final String siteName, final String sitePassword) { + StringSelection clipboardContents = new StringSelection( sitePassword ); + Toolkit.getDefaultToolkit().getSystemClipboard().setContents( clipboardContents, null ); + + SwingUtilities.invokeLater( new Runnable() { + @Override + public void run() { + dispose(); + } + } ); + } + } ); + } + } ); + + // Site Type & Counter + sitePanel.add( Components.boxLayout( BoxLayout.LINE_AXIS, // + siteTypeField = new JComboBox<>( MPElementType.values() ), + siteCounterField = new JSpinner( new SpinnerNumberModel( 1, 1, Integer.MAX_VALUE, 1 ) ) { + @Override + public Dimension getMaximumSize() { + return new Dimension( 50, getPreferredSize().height ); + } + } ) ); + siteTypeField.setSelectedItem( MPElementType.GeneratedLong ); + siteCounterField.addChangeListener( new ChangeListener() { + @Override + public void stateChanged(final ChangeEvent e) { + updatePassword( null ); + } + } ); + + // Password + add( passwordLabel = new JLabel( " ", JLabel.CENTER ), BorderLayout.SOUTH ); + passwordLabel.setFont( passwordLabel.getFont().deriveFont( 40f ) ); + + pack(); + setMinimumSize( getSize() ); + setPreferredSize( new Dimension( 600, getSize().height ) ); + pack(); + + setLocationByPlatform( true ); + setLocationRelativeTo( null ); + } + + private void updatePassword(final PasswordCallback callback) { + final MPElementType siteType = (MPElementType) siteTypeField.getSelectedItem(); + final String siteName = siteNameField.getText(); + final int siteCounter = (Integer) siteCounterField.getValue(); + + executor.submit( new Runnable() { + @Override + public void run() { + final String sitePassword = MasterPassword.generateContent( siteType, siteName, user.getKey(), siteCounter ); + if (callback != null) { + callback.passwordGenerated( siteName, sitePassword ); + } + + SwingUtilities.invokeLater( new Runnable() { + @Override + public void run() { + passwordLabel.setText( sitePassword ); + } + } ); + } + } ); + } + + @Override + public void insertUpdate(final DocumentEvent e) { + updatePassword( null ); + } + + @Override + public void removeUpdate(final DocumentEvent e) { + updatePassword( null ); + } + + @Override + public void changedUpdate(final DocumentEvent e) { + updatePassword( null ); + } + + interface PasswordCallback { + + void passwordGenerated(String siteName, String sitePassword); + } +} diff --git a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/UnlockFrame.java b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/UnlockFrame.java new file mode 100644 index 00000000..bcaac7c0 --- /dev/null +++ b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/UnlockFrame.java @@ -0,0 +1,175 @@ +package com.lyndir.lhunath.masterpassword; + +import com.google.common.io.Resources; +import com.lyndir.lhunath.masterpassword.util.Components; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import javax.swing.*; +import javax.swing.border.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + + +/** + * @author lhunath, 2014-06-08 + */ +public class UnlockFrame extends JFrame implements DocumentListener { + + private static final ExecutorService executor = Executors.newSingleThreadExecutor(); + + private final SignInCallback signInCallback; + private final JPanel root; + private final JLabel avatarView; + private final JTextField userNameField; + private final JTextField masterPasswordField; + private final JButton signInButton; + + public UnlockFrame(final SignInCallback signInCallback) + throws HeadlessException { + super( "Unlock Master Password" ); + this.signInCallback = signInCallback; + + JLabel label; + + setDefaultCloseOperation( DISPOSE_ON_CLOSE ); + setContentPane( root = new JPanel( new BorderLayout( 20, 20 ) ) ); + root.setBorder( new EmptyBorder( 20, 20, 20, 20 ) ); + + JPanel userAndPassword = new JPanel(); + userAndPassword.setLayout( new BoxLayout( userAndPassword, BoxLayout.PAGE_AXIS ) ); + userAndPassword.setBorder( new CompoundBorder( new EtchedBorder( EtchedBorder.RAISED ), new EmptyBorder( 8, 8, 8, 8 ) ) ); + add( userAndPassword, BorderLayout.CENTER ); + + // Avatar + userAndPassword.add( Box.createVerticalGlue() ); + userAndPassword.add( avatarView = new JLabel( new ImageIcon( Resources.getResource( "media/Avatars/avatar-0.png" ) ) ) { + @Override + public Dimension getMaximumSize() { + return new Dimension( Integer.MAX_VALUE, Integer.MAX_VALUE ); + } + } ); + userAndPassword.add( Box.createVerticalGlue() ); + + // User Name + userAndPassword.add( label = new JLabel( "User Name:" ) ); + label.setHorizontalAlignment( SwingConstants.CENTER ); + label.setVerticalAlignment( SwingConstants.BOTTOM ); + userAndPassword.add( userNameField = new JTextField() { + @Override + public Dimension getMaximumSize() { + return new Dimension( Integer.MAX_VALUE, getPreferredSize().height ); + } + } ); + userNameField.getDocument().addDocumentListener( this ); + userNameField.addActionListener( new AbstractAction() { + @Override + public void actionPerformed(final ActionEvent e) { + trySignIn(); + } + } ); + + // Master Password + userAndPassword.add( label = new JLabel( "Master Password:" ) ); + label.setHorizontalAlignment( SwingConstants.CENTER ); + label.setVerticalAlignment( SwingConstants.BOTTOM ); + userAndPassword.add( masterPasswordField = new JPasswordField() { + @Override + public Dimension getMaximumSize() { + return new Dimension( Integer.MAX_VALUE, getPreferredSize().height ); + } + } ); + masterPasswordField.getDocument().addDocumentListener( this ); + masterPasswordField.addActionListener( new AbstractAction() { + @Override + public void actionPerformed(final ActionEvent e) { + trySignIn(); + } + } ); + + // Sign In + add( Components.boxLayout( BoxLayout.LINE_AXIS, Box.createGlue(), signInButton = new JButton( "Sign In" ), Box.createGlue() ), + BorderLayout.SOUTH ); + signInButton.addActionListener( new AbstractAction() { + @Override + public void actionPerformed(final ActionEvent e) { + trySignIn(); + } + } ); + + checkSignIn(); + + pack(); + setMinimumSize( getSize() ); + setPreferredSize( new Dimension( 300, 300 ) ); + pack(); + + setLocationByPlatform( true ); + setLocationRelativeTo( null ); + } + + private boolean checkSignIn() { + String userName = userNameField.getText(); + String masterPassword = masterPasswordField.getText(); + + boolean enabled = !userName.isEmpty() && !masterPassword.isEmpty(); + signInButton.setEnabled( enabled ); + + return enabled; + } + + private void trySignIn() { + if (!checkSignIn()) + return; + + final String userName = userNameField.getText(); + final String masterPassword = masterPasswordField.getText(); + + userNameField.setEnabled( false ); + masterPasswordField.setEnabled( false ); + signInButton.setEnabled( false ); + signInButton.setText( "Signing In..." ); + + executor.submit( new Runnable() { + @Override + public void run() { + final boolean success = signInCallback.signedIn( userName, masterPassword ); + SwingUtilities.invokeLater( new Runnable() { + @Override + public void run() { + if (success) { + dispose(); + return; + } + + userNameField.setEnabled( true ); + masterPasswordField.setEnabled( true ); + signInButton.setText( "Sign In" ); + checkSignIn(); + } + } ); + } + } ); + } + + @Override + public void insertUpdate(final DocumentEvent e) { + checkSignIn(); + } + + @Override + public void removeUpdate(final DocumentEvent e) { + checkSignIn(); + } + + @Override + public void changedUpdate(final DocumentEvent e) { + checkSignIn(); + } + + interface SignInCallback { + + boolean signedIn(String userName, String masterPassword); + } +} diff --git a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/User.java b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/User.java new file mode 100644 index 00000000..2c579796 --- /dev/null +++ b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/User.java @@ -0,0 +1,22 @@ +package com.lyndir.lhunath.masterpassword; + +/** + * @author lhunath, 2014-06-08 + */ +public class User { + private final String name; + private final byte[] key; + + public User(final String name, final byte[] key) { + this.name = name; + this.key = key; + } + + public String getName() { + return name; + } + + public byte[] getKey() { + return key; + } +} diff --git a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/util/Components.java b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/util/Components.java new file mode 100644 index 00000000..0c3aff58 --- /dev/null +++ b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/lhunath/masterpassword/util/Components.java @@ -0,0 +1,20 @@ +package com.lyndir.lhunath.masterpassword.util; + +import java.awt.*; +import javax.swing.*; + + +/** + * @author lhunath, 2014-06-08 + */ +public abstract class Components { + + public static JComponent boxLayout(int axis, Component... components) { + JPanel container = new JPanel(); + container.setLayout( new BoxLayout( container, axis ) ); + for (Component component : components) + container.add( component ); + + return container; + } +} diff --git a/MasterPassword/Java/masterpassword-gui/src/main/resources/logback.xml b/MasterPassword/Java/masterpassword-gui/src/main/resources/logback.xml new file mode 100644 index 00000000..a5c492fa --- /dev/null +++ b/MasterPassword/Java/masterpassword-gui/src/main/resources/logback.xml @@ -0,0 +1,15 @@ + + + + + %-8relative %22c{0} [%-5level] %msg%n + + + + + + + + + + diff --git a/MasterPassword/Java/masterpassword-gui/src/main/resources/media b/MasterPassword/Java/masterpassword-gui/src/main/resources/media new file mode 120000 index 00000000..c0b8c864 --- /dev/null +++ b/MasterPassword/Java/masterpassword-gui/src/main/resources/media @@ -0,0 +1 @@ +../../../../../Resources/Media/ \ No newline at end of file diff --git a/MasterPassword/Java/pom.xml b/MasterPassword/Java/pom.xml index ea36869d..048e14a1 100644 --- a/MasterPassword/Java/pom.xml +++ b/MasterPassword/Java/pom.xml @@ -21,6 +21,7 @@ masterpassword-algorithm masterpassword-cli + masterpassword-gui diff --git a/MasterPassword/ObjC/Mac/MasterPassword-Mac.xcodeproj/project.pbxproj b/MasterPassword/ObjC/Mac/MasterPassword-Mac.xcodeproj/project.pbxproj index 7ff8f86c..a4802b08 100644 --- a/MasterPassword/ObjC/Mac/MasterPassword-Mac.xcodeproj/project.pbxproj +++ b/MasterPassword/ObjC/Mac/MasterPassword-Mac.xcodeproj/project.pbxproj @@ -1876,7 +1876,7 @@ ); GCC_PREFIX_HEADER = "MasterPassword-Prefix.pch"; INFOPLIST_FILE = "MasterPassword-Info.plist"; - PROVISIONING_PROFILE = "9FCC90BB-24CA-40E2-8565-860F95CCC039"; + PROVISIONING_PROFILE = "9BA628A2-36F5-438B-9F08-A2527CECC9F8"; SKIP_INSTALL = NO; WRAPPER_NAME = "Master Password.${WRAPPER_EXTENSION}"; }; @@ -1896,7 +1896,7 @@ ); GCC_PREFIX_HEADER = "MasterPassword-Prefix.pch"; INFOPLIST_FILE = "MasterPassword-Info.plist"; - PROVISIONING_PROFILE = "9FCC90BB-24CA-40E2-8565-860F95CCC039"; + PROVISIONING_PROFILE = "9BA628A2-36F5-438B-9F08-A2527CECC9F8"; SKIP_INSTALL = NO; WRAPPER_NAME = "Master Password.${WRAPPER_EXTENSION}"; };