2
0

Big overhaul for proper site-specific algorithm support and big Android UI update.

This commit is contained in:
Maarten Billemont 2015-02-05 00:56:24 -05:00
parent a6ab9b9194
commit 145008406d
32 changed files with 485 additions and 267 deletions

View File

@ -23,13 +23,13 @@ public abstract class MasterKey {
@Nullable @Nullable
private byte[] masterKey; private byte[] masterKey;
public static MasterKey create(final String fullName, final String masterPassword) { public static MasterKey create(final String fullName, final char[] masterPassword) {
return create( Version.CURRENT, fullName, masterPassword ); return create( Version.CURRENT, fullName, masterPassword );
} }
@Nonnull @Nonnull
public static MasterKey create(Version version, final String fullName, final String masterPassword) { public static MasterKey create(Version version, final String fullName, final char[] masterPassword) {
switch (version) { switch (version) {
case V0: case V0:
@ -52,7 +52,7 @@ public abstract class MasterKey {
} }
@Nullable @Nullable
protected abstract byte[] deriveKey(final String masterPassword); protected abstract byte[] deriveKey(final char[] masterPassword);
protected abstract Version getAlgorithm(); protected abstract Version getAlgorithm();
@ -73,7 +73,7 @@ public abstract class MasterKey {
return idForBytes( getMasterKey() ); return idForBytes( getMasterKey() );
} }
public abstract String encode(final String siteName, final MPSiteType siteType, int siteCounter, final MPSiteVariant siteVariant, public abstract String encode(@Nonnull final String siteName, final MPSiteType siteType, int siteCounter, final MPSiteVariant siteVariant,
@Nullable final String siteContext); @Nullable final String siteContext);
public boolean isValid() { public boolean isValid() {
@ -88,10 +88,10 @@ public abstract class MasterKey {
} }
} }
public MasterKey revalidate(final String masterPassword) { public MasterKey revalidate(final char[] masterPassword) {
invalidate(); invalidate();
logger.trc( "masterPassword: %s", masterPassword ); logger.trc( "masterPassword: %s", new String( masterPassword ) );
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
masterKey = deriveKey( masterPassword ); masterKey = deriveKey( masterPassword );

View File

@ -6,10 +6,10 @@ import com.google.common.primitives.Bytes;
import com.lambdaworks.crypto.SCrypt; import com.lambdaworks.crypto.SCrypt;
import com.lyndir.lhunath.opal.system.*; import com.lyndir.lhunath.opal.system.*;
import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.logging.Logger;
import java.nio.ByteBuffer; import java.nio.*;
import java.nio.ByteOrder;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.Arrays;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -48,7 +48,7 @@ public class MasterKeyV0 extends MasterKey {
@Nullable @Nullable
@Override @Override
protected byte[] deriveKey(final String masterPassword) { protected byte[] deriveKey(final char[] masterPassword) {
String fullName = getFullName(); String fullName = getFullName();
byte[] fullNameBytes = fullName.getBytes( MP_charset ); byte[] fullNameBytes = fullName.getBytes( MP_charset );
byte[] fullNameLengthBytes = bytesForInt( fullName.length() ); byte[] fullNameLengthBytes = bytesForInt( fullName.length() );
@ -58,13 +58,18 @@ public class MasterKeyV0 extends MasterKey {
logger.trc( "key scope: %s", mpKeyScope ); logger.trc( "key scope: %s", mpKeyScope );
logger.trc( "masterKeySalt ID: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) ); logger.trc( "masterKeySalt ID: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) );
CharBuffer mpChars = CharBuffer.wrap( masterPassword );
byte[] mpBytes = MP_charset.encode( mpChars ).array();
try { try {
return SCrypt.scrypt( masterPassword.getBytes( MP_charset ), masterKeySalt, MP_N, MP_r, MP_p, MP_dkLen ); return SCrypt.scrypt( mpBytes, masterKeySalt, MP_N, MP_r, MP_p, MP_dkLen );
} }
catch (GeneralSecurityException e) { catch (GeneralSecurityException e) {
logger.bug( e ); logger.bug( e );
return null; return null;
} }
finally {
Arrays.fill( mpBytes, (byte) 0 );
}
} }
public String encode(final String siteName, final MPSiteType siteType, int siteCounter, final MPSiteVariant siteVariant, public String encode(final String siteName, final MPSiteType siteType, int siteCounter, final MPSiteVariant siteVariant,

View File

@ -4,7 +4,9 @@ import com.google.common.primitives.Bytes;
import com.lambdaworks.crypto.SCrypt; import com.lambdaworks.crypto.SCrypt;
import com.lyndir.lhunath.opal.system.CodeUtils; import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.logging.Logger;
import java.nio.CharBuffer;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.Arrays;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -31,7 +33,7 @@ public class MasterKeyV3 extends MasterKeyV2 {
@Nullable @Nullable
@Override @Override
protected byte[] deriveKey(final String masterPassword) { protected byte[] deriveKey(final char[] masterPassword) {
byte[] fullNameBytes = getFullName().getBytes( MP_charset ); byte[] fullNameBytes = getFullName().getBytes( MP_charset );
byte[] fullNameLengthBytes = bytesForInt( fullNameBytes.length ); byte[] fullNameLengthBytes = bytesForInt( fullNameBytes.length );
@ -40,12 +42,17 @@ public class MasterKeyV3 extends MasterKeyV2 {
logger.trc( "key scope: %s", mpKeyScope ); logger.trc( "key scope: %s", mpKeyScope );
logger.trc( "masterKeySalt ID: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) ); logger.trc( "masterKeySalt ID: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) );
CharBuffer mpChars = CharBuffer.wrap( masterPassword );
byte[] mpBytes = MP_charset.encode( mpChars ).array();
try { try {
return SCrypt.scrypt( masterPassword.getBytes( MP_charset ), masterKeySalt, MP_N, MP_r, MP_p, MP_dkLen ); return SCrypt.scrypt( mpBytes, masterKeySalt, MP_N, MP_r, MP_p, MP_dkLen );
} }
catch (GeneralSecurityException e) { catch (GeneralSecurityException e) {
logger.bug( e ); logger.bug( e );
return null; return null;
} }
finally {
Arrays.fill( mpBytes, (byte) 0 );
}
} }
} }

View File

@ -0,0 +1,9 @@
/**
*
* @author lhunath, 15-02-04
*/
@ParametersAreNonnullByDefault package com.lyndir.masterpassword;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@ -80,7 +80,7 @@ public class MPWTests {
@Nonnull @Nonnull
@Override @Override
public String get() { public String get() {
return parentCase.getMasterPassword(); return new String( parentCase.getMasterPassword() );
} }
} ); } );
keyID = ifNotNullElse( keyID, new NNSupplier<String>() { keyID = ifNotNullElse( keyID, new NNSupplier<String>() {
@ -148,8 +148,8 @@ public class MPWTests {
return fullName; return fullName;
} }
public String getMasterPassword() { public char[] getMasterPassword() {
return masterPassword; return masterPassword.toCharArray();
} }
public String getKeyID() { public String getKeyID() {

View File

@ -5,14 +5,14 @@
android:versionName="GIT-SNAPSHOT"> android:versionName="GIT-SNAPSHOT">
<uses-sdk <uses-sdk
android:minSdkVersion="14" android:minSdkVersion="19"
android:targetSdkVersion="19" /> android:targetSdkVersion="21" />
<application <application
android:icon="@drawable/icon" android:icon="@drawable/icon"
android:label="@string/app_name" android:label="@string/app_name"
android:allowBackup="true"> android:allowBackup="true">
<activity android:name=".EmergencyActivity"> <activity android:name=".EmergencyActivity" android:theme="@style/MPTheme">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />

View File

@ -8,6 +8,7 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="20dp"
android:orientation="vertical" android:orientation="vertical"
android:gravity="center"> android:gravity="center">
@ -16,89 +17,158 @@
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1" /> android:layout_weight="1" />
<ProgressBar
android:id="@+id/progressView"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:indeterminate="true" />
<EditText <EditText
android:id="@+id/userNameField" android:id="@+id/fullNameField"
android:layout_width="300dp" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:nextFocusForward="@+id/masterPasswordField"
android:inputType="text|textCapWords|textPersonName" android:inputType="text|textCapWords|textPersonName"
android:hint="@string/userName_hint" android:hint="@string/fullName_hint"
android:gravity="center" android:gravity="center"
android:textColor="#FFFFFF" android:textColor="#FFFFFF"
android:textSize="26sp" /> android:textSize="26sp" />
<EditText <CheckBox
android:id="@+id/masterPasswordField" android:id="@+id/rememberFullNameField"
android:layout_width="300dp" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:nextFocusForward="@+id/rememberPasswordField"
android:textSize="14sp"
android:textColor="@android:color/tertiary_text_dark"
android:text="@string/remember" />
<EditText
android:id="@id/masterPasswordField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nextFocusForward="@+id/siteNameField"
android:inputType="text|textPassword" android:inputType="text|textPassword"
android:hint="@string/masterPassword_hint" android:hint="@string/masterPassword_hint"
android:gravity="center" android:gravity="center"
android:textColor="#FFFFFF" android:textColor="#FFFFFF"
android:textSize="18sp" /> android:textSize="18sp" />
<EditText <CheckBox
android:id="@+id/siteNameField" android:id="@id/rememberPasswordField"
android:layout_width="300dp" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="14sp"
android:textColor="@android:color/tertiary_text_dark"
android:text="@string/forgetOnClose" />
<EditText
android:id="@id/siteNameField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nextFocusForward="@+id/sitePasswordField"
android:inputType="text|textNoSuggestions|textUri" android:inputType="text|textNoSuggestions|textUri"
android:hint="@string/siteName_hint" android:hint="@string/siteName_hint"
android:gravity="center" android:gravity="center"
android:textColor="#FFFFFF" android:textColor="#FFFFFF"
android:textSize="26sp" /> android:textSize="18sp" />
<ImageView <FrameLayout
android:layout_width="300dp" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content">
android:layout_marginTop="20dp"
android:layout_marginBottom="30dp"
android:src="@drawable/double_"
android:contentDescription="@string/empty" />
<TextView <ProgressBar
android:id="@+id/sitePasswordField" android:id="@+id/progressView"
android:layout_width="300dp" android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="20dp"
android:indeterminate="true" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical">
<TextView
android:id="@id/sitePasswordField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nextFocusForward="@+id/siteTypeField"
android:gravity="center"
android:background="@android:color/transparent"
android:textColor="#FFFFFF"
android:textSize="32sp"
android:text="LuxdZozvDuma4["
android:onClick="copySitePassword" />
<TextView
android:id="@+id/sitePasswordTip"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:labelFor="@id/sitePasswordField"
android:gravity="center"
android:background="@android:color/transparent"
android:textSize="14sp"
android:textColor="@android:color/tertiary_text_dark"
android:text="@string/sitePassword_hint" />
</LinearLayout>
</FrameLayout>
<CheckBox
android:id="@+id/maskPasswordField"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:textSize="14sp"
android:background="@android:color/transparent" android:textColor="@android:color/tertiary_text_dark"
android:textColor="#FFFFFF" android:text="@string/maskPassword" />
android:textSize="32sp"
android:text="LuxdZozvDuma4["
android:onClick="copySitePassword" />
<Spinner <Spinner
android:id="@+id/typeField" android:id="@id/siteTypeField"
android:layout_width="300dp" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:nextFocusForward="@+id/counterField"
android:gravity="center" /> android:gravity="center" />
<EditText <EditText
android:id="@+id/counterField" android:id="@id/counterField"
android:layout_width="300dp" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:nextFocusForward="@+id/siteVersionField"
android:gravity="center" android:gravity="center"
android:inputType="text|textNoSuggestions" android:inputType="text|textNoSuggestions"
android:textColor="#FFFFFF" android:textColor="#FFFFFF"
android:textSize="26sp" android:textSize="18sp"
android:text="1" /> android:text="1" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:labelFor="@id/counterField"
android:gravity="center"
android:background="@android:color/transparent"
android:textSize="14sp"
android:textColor="@android:color/tertiary_text_dark"
android:text="@string/siteCounter_hint" />
<Spinner
android:id="@id/siteVersionField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nextFocusForward="@id/rememberFullNameField"
android:gravity="center" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:labelFor="@id/siteVersionField"
android:gravity="center"
android:background="@android:color/transparent"
android:textSize="14sp"
android:textColor="@android:color/tertiary_text_dark"
android:text="@string/siteVersion_hint" />
<View <View
android:layout_width="1dp" android:layout_width="1dp"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1" /> android:layout_weight="1" />
<CheckBox
android:id="@+id/rememberPasswordField"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:text="@string/remember" />
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

View File

@ -6,4 +6,4 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:drawableTop="@drawable/avatar0" android:drawableTop="@drawable/avatar0"
android:drawablePadding="8dp" android:drawablePadding="8dp"
android:text="Maarten Billemont" /> android:text="Robert Lee Mitchell" />

View File

@ -2,9 +2,14 @@
<resources> <resources>
<string name="app_name">Master Password</string> <string name="app_name">Master Password</string>
<string name="avatar">User Avatar</string> <string name="avatar">User Avatar</string>
<string name="remember">Remember Password</string> <string name="remember">Remember</string>
<string name="siteName_hint">Site Name</string> <string name="forgetOnClose">Forget on close</string>
<string name="userName_hint">Your Name</string> <string name="maskPassword">Hide password</string>
<string name="masterPassword_hint">Your Master Password</string> <string name="fullName_hint">Your full name</string>
<string name="masterPassword_hint">Your master password</string>
<string name="siteName_hint">eg. google.com</string>
<string name="sitePassword_hint">Tap to copy</string>
<string name="siteCounter_hint">Password #</string>
<string name="siteVersion_hint">Algorithm</string>
<string name="empty" /> <string name="empty" />
</resources> </resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="MPTheme" parent="android:Theme.Holo.Dialog.MinWidth">
</style>
</resources>

View File

@ -4,10 +4,11 @@ import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import android.app.Activity; import android.app.Activity;
import android.content.*; import android.content.*;
import android.content.ClipboardManager;
import android.graphics.Paint; import android.graphics.Paint;
import android.os.Bundle; import android.os.Bundle;
import android.text.Editable; import android.text.*;
import android.text.TextWatcher; import android.text.method.PasswordTransformationMethod;
import android.view.View; import android.view.View;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.*; import android.widget.*;
@ -17,7 +18,9 @@ import com.google.common.base.Throwables;
import com.google.common.util.concurrent.*; import com.google.common.util.concurrent.*;
import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.ConversionUtils; import com.lyndir.lhunath.opal.system.util.ConversionUtils;
import java.util.Arrays;
import java.util.concurrent.*; import java.util.concurrent.*;
import javax.annotation.Nullable;
public class EmergencyActivity extends Activity { public class EmergencyActivity extends Activity {
@ -44,8 +47,8 @@ public class EmergencyActivity extends Activity {
@InjectView(R.id.progressView) @InjectView(R.id.progressView)
ProgressBar progressView; ProgressBar progressView;
@InjectView(R.id.userNameField) @InjectView(R.id.fullNameField)
EditText userNameField; EditText fullNameField;
@InjectView(R.id.masterPasswordField) @InjectView(R.id.masterPasswordField)
EditText masterPasswordField; EditText masterPasswordField;
@ -53,23 +56,36 @@ public class EmergencyActivity extends Activity {
@InjectView(R.id.siteNameField) @InjectView(R.id.siteNameField)
EditText siteNameField; EditText siteNameField;
@InjectView(R.id.typeField) @InjectView(R.id.siteTypeField)
Spinner typeField; Spinner siteTypeField;
@InjectView(R.id.counterField) @InjectView(R.id.counterField)
EditText counterField; EditText counterField;
@InjectView(R.id.siteVersionField)
Spinner siteVersionField;
@InjectView(R.id.sitePasswordField) @InjectView(R.id.sitePasswordField)
TextView sitePasswordField; TextView sitePasswordField;
@InjectView(R.id.rememberPasswordField) @InjectView(R.id.sitePasswordTip)
CheckBox rememberPasswordField; TextView sitePasswordTip;
private int hc_userName; @InjectView(R.id.rememberFullNameField)
private int hc_masterPassword; CheckBox rememberFullNameField;
@InjectView(R.id.rememberPasswordField)
CheckBox forgetPasswordField;
@InjectView(R.id.maskPasswordField)
CheckBox maskPasswordField;
private int hc_userName;
private int hc_masterPassword;
private String sitePassword;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate( savedInstanceState ); super.onCreate( savedInstanceState );
Res.init( getResources() ); Res.init( getResources() );
@ -77,14 +93,25 @@ public class EmergencyActivity extends Activity {
setContentView( R.layout.activity_emergency ); setContentView( R.layout.activity_emergency );
ButterKnife.inject( this ); ButterKnife.inject( this );
userNameField.setOnFocusChangeListener( updateMasterKey ); fullNameField.setOnFocusChangeListener( updateMasterKey );
masterPasswordField.setOnFocusChangeListener( updateMasterKey ); masterPasswordField.setOnFocusChangeListener( updateMasterKey );
siteNameField.addTextChangedListener( updateSitePassword ); siteNameField.addTextChangedListener( updateSitePassword );
typeField.setOnItemSelectedListener( updateSitePassword ); siteTypeField.setOnItemSelectedListener( updateSitePassword );
counterField.addTextChangedListener( updateSitePassword ); counterField.addTextChangedListener( updateSitePassword );
siteVersionField.setOnItemSelectedListener( updateMasterKey );
sitePasswordField.addTextChangedListener( new ValueChangedListener() {
@Override
void update() {
boolean noPassword = TextUtils.isEmpty( sitePasswordField.getText() );
sitePasswordTip.setVisibility( noPassword? View.INVISIBLE: View.VISIBLE );
userNameField.setTypeface( Res.exo_Thin ); if (noPassword)
userNameField.setPaintFlags( userNameField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG ); sitePassword = null;
}
} );
fullNameField.setTypeface( Res.exo_Thin );
fullNameField.setPaintFlags( fullNameField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
masterPasswordField.setTypeface( Res.sourceCodePro_ExtraLight ); masterPasswordField.setTypeface( Res.sourceCodePro_ExtraLight );
masterPasswordField.setPaintFlags( masterPasswordField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG ); masterPasswordField.setPaintFlags( masterPasswordField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
siteNameField.setTypeface( Res.exo_Regular ); siteNameField.setTypeface( Res.exo_Regular );
@ -92,13 +119,33 @@ public class EmergencyActivity extends Activity {
sitePasswordField.setTypeface( Res.sourceCodePro_Black ); sitePasswordField.setTypeface( Res.sourceCodePro_Black );
sitePasswordField.setPaintFlags( sitePasswordField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG ); sitePasswordField.setPaintFlags( sitePasswordField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
typeField.setAdapter( new ArrayAdapter<>( this, R.layout.type_item, MPSiteType.forClass( MPSiteTypeClass.Generated ) ) ); siteTypeField.setAdapter( new ArrayAdapter<>( this, R.layout.spinner_item, MPSiteType.forClass( MPSiteTypeClass.Generated ) ) );
typeField.setSelection( MPSiteType.GeneratedLong.ordinal() ); siteTypeField.setSelection( MPSiteType.GeneratedLong.ordinal() );
rememberPasswordField.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() { siteVersionField.setAdapter( new ArrayAdapter<>( this, R.layout.spinner_item, MasterKey.Version.values() ) );
siteVersionField.setSelection( MasterKey.Version.CURRENT.ordinal() );
rememberFullNameField.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() {
@Override @Override
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) { public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
getPreferences( MODE_PRIVATE ).edit().putBoolean( "rememberPassword", isChecked ).apply(); getPreferences( MODE_PRIVATE ).edit().putBoolean( "rememberFullName", isChecked ).apply();
if (isChecked)
getPreferences( MODE_PRIVATE ).edit().putString( "fullName", fullNameField.getText().toString() ).apply();
else
getPreferences( MODE_PRIVATE ).edit().putString( "fullName", "" ).apply();
}
} );
forgetPasswordField.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
getPreferences( MODE_PRIVATE ).edit().putBoolean( "forgetPassword", isChecked ).apply();
}
} );
maskPasswordField.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
getPreferences( MODE_PRIVATE ).edit().putBoolean( "maskPassword", isChecked ).apply();
sitePasswordField.setTransformationMethod( isChecked? new PasswordTransformationMethod(): null );
} }
} ); } );
} }
@ -107,14 +154,21 @@ public class EmergencyActivity extends Activity {
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
userNameField.setText( getPreferences( MODE_PRIVATE ).getString( "userName", "" ) ); fullNameField.setText( getPreferences( MODE_PRIVATE ).getString( "fullName", "" ) );
rememberPasswordField.setSelected( isRememberPasswordEnabled() ); rememberFullNameField.setChecked( isRememberFullNameEnabled() );
masterPasswordField.requestFocus(); forgetPasswordField.setChecked( isForgetPasswordEnabled() );
maskPasswordField.setChecked( isMaskPasswordEnabled() );
sitePasswordField.setTransformationMethod( isMaskPasswordEnabled()? new PasswordTransformationMethod(): null );
if (TextUtils.isEmpty( masterPasswordField.getText() ))
masterPasswordField.requestFocus();
else
siteNameField.requestFocus();
} }
@Override @Override
protected void onPause() { protected void onPause() {
if (!isRememberPasswordEnabled()) { if (isForgetPasswordEnabled()) {
synchronized (this) { synchronized (this) {
hc_userName = hc_masterPassword = 0; hc_userName = hc_masterPassword = 0;
if (masterKeyFuture != null) { if (masterKeyFuture != null) {
@ -122,44 +176,64 @@ public class EmergencyActivity extends Activity {
masterKeyFuture = null; masterKeyFuture = null;
} }
sitePasswordField.setText( "" ); masterPasswordField.setText( "" );
progressView.setVisibility( View.INVISIBLE );
} }
} }
siteNameField.setText( "" );
sitePasswordField.setText( "" );
progressView.setVisibility( View.INVISIBLE );
super.onPause(); super.onPause();
} }
private boolean isRememberPasswordEnabled() { private boolean isRememberFullNameEnabled() {
return getPreferences( MODE_PRIVATE ).getBoolean( "rememberPassword", false ); return getPreferences( MODE_PRIVATE ).getBoolean( "rememberFullName", false );
}
private boolean isForgetPasswordEnabled() {
return getPreferences( MODE_PRIVATE ).getBoolean( "forgetPassword", false );
}
private boolean isMaskPasswordEnabled() {
return getPreferences( MODE_PRIVATE ).getBoolean( "maskPassword", false );
} }
private synchronized void updateMasterKey() { private synchronized void updateMasterKey() {
final String userName = userNameField.getText().toString(); final String fullName = fullNameField.getText().toString();
final String masterPassword = masterPasswordField.getText().toString(); final char[] masterPassword = masterPasswordField.getText().toString().toCharArray();
if (userName.hashCode() == hc_userName && masterPassword.hashCode() == hc_masterPassword) final MasterKey.Version version = (MasterKey.Version) siteVersionField.getSelectedItem();
try {
if (fullName.hashCode() == hc_userName && Arrays.hashCode( masterPassword ) == hc_masterPassword &&
masterKeyFuture != null && masterKeyFuture.get().getAlgorithm() == version)
return;
}
catch (InterruptedException | ExecutionException e) {
return; return;
hc_userName = userName.hashCode(); }
hc_masterPassword = masterPassword.hashCode(); hc_userName = fullName.hashCode();
hc_masterPassword = Arrays.hashCode( masterPassword );
getPreferences( MODE_PRIVATE ).edit().putString( "userName", userName ).apply(); if (isRememberFullNameEnabled())
getPreferences( MODE_PRIVATE ).edit().putString( "fullName", fullName ).apply();
if (masterKeyFuture != null) if (masterKeyFuture != null)
masterKeyFuture.cancel( true ); masterKeyFuture.cancel( true );
if (userName.isEmpty() || masterPassword.isEmpty()) { if (fullName.isEmpty() || masterPassword.length == 0) {
sitePasswordField.setText( "" ); sitePasswordField.setText( "" );
progressView.setVisibility( View.INVISIBLE ); progressView.setVisibility( View.INVISIBLE );
return; return;
} }
sitePasswordField.setText( "" );
progressView.setVisibility( View.VISIBLE ); progressView.setVisibility( View.VISIBLE );
(masterKeyFuture = executor.submit( new Callable<MasterKey>() { (masterKeyFuture = executor.submit( new Callable<MasterKey>() {
@Override @Override
public MasterKey call() public MasterKey call()
throws Exception { throws Exception {
try { try {
return MasterKey.create( userName, masterPassword ); return MasterKey.create( version, fullName, masterPassword );
} }
catch (RuntimeException e) { catch (RuntimeException e) {
sitePasswordField.setText( "" ); sitePasswordField.setText( "" );
@ -183,21 +257,25 @@ public class EmergencyActivity extends Activity {
private void updateSitePassword() { private void updateSitePassword() {
final String siteName = siteNameField.getText().toString(); final String siteName = siteNameField.getText().toString();
final MPSiteType type = (MPSiteType) typeField.getSelectedItem(); final MPSiteType type = (MPSiteType) siteTypeField.getSelectedItem();
final int counter = ConversionUtils.toIntegerNN( counterField.getText() ); final int counter = ConversionUtils.toIntegerNN( counterField.getText() );
if (masterKeyFuture == null || siteName.isEmpty() || type == null) { if (masterKeyFuture == null || siteName.isEmpty() || type == null) {
sitePasswordField.setText( "" ); sitePasswordField.setText( "" );
progressView.setVisibility( View.INVISIBLE ); progressView.setVisibility( View.INVISIBLE );
if (masterKeyFuture == null)
updateMasterKey();
return; return;
} }
sitePasswordField.setText( "" );
progressView.setVisibility( View.VISIBLE ); progressView.setVisibility( View.VISIBLE );
executor.submit( new Runnable() { executor.submit( new Runnable() {
@Override @Override
public void run() { public void run() {
try { try {
final String sitePassword = masterKeyFuture.get().encode( siteName, type, counter, MPSiteVariant.Password, null ); sitePassword = masterKeyFuture.get().encode( siteName, type, counter, MPSiteVariant.Password, null );
runOnUiThread( new Runnable() { runOnUiThread( new Runnable() {
@Override @Override
@ -228,8 +306,7 @@ public class EmergencyActivity extends Activity {
} }
public void copySitePassword(View view) { public void copySitePassword(View view) {
String sitePassword = sitePasswordField.getText().toString(); if (TextUtils.isEmpty( sitePassword ))
if (sitePassword.isEmpty())
return; return;
ClipDescription description = new ClipDescription( strf( "Password for %s", siteNameField.getText() ), ClipDescription description = new ClipDescription( strf( "Password for %s", siteNameField.getText() ),

View File

@ -45,7 +45,8 @@ public class CLI {
throws IOException { throws IOException {
// Read information from the environment. // Read information from the environment.
String siteName = null, masterPassword, context = null; char[] masterPassword = null;
String siteName = null, context = null;
String userName = System.getenv( ENV_USERNAME ); String userName = System.getenv( ENV_USERNAME );
String siteTypeName = ifNotNullElse( System.getenv( ENV_SITETYPE ), "" ); String siteTypeName = ifNotNullElse( System.getenv( ENV_SITETYPE ), "" );
MPSiteType siteType = siteTypeName.isEmpty()? MPSiteType.GeneratedLong: MPSiteType.forOption( siteTypeName ); MPSiteType siteType = siteTypeName.isEmpty()? MPSiteType.GeneratedLong: MPSiteType.forOption( siteTypeName );
@ -174,11 +175,11 @@ public class CLI {
} }
if (console != null) if (console != null)
masterPassword = new String( console.readPassword( "%s's master password: ", userName ) ); masterPassword = console.readPassword( "%s's master password: ", userName );
else { else {
System.err.format( "%s's master password: ", userName ); System.err.format( "%s's master password: ", userName );
masterPassword = lineReader.readLine(); masterPassword = lineReader.readLine().toCharArray();
} }
} }

View File

@ -0,0 +1,10 @@
/**
*
* @author lhunath, 15-02-04
*/
@ParametersAreNonnullByDefault
package com.lyndir.masterpassword;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@ -1,7 +1,7 @@
package com.lyndir.masterpassword.gui; package com.lyndir.masterpassword.gui;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.lyndir.masterpassword.util.Components; import com.lyndir.masterpassword.gui.util.Components;
import java.awt.*; import java.awt.*;
import javax.swing.*; import javax.swing.*;
@ -32,7 +32,7 @@ public abstract class AuthenticationPanel extends Components.GradientPanel {
} }
protected void updateUser(boolean repack) { protected void updateUser(boolean repack) {
unlockFrame.setUser( getSelectedUser() ); unlockFrame.updateUser( getSelectedUser() );
validate(); validate();
if (repack) if (repack)
@ -41,6 +41,8 @@ public abstract class AuthenticationPanel extends Components.GradientPanel {
protected abstract User getSelectedUser(); protected abstract User getSelectedUser();
public abstract char[] getMasterPassword();
public Component getFocusComponent() { public Component getFocusComponent() {
return null; return null;
} }

View File

@ -21,6 +21,8 @@ import com.google.common.base.Charsets;
import com.google.common.io.*; import com.google.common.io.*;
import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.TypeUtils; import com.lyndir.lhunath.opal.system.util.TypeUtils;
import com.lyndir.masterpassword.MasterKey;
import com.lyndir.masterpassword.model.IncorrectMasterPasswordException;
import java.io.*; import java.io.*;
import java.net.URI; import java.net.URI;
import java.net.URL; import java.net.URL;
@ -93,19 +95,9 @@ public class GUI implements UnlockFrame.SignInCallback {
} }
@Override @Override
public boolean signedIn(final User user) { public void signedIn(final User user) {
if (!user.isKeyAvailable()) passwordFrame = newPasswordFrame( user );
return false; open();
try {
user.getKey();
passwordFrame = newPasswordFrame( user );
open();
return true;
} catch (MasterKeyException e) {
JOptionPane.showMessageDialog( null, e.getLocalizedMessage(), "Sign In Failed", JOptionPane.ERROR_MESSAGE );
return false;
}
} }
protected PasswordFrame newPasswordFrame(final User user) { protected PasswordFrame newPasswordFrame(final User user) {

View File

@ -1,6 +1,6 @@
package com.lyndir.masterpassword.gui; package com.lyndir.masterpassword.gui;
import com.lyndir.masterpassword.util.Components; import com.lyndir.masterpassword.gui.util.Components;
import java.awt.*; import java.awt.*;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
@ -55,7 +55,12 @@ public class IncognitoAuthenticationPanel extends AuthenticationPanel implements
@Override @Override
protected User getSelectedUser() { protected User getSelectedUser() {
return new IncognitoUser( fullNameField.getText(), new String( masterPasswordField.getPassword() ) ); return new IncognitoUser( fullNameField.getText() );
}
@Override
public char[] getMasterPassword() {
return masterPasswordField.getPassword();
} }
@Override @Override

View File

@ -1,7 +1,8 @@
package com.lyndir.masterpassword.gui; package com.lyndir.masterpassword.gui;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.lyndir.masterpassword.MasterKey; import com.lyndir.masterpassword.model.IncorrectMasterPasswordException;
import javax.annotation.Nullable;
/** /**
@ -9,32 +10,27 @@ import com.lyndir.masterpassword.MasterKey;
*/ */
public class IncognitoUser extends User { public class IncognitoUser extends User {
private final String fullName; private final String fullName;
private final String masterPassword; private char[] masterPassword;
private MasterKey.Version algorithmVersion;
public IncognitoUser(final String fullName, final String masterPassword) { public IncognitoUser(final String fullName) {
this.fullName = fullName; this.fullName = fullName;
this.masterPassword = masterPassword;
} }
public String getFullName() { public String getFullName() {
return fullName; return fullName;
} }
@Nullable
@Override @Override
protected String getMasterPassword() { protected char[] getMasterPassword() {
return masterPassword; return masterPassword;
} }
@Override @Override
public MasterKey.Version getAlgorithmVersion() { public void authenticate(final char[] masterPassword)
return algorithmVersion; throws IncorrectMasterPasswordException {
} this.masterPassword = masterPassword;
@Override
public void setAlgorithmVersion(final MasterKey.Version algorithmVersion) {
this.algorithmVersion = algorithmVersion;
} }
@Override @Override

View File

@ -1,11 +0,0 @@
package com.lyndir.masterpassword.gui;
/**
* @author lhunath, 14-12-17
*/
public class MasterKeyException extends Exception {
public MasterKeyException(final String message) {
super( message );
}
}

View File

@ -5,7 +5,7 @@ import com.google.common.collect.*;
import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.masterpassword.model.MPUser; import com.lyndir.masterpassword.model.MPUser;
import com.lyndir.masterpassword.model.MPUserFileManager; import com.lyndir.masterpassword.model.MPUserFileManager;
import com.lyndir.masterpassword.util.Components; import com.lyndir.masterpassword.gui.util.Components;
import java.awt.*; import java.awt.*;
import java.awt.event.*; import java.awt.event.*;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -90,11 +90,12 @@ public class ModelAuthenticationPanel extends AuthenticationPanel implements Ite
if (selectedIndex < 0) if (selectedIndex < 0)
return null; return null;
ModelUser selectedUser = userField.getModel().getElementAt( selectedIndex ); return userField.getModel().getElementAt( selectedIndex );
if (selectedUser != null) }
selectedUser.setMasterPassword( new String( masterPasswordField.getPassword() ) );
return selectedUser; @Override
public char[] getMasterPassword() {
return masterPasswordField.getPassword();
} }
@Override @Override

View File

@ -1,14 +1,10 @@
package com.lyndir.masterpassword.gui; package com.lyndir.masterpassword.gui;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf; import com.google.common.base.*;
import com.google.common.base.Function;
import com.google.common.collect.FluentIterable; import com.google.common.collect.FluentIterable;
import com.lyndir.lhunath.opal.system.util.ObjectUtils;
import com.lyndir.masterpassword.MasterKey;
import com.lyndir.masterpassword.model.*; import com.lyndir.masterpassword.model.*;
import javax.annotation.Nullable; import java.util.Arrays;
import org.jetbrains.annotations.NotNull; import javax.annotation.*;
/** /**
@ -17,7 +13,9 @@ import org.jetbrains.annotations.NotNull;
public class ModelUser extends User { public class ModelUser extends User {
private final MPUser model; private final MPUser model;
private String masterPassword;
@Nullable
private char[] masterPassword;
public ModelUser(MPUser model) { public ModelUser(MPUser model) {
this.model = model; this.model = model;
@ -32,22 +30,12 @@ public class ModelUser extends User {
return model.getFullName(); return model.getFullName();
} }
@Nullable
@Override @Override
protected String getMasterPassword() { protected char[] getMasterPassword() {
return masterPassword; return masterPassword;
} }
@Override
public MasterKey.Version getAlgorithmVersion() {
return model.getAlgorithmVersion();
}
@Override
public void setAlgorithmVersion(final MasterKey.Version algorithmVersion) {
model.setAlgorithmVersion( algorithmVersion );
MPUserFileManager.get().save();
}
@Override @Override
public int getAvatar() { public int getAvatar() {
return model.getAvatar(); return model.getAvatar();
@ -58,30 +46,20 @@ public class ModelUser extends User {
MPUserFileManager.get().save(); MPUserFileManager.get().save();
} }
public void setMasterPassword(final String masterPassword) { public void authenticate(final char[] masterPassword)
throws IncorrectMasterPasswordException {
model.authenticate( masterPassword );
this.masterPassword = masterPassword; this.masterPassword = masterPassword;
} }
@NotNull
@Override
public MasterKey getKey() throws MasterKeyException {
MasterKey key = super.getKey();
if (!model.hasKeyID()) {
model.setKeyID( key.getKeyID() );
MPUserFileManager.get().save();
} else if (!model.hasKeyID( key.getKeyID() )) {
reset();
throw new MasterKeyException( strf( "Incorrect master password for user: %s", getFullName() ) );
}
return key;
}
@Override @Override
public void reset() { public void reset() {
super.reset(); super.reset();
masterPassword = null; if (masterPassword != null) {
Arrays.fill( masterPassword, (char) 0 );
masterPassword = null;
}
} }
@Override @Override
@ -89,8 +67,8 @@ public class ModelUser extends User {
return FluentIterable.from( model.findSitesByName( query ) ).transform( new Function<MPSiteResult, Site>() { return FluentIterable.from( model.findSitesByName( query ) ).transform( new Function<MPSiteResult, Site>() {
@Nullable @Nullable
@Override @Override
public Site apply(final MPSiteResult result) { public Site apply(@Nullable final MPSiteResult result) {
return new ModelSite( result ); return new ModelSite( Preconditions.checkNotNull( result ) );
} }
} ); } );
} }

View File

@ -5,12 +5,13 @@ import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.*; import com.google.common.util.concurrent.*;
import com.lyndir.masterpassword.*; import com.lyndir.masterpassword.*;
import com.lyndir.masterpassword.util.Components; import com.lyndir.masterpassword.gui.util.Components;
import java.awt.*; import java.awt.*;
import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.StringSelection;
import java.awt.event.*; import java.awt.event.*;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.swing.*; import javax.swing.*;
import javax.swing.event.*; import javax.swing.event.*;
@ -49,7 +50,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
sitePanel.setOpaque( true ); sitePanel.setOpaque( true );
sitePanel.setBackground( Res.colors().controlBg() ); sitePanel.setBackground( Res.colors().controlBg() );
sitePanel.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) ); sitePanel.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
add( Components.bordered( sitePanel, BorderFactory.createRaisedBevelBorder(), Res.colors().frameBg() ), BorderLayout.CENTER ); add( Components.borderPanel( sitePanel, BorderFactory.createRaisedBevelBorder(), Res.colors().frameBg() ), BorderLayout.CENTER );
// User // User
sitePanel.add( Components.label( strf( "Generating passwords for: %s", user.getFullName() ), JLabel.CENTER ) ); sitePanel.add( Components.label( strf( "Generating passwords for: %s", user.getFullName() ), JLabel.CENTER ) );
@ -66,7 +67,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
public void actionPerformed(final ActionEvent e) { public void actionPerformed(final ActionEvent e) {
Futures.addCallback( updatePassword(), new FutureCallback<String>() { Futures.addCallback( updatePassword(), new FutureCallback<String>() {
@Override @Override
public void onSuccess(final String sitePassword) { public void onSuccess(@Nullable final String sitePassword) {
StringSelection clipboardContents = new StringSelection( sitePassword ); StringSelection clipboardContents = new StringSelection( sitePassword );
Toolkit.getDefaultToolkit().getSystemClipboard().setContents( clipboardContents, null ); Toolkit.getDefaultToolkit().getSystemClipboard().setContents( clipboardContents, null );
@ -119,7 +120,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
siteVersionField.setFont( Res.valueFont().deriveFont( 12f ) ); siteVersionField.setFont( Res.valueFont().deriveFont( 12f ) );
siteVersionField.setAlignmentX( RIGHT_ALIGNMENT ); siteVersionField.setAlignmentX( RIGHT_ALIGNMENT );
siteVersionField.setSelectedItem( user.getAlgorithmVersion() ); siteVersionField.setSelectedItem( MasterKey.Version.CURRENT );
siteVersionField.addItemListener( new ItemListener() { siteVersionField.addItemListener( new ItemListener() {
@Override @Override
public void itemStateChanged(final ItemEvent e) { public void itemStateChanged(final ItemEvent e) {
@ -165,7 +166,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
passwordContainer.setOpaque( true ); passwordContainer.setOpaque( true );
passwordContainer.setBackground( Color.white ); passwordContainer.setBackground( Color.white );
passwordContainer.setBorder( BorderFactory.createEmptyBorder( 8, 8, 8, 8 ) ); passwordContainer.setBorder( BorderFactory.createEmptyBorder( 8, 8, 8, 8 ) );
add( Components.bordered( passwordContainer, BorderFactory.createLoweredSoftBevelBorder(), Res.colors().frameBg() ), add( Components.borderPanel( passwordContainer, BorderFactory.createLoweredSoftBevelBorder(), Res.colors().frameBg() ),
BorderLayout.SOUTH ); BorderLayout.SOUTH );
pack(); pack();
@ -197,7 +198,8 @@ public class PasswordFrame extends JFrame implements DocumentListener {
final MasterKey.Version siteVersion = siteVersionField.getItemAt( siteVersionField.getSelectedIndex() ); final MasterKey.Version siteVersion = siteVersionField.getItemAt( siteVersionField.getSelectedIndex() );
final int siteCounter = (Integer) siteCounterField.getValue(); final int siteCounter = (Integer) siteCounterField.getValue();
final Site site = currentSite != null && currentSite.getSiteName().equals( siteNameQuery )? currentSite final Site site = currentSite != null && currentSite.getSiteName().equals( siteNameQuery )? currentSite
: Iterables.getFirst( user.findSitesByName( siteNameQuery ), new IncognitoSite( siteNameQuery, siteType, siteCounter, user.getAlgorithmVersion() ) ); : Iterables.getFirst( user.findSitesByName( siteNameQuery ),
new IncognitoSite( siteNameQuery, siteType, siteCounter, siteVersion ) );
assert site != null; assert site != null;
if (site == currentSite) { if (site == currentSite) {
site.setSiteType( siteType ); site.setSiteType( siteType );
@ -215,7 +217,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
} ); } );
Futures.addCallback( passwordFuture, new FutureCallback<String>() { Futures.addCallback( passwordFuture, new FutureCallback<String>() {
@Override @Override
public void onSuccess(final String sitePassword) { public void onSuccess(@Nullable final String sitePassword) {
SwingUtilities.invokeLater( new Runnable() { SwingUtilities.invokeLater( new Runnable() {
@Override @Override
public void run() { public void run() {

View File

@ -2,7 +2,8 @@ package com.lyndir.masterpassword.gui;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*; import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
import com.lyndir.masterpassword.util.Components; import com.lyndir.masterpassword.gui.util.Components;
import com.lyndir.masterpassword.model.IncorrectMasterPasswordException;
import java.awt.*; import java.awt.*;
import java.awt.event.*; import java.awt.event.*;
import javax.swing.*; import javax.swing.*;
@ -34,7 +35,7 @@ public class UnlockFrame extends JFrame {
authenticationContainer.setOpaque( true ); authenticationContainer.setOpaque( true );
authenticationContainer.setBackground( Res.colors().controlBg() ); authenticationContainer.setBackground( Res.colors().controlBg() );
authenticationContainer.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) ); authenticationContainer.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
add( Components.bordered( authenticationContainer, BorderFactory.createRaisedBevelBorder(), Res.colors().frameBg() ) ); add( Components.borderPanel( authenticationContainer, BorderFactory.createRaisedBevelBorder(), Res.colors().frameBg() ) );
// Sign In // Sign In
JPanel signInBox = Components.boxLayout( BoxLayout.LINE_AXIS, Box.createGlue(), signInButton = Components.button( "Sign In" ), JPanel signInBox = Components.boxLayout( BoxLayout.LINE_AXIS, Box.createGlue(), signInButton = Components.button( "Sign In" ),
@ -108,13 +109,13 @@ public class UnlockFrame extends JFrame {
} ); } );
} }
void setUser(User user) { void updateUser(User user) {
this.user = user; this.user = user;
checkSignIn(); checkSignIn();
} }
boolean checkSignIn() { boolean checkSignIn() {
boolean enabled = user != null && !user.getFullName().isEmpty() && user.isKeyAvailable(); boolean enabled = user != null && !user.getFullName().isEmpty() && authenticationPanel.getMasterPassword().length > 0;
signInButton.setEnabled( enabled ); signInButton.setEnabled( enabled );
return enabled; return enabled;
@ -133,29 +134,37 @@ public class UnlockFrame extends JFrame {
Res.execute( this, new Runnable() { Res.execute( this, new Runnable() {
@Override @Override
public void run() { public void run() {
final boolean success = signInCallback.signedIn( user ); try {
user.authenticate( authenticationPanel.getMasterPassword() );
SwingUtilities.invokeLater( new Runnable() { SwingUtilities.invokeLater( new Runnable() {
@Override @Override
public void run() { public void run() {
if (success) { signInCallback.signedIn( user );
dispose(); dispose();
return;
} }
} );
}
catch (final IncorrectMasterPasswordException e) {
SwingUtilities.invokeLater( new Runnable() {
@Override
public void run() {
JOptionPane.showMessageDialog( null, e.getLocalizedMessage(), "Sign In Failed", JOptionPane.ERROR_MESSAGE );
authenticationPanel.reset();
signInButton.setText( "Sign In" );
for (JComponent signInComponent : signInComponents)
signInComponent.setEnabled( true );
checkSignIn();
}
} );
}
authenticationPanel.reset();
signInButton.setText( "Sign In" );
for (JComponent signInComponent : signInComponents)
signInComponent.setEnabled( true );
checkSignIn();
}
} );
} }
} ); } );
} }
interface SignInCallback { interface SignInCallback {
boolean signedIn(User user); void signedIn(User user);
} }
} }

View File

@ -4,9 +4,11 @@ import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.lyndir.masterpassword.MasterKey; import com.lyndir.masterpassword.MasterKey;
import com.lyndir.masterpassword.model.IncorrectMasterPasswordException;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.Objects; import java.util.Objects;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/** /**
@ -19,33 +21,23 @@ public abstract class User {
public abstract String getFullName(); public abstract String getFullName();
protected abstract String getMasterPassword(); @Nullable
protected abstract char[] getMasterPassword();
public abstract MasterKey.Version getAlgorithmVersion(); public abstract void authenticate(final char[] masterPassword)
throws IncorrectMasterPasswordException;
public abstract void setAlgorithmVersion(final MasterKey.Version algorithmVersion);
public int getAvatar() { public int getAvatar() {
return 0; return 0;
} }
public boolean isKeyAvailable() { public boolean isKeyAvailable() {
String masterPassword = getMasterPassword(); return getMasterPassword() != null;
return masterPassword != null && !masterPassword.isEmpty();
} }
@Nonnull @Nonnull
public MasterKey getKey() throws MasterKeyException { public MasterKey getKey(MasterKey.Version algorithmVersion) {
return getKey( getAlgorithmVersion() ); char[] masterPassword = getMasterPassword();
}
@Nonnull
public MasterKey getKey(MasterKey.Version algorithmVersion) throws MasterKeyException {
String masterPassword = getMasterPassword();
if (masterPassword == null || masterPassword.isEmpty()) {
reset();
throw new MasterKeyException( strf( "Master password unknown for user: %s", getFullName() ) );
}
MasterKey key = keyByVersion.get( algorithmVersion ); MasterKey key = keyByVersion.get( algorithmVersion );
if (key == null) if (key == null)

View File

@ -0,0 +1,9 @@
/**
*
* @author lhunath, 15-02-04
*/
@ParametersAreNonnullByDefault
package com.lyndir.masterpassword.gui;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@ -1,8 +1,8 @@
package com.lyndir.masterpassword.util; package com.lyndir.masterpassword.gui.util;
import com.lyndir.masterpassword.gui.ModelUser;
import com.lyndir.masterpassword.gui.Res; import com.lyndir.masterpassword.gui.Res;
import java.awt.*; import java.awt.*;
import javax.annotation.Nullable;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.Border; import javax.swing.border.Border;
import javax.swing.border.CompoundBorder; import javax.swing.border.CompoundBorder;
@ -23,11 +23,11 @@ public abstract class Components {
return container; return container;
} }
public static GradientPanel bordered(final JComponent component, final Border border) { public static GradientPanel borderPanel(final JComponent component, @Nullable final Border border) {
return bordered( component, border, null ); return borderPanel( component, border, null );
} }
public static GradientPanel bordered(final JComponent component, final Border border, Color background) { public static GradientPanel borderPanel(final JComponent component, @Nullable final Border border, @Nullable Color background) {
GradientPanel box = boxLayout( BoxLayout.LINE_AXIS, component ); GradientPanel box = boxLayout( BoxLayout.LINE_AXIS, component );
if (border != null) if (border != null)
@ -39,7 +39,7 @@ public abstract class Components {
return box; return box;
} }
public static GradientPanel gradientPanel(final LayoutManager layout, final Color color) { public static GradientPanel gradientPanel(@Nullable final LayoutManager layout, @Nullable final Color color) {
return new GradientPanel( layout, color ) { return new GradientPanel( layout, color ) {
{ {
setOpaque( color != null ); setOpaque( color != null );
@ -176,20 +176,24 @@ public abstract class Components {
public static class GradientPanel extends JPanel { public static class GradientPanel extends JPanel {
private Color gradientColor; @Nullable
private Color gradientColor;
@Nullable
private GradientPaint paint; private GradientPaint paint;
protected GradientPanel(final LayoutManager layout, final Color gradientColor) { protected GradientPanel(@Nullable final LayoutManager layout, @Nullable final Color gradientColor) {
super( layout ); super( layout );
this.gradientColor = gradientColor; this.gradientColor = gradientColor;
setBackground( null ); setBackground( null );
} }
@Nullable
public Color getGradientColor() { public Color getGradientColor() {
return gradientColor; return gradientColor;
} }
public void setGradientColor(final Color gradientColor) { public void setGradientColor(@Nullable final Color gradientColor) {
this.gradientColor = gradientColor; this.gradientColor = gradientColor;
} }

View File

@ -0,0 +1,9 @@
/**
*
* @author lhunath, 15-02-04
*/
@ParametersAreNonnullByDefault
package com.lyndir.masterpassword.gui.util;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@ -0,0 +1,19 @@
package com.lyndir.masterpassword.model;
/**
* @author lhunath, 14-12-17
*/
public class IncorrectMasterPasswordException extends Exception {
private final MPUser user;
public IncorrectMasterPasswordException(final MPUser user) {
super( "Incorrect master password for user: " + user.getFullName() );
this.user = user;
}
public MPUser getUser() {
return user;
}
}

View File

@ -39,8 +39,9 @@ public class MPSite {
this.siteCounter = siteCounter; this.siteCounter = siteCounter;
} }
protected MPSite(final MPUser user, final MasterKey.Version algorithmVersion, final Instant lastUsed, final String siteName, final MPSiteType siteType, final int siteCounter, protected MPSite(final MPUser user, final MasterKey.Version algorithmVersion, final Instant lastUsed, final String siteName,
final int uses, final String loginName, final String importContent) { final MPSiteType siteType, final int siteCounter, final int uses, @Nullable final String loginName,
@Nullable final String importContent) {
this.user = user; this.user = user;
this.algorithmVersion = algorithmVersion; this.algorithmVersion = algorithmVersion;
this.lastUsed = lastUsed; this.lastUsed = lastUsed;

View File

@ -4,6 +4,7 @@ import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf; import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.masterpassword.MasterKey; import com.lyndir.masterpassword.MasterKey;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -63,8 +64,8 @@ public class MPSiteMarshaller {
header.append( "# Full Name: " ).append( user.getFullName() ).append( '\n' ); header.append( "# Full Name: " ).append( user.getFullName() ).append( '\n' );
header.append( "# Avatar: " ).append( user.getAvatar() ).append( '\n' ); header.append( "# Avatar: " ).append( user.getAvatar() ).append( '\n' );
header.append( "# Key ID: " ).append( user.exportKeyID() ).append( '\n' ); header.append( "# Key ID: " ).append( user.exportKeyID() ).append( '\n' );
header.append( "# Version: " ).append( user.getAlgorithmVersion().toBundleVersion() ).append( '\n' ); header.append( "# Version: " ).append( MasterKey.Version.CURRENT.toBundleVersion() ).append( '\n' );
header.append( "# Algorithm: " ).append( user.getAlgorithmVersion().toInt() ).append( '\n' ); header.append( "# Algorithm: " ).append( MasterKey.Version.CURRENT.toInt() ).append( '\n' );
header.append( "# Default Type: " ).append( user.getDefaultType().getType() ).append( '\n' ); header.append( "# Default Type: " ).append( user.getDefaultType().getType() ).append( '\n' );
header.append( "# Passwords: " ).append( contentMode.name() ).append( '\n' ); header.append( "# Passwords: " ).append( contentMode.name() ).append( '\n' );
header.append( "##\n" ); header.append( "##\n" );
@ -82,7 +83,7 @@ public class MPSiteMarshaller {
site.getUses(), // uses site.getUses(), // uses
strf( "%d:%d:%d", // strf( "%d:%d:%d", //
site.getSiteType().getType(), // type site.getSiteType().getType(), // type
site.getAlgorithmVersion(), // algorithm site.getAlgorithmVersion().toInt(), // algorithm
site.getSiteCounter() ), // counter site.getSiteCounter() ), // counter
ifNotNullElse( site.getLoginName(), "" ), // loginName ifNotNullElse( site.getLoginName(), "" ), // loginName
site.getSiteName(), // siteName site.getSiteName(), // siteName

View File

@ -8,6 +8,8 @@ import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.masterpassword.MPSiteType; import com.lyndir.masterpassword.MPSiteType;
import com.lyndir.masterpassword.MasterKey; import com.lyndir.masterpassword.MasterKey;
import java.util.*; import java.util.*;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.joda.time.*; import org.joda.time.*;
@ -19,6 +21,7 @@ public class MPUser implements Comparable<MPUser> {
private final String fullName; private final String fullName;
private final Collection<MPSite> sites = Sets.newHashSet(); private final Collection<MPSite> sites = Sets.newHashSet();
@Nullable
private byte[] keyID; private byte[] keyID;
private MasterKey.Version algorithmVersion; private MasterKey.Version algorithmVersion;
private int avatar; private int avatar;
@ -29,12 +32,12 @@ public class MPUser implements Comparable<MPUser> {
this( fullName, null ); this( fullName, null );
} }
public MPUser(final String fullName, final byte[] keyID) { public MPUser(final String fullName, @Nullable final byte[] keyID) {
this( fullName, keyID, MasterKey.Version.CURRENT, 0, MPSiteType.GeneratedLong, new DateTime() ); this( fullName, keyID, MasterKey.Version.CURRENT, 0, MPSiteType.GeneratedLong, new DateTime() );
} }
public MPUser(final String fullName, final byte[] keyID, final MasterKey.Version algorithmVersion, final int avatar, final MPSiteType defaultType, public MPUser(final String fullName, @Nullable final byte[] keyID, final MasterKey.Version algorithmVersion, final int avatar,
final ReadableInstant lastUsed) { final MPSiteType defaultType, final ReadableInstant lastUsed) {
this.fullName = fullName; this.fullName = fullName;
this.keyID = keyID; this.keyID = keyID;
this.algorithmVersion = algorithmVersion; this.algorithmVersion = algorithmVersion;
@ -64,24 +67,31 @@ public class MPUser implements Comparable<MPUser> {
return keyID != null; return keyID != null;
} }
public boolean hasKeyID(final byte[] keyID) {
return Arrays.equals( this.keyID, keyID );
}
public String exportKeyID() { public String exportKeyID() {
return CodeUtils.encodeHex( keyID ); return CodeUtils.encodeHex( keyID );
} }
public void setKeyID(final byte[] keyID) { /**
this.keyID = keyID; * Performs an authentication attempt against the keyID for this user.
} *
* Note: If this user doesn't have a keyID set yet, authentication will always succeed and the key ID will be set as a result.
*
* @param masterPassword The password to authenticate with.
*
* @return The master key for the user if authentication was successful.
*
* @throws IncorrectMasterPasswordException If authentication fails due to the given master password not matching the user's keyID.
*/
@Nonnull
public MasterKey authenticate(final char[] masterPassword)
throws IncorrectMasterPasswordException {
MasterKey masterKey = MasterKey.create( algorithmVersion, getFullName(), masterPassword );
if (keyID == null)
keyID = masterKey.getKeyID();
else if (!Arrays.equals( masterKey.getKeyID(), keyID ))
throw new IncorrectMasterPasswordException( this );
public MasterKey.Version getAlgorithmVersion() { return masterKey;
return algorithmVersion;
}
public void setAlgorithmVersion(final MasterKey.Version algorithmVersion) {
this.algorithmVersion = algorithmVersion;
} }
public int getAvatar() { public int getAvatar() {

View File

@ -0,0 +1,9 @@
/**
*
* @author lhunath, 15-02-04
*/
@ParametersAreNonnullByDefault
package com.lyndir.masterpassword.model;
import javax.annotation.ParametersAreNonnullByDefault;