2
0

Compare commits

..

9 Commits

Author SHA1 Message Date
Maarten Billemont
73372f507a [maven-release-plugin] prepare release 2.2-android 2015-02-05 20:28:25 -05:00
Maarten Billemont
feddd038e3 Also sign masterpassword-gui 2015-02-05 20:27:09 -05:00
Maarten Billemont
3f6773f3a9 Android release preparation. 2015-02-05 17:29:29 -05:00
Maarten Billemont
12b1610dc7 Remove unavailable resources + update graphics. 2015-02-05 16:34:04 -05:00
Maarten Billemont
e20b33a051 Prepare for making Android releases. 2015-02-05 13:43:57 -05:00
Maarten Billemont
b84ae532f2 Some generic code fix-ups throughout. 2015-02-05 13:14:17 -05:00
Maarten Billemont
145008406d Big overhaul for proper site-specific algorithm support and big Android UI update. 2015-02-05 00:56:24 -05:00
Maarten Billemont
a6ab9b9194 Moar UI work on the Java app + support for per-site algorithm versioning. 2015-02-04 19:51:38 -05:00
Maarten Billemont
78c593fc08 Many UI improvements to the Java GUI. 2015-02-04 11:25:18 -05:00
127 changed files with 1549 additions and 841 deletions

View File

@@ -7,13 +7,12 @@
<parent>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword</artifactId>
<version>GIT-SNAPSHOT</version>
<version>2.2</version>
</parent>
<name>Master Password Algorithm Implementation</name>
<description>The implementation of the Master Password algorithm</description>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword-algorithm</artifactId>
<packaging>jar</packaging>
@@ -24,12 +23,12 @@
<dependency>
<groupId>com.lyndir.lhunath.opal</groupId>
<artifactId>opal-system</artifactId>
<version>1.6-p7</version>
<version>1.6-p8</version>
</dependency>
<dependency>
<groupId>com.lyndir.lhunath.opal</groupId>
<artifactId>opal-crypto</artifactId>
<version>1.6-p7</version>
<version>1.6-p8</version>
</dependency>
<!-- EXTERNAL DEPENDENCIES -->

View File

@@ -143,11 +143,11 @@ public enum MPSiteType {
}
/**
* @param name The name of the type to look up. It is matched case insensitively.
* @param name The name fromInt the type to look up. It is matched case insensitively.
*
* @return The type registered with the given name.
*/
public static MPSiteType forName(final String name) {
public static MPSiteType forName(@Nullable final String name) {
if (name == null)
return null;

View File

@@ -3,6 +3,7 @@ package com.lyndir.masterpassword;
import com.google.common.collect.ImmutableList;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.util.List;
import javax.annotation.Nullable;
/**
@@ -61,11 +62,11 @@ public enum MPSiteVariant {
throw logger.bug( "No variant for option: %s", option );
}
/**
* @param name The name of the variant to look up. It is matched case insensitively.
* @param name The name fromInt the variant to look up. It is matched case insensitively.
*
* @return The variant registered with the given name.
*/
public static MPSiteVariant forName(final String name) {
public static MPSiteVariant forName(@Nullable final String name) {
if (name == null)
return null;

View File

@@ -3,10 +3,8 @@ package com.lyndir.masterpassword;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.lyndir.lhunath.opal.system.util.MetaObject;
import java.util.List;
import java.util.Map;
/**

View File

@@ -1,8 +1,6 @@
package com.lyndir.masterpassword;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.MetaObject;
import com.lyndir.lhunath.opal.system.util.ObjectMeta;
/**

View File

@@ -1,145 +1,164 @@
package com.lyndir.masterpassword;
import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
import com.google.common.primitives.Bytes;
import com.lambdaworks.crypto.SCrypt;
import com.lyndir.lhunath.opal.system.*;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.jetbrains.annotations.NotNull;
/**
* @author lhunath, 2014-08-30
*/
public class MasterKey {
public static final int ALGORITHM = 1;
public static final String VERSION = "2.1";
public abstract class MasterKey {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MasterKey.class );
private static final int MP_N = 32768;
private static final int MP_r = 8;
private static final int MP_p = 2;
private static final int MP_dkLen = 64;
private static final int MP_intLen = 32;
private static final Charset MP_charset = Charsets.UTF_8;
private static final ByteOrder MP_byteOrder = ByteOrder.BIG_ENDIAN;
private static final MessageDigests MP_hash = MessageDigests.SHA256;
private static final MessageAuthenticationDigests MP_mac = MessageAuthenticationDigests.HmacSHA256;
private static final Logger logger = Logger.get( MasterKey.class );
@Nonnull
private final String fullName;
private final byte[] masterKey;
private boolean valid;
@Nullable
private byte[] masterKey;
public MasterKey(final String fullName, final String masterPassword) {
public static MasterKey create(final String fullName, final char[] masterPassword) {
return create( Version.CURRENT, fullName, masterPassword );
}
@Nonnull
public static MasterKey create(Version version, final String fullName, final char[] masterPassword) {
switch (version) {
case V0:
return new MasterKeyV0( fullName ).revalidate( masterPassword );
case V1:
return new MasterKeyV1( fullName ).revalidate( masterPassword );
case V2:
return new MasterKeyV2( fullName ).revalidate( masterPassword );
case V3:
return new MasterKeyV3( fullName ).revalidate( masterPassword );
}
throw new UnsupportedOperationException( "Unsupported version: " + version );
}
protected MasterKey(@NotNull final String fullName) {
this.fullName = fullName;
logger.trc( "fullName: %s", fullName );
logger.trc( "masterPassword: %s", masterPassword );
long start = System.currentTimeMillis();
byte[] userNameBytes = fullName.getBytes( MP_charset );
byte[] userNameLengthBytes = bytesForInt( userNameBytes.length );
String mpKeyScope = MPSiteVariant.Password.getScope();
byte[] masterKeySalt = Bytes.concat( mpKeyScope.getBytes( MP_charset ), userNameLengthBytes, userNameBytes );
logger.trc( "key scope: %s", mpKeyScope );
logger.trc( "masterKeySalt ID: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) );
try {
masterKey = SCrypt.scrypt( masterPassword.getBytes( MP_charset ), masterKeySalt, MP_N, MP_r, MP_p, MP_dkLen );
valid = true;
logger.trc( "masterKey ID: %s (derived in %.2fs)", CodeUtils.encodeHex( idForBytes( masterKey ) ),
(System.currentTimeMillis() - start) / 1000D );
}
catch (GeneralSecurityException e) {
throw logger.bug( e );
}
}
@Nullable
protected abstract byte[] deriveKey(final char[] masterPassword);
public abstract Version getAlgorithmVersion();
@NotNull
public String getFullName() {
return fullName;
}
public byte[] getKeyID() {
@Nonnull
protected byte[] getKey() {
Preconditions.checkState( valid );
return idForBytes( masterKey );
return Preconditions.checkNotNull( masterKey );
}
public String encode(final String siteName, final MPSiteType siteType, int siteCounter, final MPSiteVariant siteVariant,
@Nullable final String siteContext) {
Preconditions.checkState( valid );
Preconditions.checkArgument( siteType.getTypeClass() == MPSiteTypeClass.Generated );
Preconditions.checkArgument( !siteName.isEmpty() );
public byte[] getKeyID() {
logger.trc( "siteName: %s", siteName );
logger.trc( "siteCounter: %d", siteCounter );
logger.trc( "siteVariant: %d (%s)", siteVariant.ordinal(), siteVariant );
logger.trc( "siteType: %d (%s)", siteType.ordinal(), siteType );
return idForBytes( getKey() );
}
if (siteCounter == 0)
siteCounter = (int) (System.currentTimeMillis() / (300 * 1000)) * 300;
public abstract String encode(@Nonnull final String siteName, final MPSiteType siteType, int siteCounter,
final MPSiteVariant siteVariant, @Nullable final String siteContext);
String siteScope = siteVariant.getScope();
byte[] siteNameBytes = siteName.getBytes( MP_charset );
byte[] siteNameLengthBytes = bytesForInt( siteNameBytes.length );
byte[] siteCounterBytes = bytesForInt( siteCounter );
byte[] siteContextBytes = siteContext == null? null: siteContext.getBytes( MP_charset );
byte[] siteContextLengthBytes = bytesForInt( siteContextBytes == null? 0: siteContextBytes.length );
logger.trc( "site scope: %s, context: %s", siteScope, siteContext == null? "<empty>": siteContext );
logger.trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)", siteScope, CodeUtils.encodeHex( siteNameLengthBytes ),
siteName, CodeUtils.encodeHex( siteCounterBytes ), CodeUtils.encodeHex( siteContextLengthBytes ),
siteContext == null? "(null)": siteContext );
byte[] sitePasswordInfo = Bytes.concat( siteScope.getBytes( MP_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
if (siteContextBytes != null)
sitePasswordInfo = Bytes.concat( sitePasswordInfo, siteContextLengthBytes, siteContextBytes );
logger.trc( "sitePasswordInfo ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) );
byte[] sitePasswordSeed = MP_mac.of( masterKey, sitePasswordInfo );
logger.trc( "sitePasswordSeed ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordSeed ) ) );
Preconditions.checkState( sitePasswordSeed.length > 0 );
int templateIndex = sitePasswordSeed[0] & 0xFF; // Mask the integer's sign.
MPTemplate template = siteType.getTemplateAtRollingIndex( templateIndex );
logger.trc( "type %s, template: %s", siteType, template.getTemplateString() );
StringBuilder password = new StringBuilder( template.length() );
for (int i = 0; i < template.length(); ++i) {
int characterIndex = sitePasswordSeed[i + 1] & 0xFF; // Mask the integer's sign.
MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i );
char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex );
logger.trc( "class %c, index %d (0x%02X) -> character: %c", characterClass.getIdentifier(), characterIndex,
sitePasswordSeed[i + 1], passwordCharacter );
password.append( passwordCharacter );
}
return password.toString();
public boolean isValid() {
return masterKey != null;
}
public void invalidate() {
valid = false;
Arrays.fill( masterKey, (byte) 0 );
if (masterKey != null) {
Arrays.fill( masterKey, (byte) 0 );
masterKey = null;
}
}
private static byte[] bytesForInt(final int integer) {
return ByteBuffer.allocate( MP_intLen / Byte.SIZE ).order( MP_byteOrder ).putInt( integer ).array();
public MasterKey revalidate(final char[] masterPassword) {
invalidate();
logger.trc( "masterPassword: %s", new String( masterPassword ) );
long start = System.currentTimeMillis();
masterKey = deriveKey( masterPassword );
if (masterKey == null)
logger.dbg( "masterKey calculation failed after %.2fs.", (System.currentTimeMillis() - start) / 1000D );
else
logger.trc( "masterKey ID: %s (derived in %.2fs)", CodeUtils.encodeHex( idForBytes( masterKey ) ),
(System.currentTimeMillis() - start) / 1000D );
return this;
}
private static byte[] idForBytes(final byte[] bytes) {
return MP_hash.of( bytes );
protected abstract byte[] bytesForInt(final int integer);
protected abstract byte[] idForBytes(final byte[] bytes);
public enum Version {
/**
* bugs:
* - does math with chars whose signedness was platform-dependent.
* - miscounted the byte-length fromInt multi-byte site names.
* - miscounted the byte-length fromInt multi-byte full names.
*/
V0,
/**
* bugs:
* - miscounted the byte-length fromInt multi-byte site names.
* - miscounted the byte-length fromInt multi-byte full names.
*/
V1,
/**
* bugs:
* - miscounted the byte-length fromInt multi-byte full names.
*/
V2,
/**
* bugs:
* - no known issues.
*/
V3;
public static final Version CURRENT = V3;
public static Version fromInt(final int algorithmVersion) {
return values()[algorithmVersion];
}
public int toInt() {
return ordinal();
}
public String toBundleVersion() {
switch (this) {
case V0:
return "1.0";
case V1:
return "2.0";
case V2:
return "2.1";
case V3:
return "2.2";
}
throw new UnsupportedOperationException( "Unsupported version: " + this );
}
}
}

View File

@@ -0,0 +1,135 @@
package com.lyndir.masterpassword;
import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
import com.google.common.primitives.Bytes;
import com.lambdaworks.crypto.SCrypt;
import com.lyndir.lhunath.opal.system.*;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.nio.*;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import javax.annotation.Nullable;
/**
* bugs:
* - does math with chars whose signedness was platform-dependent.
* - miscounted the byte-length fromInt multi-byte site names.
* - miscounted the byte-length fromInt multi-byte full names.
*
* @author lhunath, 2014-08-30
*/
public class MasterKeyV0 extends MasterKey {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MasterKeyV0.class );
protected final int MP_N = 32768;
protected final int MP_r = 8;
protected final int MP_p = 2;
protected final int MP_dkLen = 64;
protected final int MP_intLen = 32;
protected final Charset MP_charset = Charsets.UTF_8;
protected final ByteOrder MP_byteOrder = ByteOrder.BIG_ENDIAN;
protected final MessageDigests MP_hash = MessageDigests.SHA256;
protected final MessageAuthenticationDigests MP_mac = MessageAuthenticationDigests.HmacSHA256;
public MasterKeyV0(final String fullName) {
super( fullName );
}
@Override
public Version getAlgorithmVersion() {
return Version.V0;
}
@Nullable
@Override
protected byte[] deriveKey(final char[] masterPassword) {
String fullName = getFullName();
byte[] fullNameBytes = fullName.getBytes( MP_charset );
byte[] fullNameLengthBytes = bytesForInt( fullName.length() );
String mpKeyScope = MPSiteVariant.Password.getScope();
byte[] masterKeySalt = Bytes.concat( mpKeyScope.getBytes( MP_charset ), fullNameLengthBytes, fullNameBytes );
logger.trc( "key scope: %s", mpKeyScope );
logger.trc( "masterKeySalt ID: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) );
CharBuffer mpChars = CharBuffer.wrap( masterPassword );
byte[] mpBytes = MP_charset.encode( mpChars ).array();
try {
return SCrypt.scrypt( mpBytes, masterKeySalt, MP_N, MP_r, MP_p, MP_dkLen );
}
catch (GeneralSecurityException e) {
logger.bug( e );
return null;
}
finally {
Arrays.fill( mpBytes, (byte) 0 );
}
}
public String encode(final String siteName, final MPSiteType siteType, int siteCounter, final MPSiteVariant siteVariant,
@Nullable final String siteContext) {
Preconditions.checkArgument( siteType.getTypeClass() == MPSiteTypeClass.Generated );
Preconditions.checkArgument( !siteName.isEmpty() );
logger.trc( "siteName: %s", siteName );
logger.trc( "siteCounter: %d", siteCounter );
logger.trc( "siteVariant: %d (%s)", siteVariant.ordinal(), siteVariant );
logger.trc( "siteType: %d (%s)", siteType.ordinal(), siteType );
if (siteCounter == 0)
siteCounter = (int) (System.currentTimeMillis() / (300 * 1000)) * 300;
String siteScope = siteVariant.getScope();
byte[] siteNameBytes = siteName.getBytes( MP_charset );
byte[] siteNameLengthBytes = bytesForInt( siteName.length() );
byte[] siteCounterBytes = bytesForInt( siteCounter );
byte[] siteContextBytes = siteContext == null? null: siteContext.getBytes( MP_charset );
byte[] siteContextLengthBytes = bytesForInt( siteContextBytes == null? 0: siteContextBytes.length );
logger.trc( "site scope: %s, context: %s", siteScope, siteContext == null? "<empty>": siteContext );
logger.trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)", siteScope, CodeUtils.encodeHex( siteNameLengthBytes ),
siteName, CodeUtils.encodeHex( siteCounterBytes ), CodeUtils.encodeHex( siteContextLengthBytes ),
siteContext == null? "(null)": siteContext );
byte[] sitePasswordInfo = Bytes.concat( siteScope.getBytes( MP_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
if (siteContextBytes != null)
sitePasswordInfo = Bytes.concat( sitePasswordInfo, siteContextLengthBytes, siteContextBytes );
logger.trc( "sitePasswordInfo ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) );
byte[] sitePasswordSeed = MP_mac.of( getKey(), sitePasswordInfo );
logger.trc( "sitePasswordSeed ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordSeed ) ) );
Preconditions.checkState( sitePasswordSeed.length > 0 );
int templateIndex = sitePasswordSeed[0] & 0xFFFF;
MPTemplate template = siteType.getTemplateAtRollingIndex( templateIndex );
logger.trc( "type %s, template: %s", siteType, template.getTemplateString() );
StringBuilder password = new StringBuilder( template.length() );
for (int i = 0; i < template.length(); ++i) {
int characterIndex = sitePasswordSeed[i + 1] & 0xFFFF;
MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i );
char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex );
logger.trc( "class %c, index %d (0x%02X) -> character: %c", characterClass.getIdentifier(), characterIndex,
sitePasswordSeed[i + 1], passwordCharacter );
password.append( passwordCharacter );
}
return password.toString();
}
@Override
protected byte[] bytesForInt(final int integer) {
return ByteBuffer.allocate( MP_intLen / Byte.SIZE ).order( MP_byteOrder ).putInt( integer ).array();
}
@Override
protected byte[] idForBytes(final byte[] bytes) {
return MP_hash.of( bytes );
}
}

View File

@@ -0,0 +1,82 @@
package com.lyndir.masterpassword;
import com.google.common.base.Preconditions;
import com.google.common.primitives.Bytes;
import com.lyndir.lhunath.opal.system.*;
import com.lyndir.lhunath.opal.system.logging.Logger;
import javax.annotation.Nullable;
/**
* bugs:
* - miscounted the byte-length fromInt multi-byte site names.
* - miscounted the byte-length fromInt multi-byte full names.
*
* @author lhunath, 2014-08-30
*/
public class MasterKeyV1 extends MasterKeyV0 {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MasterKeyV1.class );
public MasterKeyV1(final String fullName) {
super( fullName );
}
@Override
public Version getAlgorithmVersion() {
return Version.V1;
}
public String encode(final String siteName, final MPSiteType siteType, int siteCounter, final MPSiteVariant siteVariant,
@Nullable final String siteContext) {
Preconditions.checkArgument( siteType.getTypeClass() == MPSiteTypeClass.Generated );
Preconditions.checkArgument( !siteName.isEmpty() );
logger.trc( "siteName: %s", siteName );
logger.trc( "siteCounter: %d", siteCounter );
logger.trc( "siteVariant: %d (%s)", siteVariant.ordinal(), siteVariant );
logger.trc( "siteType: %d (%s)", siteType.ordinal(), siteType );
if (siteCounter == 0)
siteCounter = (int) (System.currentTimeMillis() / (300 * 1000)) * 300;
String siteScope = siteVariant.getScope();
byte[] siteNameBytes = siteName.getBytes( MP_charset );
byte[] siteNameLengthBytes = bytesForInt( siteName.length() );
byte[] siteCounterBytes = bytesForInt( siteCounter );
byte[] siteContextBytes = siteContext == null? null: siteContext.getBytes( MP_charset );
byte[] siteContextLengthBytes = bytesForInt( siteContextBytes == null? 0: siteContextBytes.length );
logger.trc( "site scope: %s, context: %s", siteScope, siteContext == null? "<empty>": siteContext );
logger.trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)", siteScope, CodeUtils.encodeHex( siteNameLengthBytes ),
siteName, CodeUtils.encodeHex( siteCounterBytes ), CodeUtils.encodeHex( siteContextLengthBytes ),
siteContext == null? "(null)": siteContext );
byte[] sitePasswordInfo = Bytes.concat( siteScope.getBytes( MP_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
if (siteContextBytes != null)
sitePasswordInfo = Bytes.concat( sitePasswordInfo, siteContextLengthBytes, siteContextBytes );
logger.trc( "sitePasswordInfo ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) );
byte[] sitePasswordSeed = MP_mac.of( getKey(), sitePasswordInfo );
logger.trc( "sitePasswordSeed ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordSeed ) ) );
Preconditions.checkState( sitePasswordSeed.length > 0 );
int templateIndex = sitePasswordSeed[0] & 0xFF; // Mask the integer's sign.
MPTemplate template = siteType.getTemplateAtRollingIndex( templateIndex );
logger.trc( "type %s, template: %s", siteType, template.getTemplateString() );
StringBuilder password = new StringBuilder( template.length() );
for (int i = 0; i < template.length(); ++i) {
int characterIndex = sitePasswordSeed[i + 1] & 0xFF; // Mask the integer's sign.
MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i );
char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex );
logger.trc( "class %c, index %d (0x%02X) -> character: %c", characterClass.getIdentifier(), characterIndex,
sitePasswordSeed[i + 1], passwordCharacter );
password.append( passwordCharacter );
}
return password.toString();
}
}

View File

@@ -0,0 +1,81 @@
package com.lyndir.masterpassword;
import com.google.common.base.Preconditions;
import com.google.common.primitives.Bytes;
import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.lhunath.opal.system.logging.Logger;
import javax.annotation.Nullable;
/**
* bugs:
* - miscounted the byte-length fromInt multi-byte full names.
*
* @author lhunath, 2014-08-30
*/
public class MasterKeyV2 extends MasterKeyV1 {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MasterKeyV2.class );
public MasterKeyV2(final String fullName) {
super( fullName );
}
@Override
public Version getAlgorithmVersion() {
return Version.V2;
}
public String encode(final String siteName, final MPSiteType siteType, int siteCounter, final MPSiteVariant siteVariant,
@Nullable final String siteContext) {
Preconditions.checkArgument( siteType.getTypeClass() == MPSiteTypeClass.Generated );
Preconditions.checkArgument( !siteName.isEmpty() );
logger.trc( "siteName: %s", siteName );
logger.trc( "siteCounter: %d", siteCounter );
logger.trc( "siteVariant: %d (%s)", siteVariant.ordinal(), siteVariant );
logger.trc( "siteType: %d (%s)", siteType.ordinal(), siteType );
if (siteCounter == 0)
siteCounter = (int) (System.currentTimeMillis() / (300 * 1000)) * 300;
String siteScope = siteVariant.getScope();
byte[] siteNameBytes = siteName.getBytes( MP_charset );
byte[] siteNameLengthBytes = bytesForInt( siteNameBytes.length );
byte[] siteCounterBytes = bytesForInt( siteCounter );
byte[] siteContextBytes = siteContext == null? null: siteContext.getBytes( MP_charset );
byte[] siteContextLengthBytes = bytesForInt( siteContextBytes == null? 0: siteContextBytes.length );
logger.trc( "site scope: %s, context: %s", siteScope, siteContext == null? "<empty>": siteContext );
logger.trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)", siteScope, CodeUtils.encodeHex( siteNameLengthBytes ),
siteName, CodeUtils.encodeHex( siteCounterBytes ), CodeUtils.encodeHex( siteContextLengthBytes ),
siteContext == null? "(null)": siteContext );
byte[] sitePasswordInfo = Bytes.concat( siteScope.getBytes( MP_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
if (siteContextBytes != null)
sitePasswordInfo = Bytes.concat( sitePasswordInfo, siteContextLengthBytes, siteContextBytes );
logger.trc( "sitePasswordInfo ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) );
byte[] sitePasswordSeed = MP_mac.of( getKey(), sitePasswordInfo );
logger.trc( "sitePasswordSeed ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordSeed ) ) );
Preconditions.checkState( sitePasswordSeed.length > 0 );
int templateIndex = sitePasswordSeed[0] & 0xFF; // Mask the integer's sign.
MPTemplate template = siteType.getTemplateAtRollingIndex( templateIndex );
logger.trc( "type %s, template: %s", siteType, template.getTemplateString() );
StringBuilder password = new StringBuilder( template.length() );
for (int i = 0; i < template.length(); ++i) {
int characterIndex = sitePasswordSeed[i + 1] & 0xFF; // Mask the integer's sign.
MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i );
char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex );
logger.trc( "class %c, index %d (0x%02X) -> character: %c", characterClass.getIdentifier(), characterIndex,
sitePasswordSeed[i + 1], passwordCharacter );
password.append( passwordCharacter );
}
return password.toString();
}
}

View File

@@ -0,0 +1,58 @@
package com.lyndir.masterpassword;
import com.google.common.primitives.Bytes;
import com.lambdaworks.crypto.SCrypt;
import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.nio.CharBuffer;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import javax.annotation.Nullable;
/**
* bugs:
* - no known issues.
*
* @author lhunath, 2014-08-30
*/
public class MasterKeyV3 extends MasterKeyV2 {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MasterKeyV3.class );
public MasterKeyV3(final String fullName) {
super( fullName );
}
@Override
public Version getAlgorithmVersion() {
return Version.V3;
}
@Nullable
@Override
protected byte[] deriveKey(final char[] masterPassword) {
byte[] fullNameBytes = getFullName().getBytes( MP_charset );
byte[] fullNameLengthBytes = bytesForInt( fullNameBytes.length );
String mpKeyScope = MPSiteVariant.Password.getScope();
byte[] masterKeySalt = Bytes.concat( mpKeyScope.getBytes( MP_charset ), fullNameLengthBytes, fullNameBytes );
logger.trc( "key scope: %s", mpKeyScope );
logger.trc( "masterKeySalt ID: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) );
CharBuffer mpChars = CharBuffer.wrap( masterPassword );
byte[] mpBytes = MP_charset.encode( mpChars ).array();
try {
return SCrypt.scrypt( mpBytes, masterKeySalt, MP_N, MP_r, MP_p, MP_dkLen );
}
catch (GeneralSecurityException e) {
logger.bug( e );
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
@Override
public String get() {
return parentCase.getMasterPassword();
return new String( parentCase.getMasterPassword() );
}
} );
keyID = ifNotNullElse( keyID, new NNSupplier<String>() {
@@ -148,8 +148,8 @@ public class MPWTests {
return fullName;
}
public String getMasterPassword() {
return masterPassword;
public char[] getMasterPassword() {
return masterPassword == null? null: masterPassword.toCharArray();
}
public String getKeyID() {
@@ -161,7 +161,7 @@ public class MPWTests {
}
public int getSiteCounter() {
return siteCounter;
return ifNotNullElse( siteCounter, 1 );
}
public MPSiteType getSiteType() {

View File

@@ -35,7 +35,7 @@ public class MasterKeyTest {
throws Exception {
for (MPWTests.Case testCase : tests.getCases()) {
MasterKey masterKey = new MasterKey( testCase.getFullName(), testCase.getMasterPassword() );
MasterKey masterKey = MasterKey.create( testCase.getFullName(), testCase.getMasterPassword() );
assertEquals(
masterKey.encode( testCase.getSiteName(), testCase.getSiteType(), testCase.getSiteCounter(), testCase.getSiteVariant(),
testCase.getSiteContext() ), testCase.getResult(), "Failed test case: " + testCase );
@@ -46,7 +46,7 @@ public class MasterKeyTest {
public void testGetUserName()
throws Exception {
assertEquals( new MasterKey( defaultCase.getFullName(), defaultCase.getMasterPassword() ).getFullName(),
assertEquals( MasterKey.create( defaultCase.getFullName(), defaultCase.getMasterPassword() ).getFullName(),
defaultCase.getFullName() );
}
@@ -55,7 +55,7 @@ public class MasterKeyTest {
throws Exception {
for (MPWTests.Case testCase : tests.getCases()) {
MasterKey masterKey = new MasterKey( testCase.getFullName(), testCase.getMasterPassword() );
MasterKey masterKey = MasterKey.create( testCase.getFullName(), testCase.getMasterPassword() );
assertEquals( CodeUtils.encodeHex( masterKey.getKeyID() ), testCase.getKeyID(), "Failed test case: " + testCase );
}
}
@@ -65,7 +65,7 @@ public class MasterKeyTest {
throws Exception {
try {
MasterKey masterKey = new MasterKey( defaultCase.getFullName(), defaultCase.getMasterPassword() );
MasterKey masterKey = MasterKey.create( defaultCase.getFullName(), defaultCase.getMasterPassword() );
masterKey.invalidate();
masterKey.encode( defaultCase.getSiteName(), defaultCase.getSiteType(), defaultCase.getSiteCounter(),
defaultCase.getSiteVariant(), defaultCase.getSiteContext() );

View File

@@ -2,17 +2,17 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.lyndir.masterpassword"
android:versionCode="1"
android:versionName="GIT-SNAPSHOT">
android:versionName="2.2">
<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="19" />
android:minSdkVersion="19"
android:targetSdkVersion="21" />
<application
android:icon="@drawable/icon"
android:label="@string/app_name"
android:allowBackup="true">
<activity android:name=".EmergencyActivity">
<activity android:name=".EmergencyActivity" android:theme="@style/MPTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />

View File

@@ -1,3 +0,0 @@
# File used by Eclipse to determine the target system
# Project target.
target=android-16

View File

@@ -7,13 +7,12 @@
<parent>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword</artifactId>
<version>GIT-SNAPSHOT</version>
<version>2.2</version>
</parent>
<name>Master Password Android</name>
<description>An Android application to the Master Password algorithm</description>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword-android</artifactId>
<packaging>apk</packaging>
@@ -30,7 +29,7 @@
<skip>false</skip>
</zipalign>
<sdk>
<platform>19</platform>
<platform>21</platform>
</sdk>
</configuration>
</plugin>
@@ -39,9 +38,32 @@
<profiles>
<profile>
<id>sign</id>
<id>release</id>
<build>
<plugins>
<plugin>
<groupId>com.jayway.maven.plugins.android.generation2</groupId>
<artifactId>android-maven-plugin</artifactId>
<configuration>
<sign>
<debug>false</debug>
</sign>
</configuration>
<executions>
<execution>
<id>manifest-update</id>
<phase>process-resources</phase>
<goals>
<goal>manifest-update</goal>
</goals>
<configuration>
<manifestVersionCodeUpdateFromVersion>true</manifestVersionCodeUpdateFromVersion>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jarsigner-plugin</artifactId>
@@ -54,14 +76,14 @@
<phase>package</phase>
<inherited>true</inherited>
<configuration>
<archiveDirectory></archiveDirectory>
<archiveDirectory />
<includes>
<include>target/*.apk</include>
</includes>
<keystore>release.jks</keystore>
<storepass>${env.PASSWORD}</storepass>
<keypass>${env.PASSWORD}</keypass>
<alias>android</alias>
<alias>masterpassword-android</alias>
<arguments>
<argument>-sigalg</argument><argument>MD5withRSA</argument>
<argument>-digestalg</argument><argument>SHA1</argument>
@@ -70,16 +92,6 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.jayway.maven.plugins.android.generation2</groupId>
<artifactId>android-maven-plugin</artifactId>
<inherited>true</inherited>
<configuration>
<sign>
<debug>false</debug>
</sign>
</configuration>
</plugin>
</plugins>
</build>
</profile>
@@ -92,7 +104,7 @@
<dependency>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword-algorithm</artifactId>
<version>GIT-SNAPSHOT</version>
<version>2.2</version>
</dependency>
<dependency>

View File

@@ -0,0 +1 @@
/Users/lhunath/SpiderOak Hive/secret/release-com.lyndir.masterpassword.jks

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 KiB

After

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<size
android:width="20dp"
android:height="20dp" />
</shape>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

View File

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

View File

@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/background">
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:fillViewport="true">
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<View
android:layout_width="match_parent"
android:layout_height="0dp" />
<LinearLayout
android:id="@+id/users"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:divider="@drawable/divider20"
android:showDividers="middle" />
<View
android:layout_width="match_parent"
android:layout_height="0dp" />
</LinearLayout>
</HorizontalScrollView>
</FrameLayout>

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/userName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableTop="@drawable/avatar0"
android:drawablePadding="8dp"
android:text="Maarten Billemont" />

View File

@@ -1,10 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Master Password</string>
<string name="avatar">User Avatar</string>
<string name="remember">Remember Password</string>
<string name="siteName_hint">Site Name</string>
<string name="userName_hint">Your Name</string>
<string name="masterPassword_hint">Your Master Password</string>
<string name="remember">Remember</string>
<string name="forgetOnClose">Forget on close</string>
<string name="maskPassword">Hide 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" />
</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.content.*;
import android.content.ClipboardManager;
import android.graphics.Paint;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.text.*;
import android.text.method.PasswordTransformationMethod;
import android.view.View;
import android.view.WindowManager;
import android.widget.*;
@@ -17,7 +18,9 @@ import com.google.common.base.Throwables;
import com.google.common.util.concurrent.*;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
import java.util.Arrays;
import java.util.concurrent.*;
import javax.annotation.Nullable;
public class EmergencyActivity extends Activity {
@@ -44,8 +47,8 @@ public class EmergencyActivity extends Activity {
@InjectView(R.id.progressView)
ProgressBar progressView;
@InjectView(R.id.userNameField)
EditText userNameField;
@InjectView(R.id.fullNameField)
EditText fullNameField;
@InjectView(R.id.masterPasswordField)
EditText masterPasswordField;
@@ -53,23 +56,36 @@ public class EmergencyActivity extends Activity {
@InjectView(R.id.siteNameField)
EditText siteNameField;
@InjectView(R.id.typeField)
Spinner typeField;
@InjectView(R.id.siteTypeField)
Spinner siteTypeField;
@InjectView(R.id.counterField)
EditText counterField;
@InjectView(R.id.siteVersionField)
Spinner siteVersionField;
@InjectView(R.id.sitePasswordField)
TextView sitePasswordField;
@InjectView(R.id.rememberPasswordField)
CheckBox rememberPasswordField;
@InjectView(R.id.sitePasswordTip)
TextView sitePasswordTip;
private int hc_userName;
private int hc_masterPassword;
@InjectView(R.id.rememberFullNameField)
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
public void onCreate(Bundle savedInstanceState) {
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate( savedInstanceState );
Res.init( getResources() );
@@ -77,14 +93,25 @@ public class EmergencyActivity extends Activity {
setContentView( R.layout.activity_emergency );
ButterKnife.inject( this );
userNameField.setOnFocusChangeListener( updateMasterKey );
fullNameField.setOnFocusChangeListener( updateMasterKey );
masterPasswordField.setOnFocusChangeListener( updateMasterKey );
siteNameField.addTextChangedListener( updateSitePassword );
typeField.setOnItemSelectedListener( updateSitePassword );
siteTypeField.setOnItemSelectedListener( 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 );
userNameField.setPaintFlags( userNameField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
if (noPassword)
sitePassword = null;
}
} );
fullNameField.setTypeface( Res.exo_Thin );
fullNameField.setPaintFlags( fullNameField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
masterPasswordField.setTypeface( Res.sourceCodePro_ExtraLight );
masterPasswordField.setPaintFlags( masterPasswordField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
siteNameField.setTypeface( Res.exo_Regular );
@@ -92,13 +119,33 @@ public class EmergencyActivity extends Activity {
sitePasswordField.setTypeface( Res.sourceCodePro_Black );
sitePasswordField.setPaintFlags( sitePasswordField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
typeField.setAdapter( new ArrayAdapter<>( this, R.layout.type_item, MPSiteType.forClass( MPSiteTypeClass.Generated ) ) );
typeField.setSelection( MPSiteType.GeneratedLong.ordinal() );
siteTypeField.setAdapter( new ArrayAdapter<>( this, R.layout.spinner_item, MPSiteType.forClass( MPSiteTypeClass.Generated ) ) );
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
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() {
super.onResume();
userNameField.setText( getPreferences( MODE_PRIVATE ).getString( "userName", "" ) );
rememberPasswordField.setSelected( isRememberPasswordEnabled() );
masterPasswordField.requestFocus();
fullNameField.setText( getPreferences( MODE_PRIVATE ).getString( "fullName", "" ) );
rememberFullNameField.setChecked( isRememberFullNameEnabled() );
forgetPasswordField.setChecked( isForgetPasswordEnabled() );
maskPasswordField.setChecked( isMaskPasswordEnabled() );
sitePasswordField.setTransformationMethod( isMaskPasswordEnabled()? new PasswordTransformationMethod(): null );
if (TextUtils.isEmpty( masterPasswordField.getText() ))
masterPasswordField.requestFocus();
else
siteNameField.requestFocus();
}
@Override
protected void onPause() {
if (!isRememberPasswordEnabled()) {
if (isForgetPasswordEnabled()) {
synchronized (this) {
hc_userName = hc_masterPassword = 0;
if (masterKeyFuture != null) {
@@ -122,44 +176,64 @@ public class EmergencyActivity extends Activity {
masterKeyFuture = null;
}
sitePasswordField.setText( "" );
progressView.setVisibility( View.INVISIBLE );
masterPasswordField.setText( "" );
}
}
siteNameField.setText( "" );
sitePasswordField.setText( "" );
progressView.setVisibility( View.INVISIBLE );
super.onPause();
}
private boolean isRememberPasswordEnabled() {
return getPreferences( MODE_PRIVATE ).getBoolean( "rememberPassword", false );
private boolean isRememberFullNameEnabled() {
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() {
final String userName = userNameField.getText().toString();
final String masterPassword = masterPasswordField.getText().toString();
if (userName.hashCode() == hc_userName && masterPassword.hashCode() == hc_masterPassword)
final String fullName = fullNameField.getText().toString();
final char[] masterPassword = masterPasswordField.getText().toString().toCharArray();
final MasterKey.Version version = (MasterKey.Version) siteVersionField.getSelectedItem();
try {
if (fullName.hashCode() == hc_userName && Arrays.hashCode( masterPassword ) == hc_masterPassword &&
masterKeyFuture != null && masterKeyFuture.get().getAlgorithmVersion() == version)
return;
}
catch (InterruptedException | ExecutionException e) {
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)
masterKeyFuture.cancel( true );
if (userName.isEmpty() || masterPassword.isEmpty()) {
if (fullName.isEmpty() || masterPassword.length == 0) {
sitePasswordField.setText( "" );
progressView.setVisibility( View.INVISIBLE );
return;
}
sitePasswordField.setText( "" );
progressView.setVisibility( View.VISIBLE );
(masterKeyFuture = executor.submit( new Callable<MasterKey>() {
@Override
public MasterKey call()
throws Exception {
try {
return new MasterKey( userName, masterPassword );
return MasterKey.create( version, fullName, masterPassword );
}
catch (RuntimeException e) {
sitePasswordField.setText( "" );
@@ -183,21 +257,25 @@ public class EmergencyActivity extends Activity {
private void updateSitePassword() {
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() );
if (masterKeyFuture == null || siteName.isEmpty() || type == null) {
sitePasswordField.setText( "" );
progressView.setVisibility( View.INVISIBLE );
if (masterKeyFuture == null)
updateMasterKey();
return;
}
sitePasswordField.setText( "" );
progressView.setVisibility( View.VISIBLE );
executor.submit( new Runnable() {
@Override
public void run() {
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() {
@Override
@@ -228,8 +306,7 @@ public class EmergencyActivity extends Activity {
}
public void copySitePassword(View view) {
String sitePassword = sitePasswordField.getText().toString();
if (sitePassword.isEmpty())
if (TextUtils.isEmpty( sitePassword ))
return;
ClipDescription description = new ClipDescription( strf( "Password for %s", siteNameField.getText() ),

View File

@@ -1,7 +1,6 @@
package com.lyndir.masterpassword;
import android.content.res.Resources;
import android.graphics.Paint;
import android.graphics.Typeface;

View File

@@ -1,34 +0,0 @@
package com.lyndir.masterpassword;
import android.app.Activity;
import android.os.Bundle;
import android.widget.LinearLayout;
import butterknife.ButterKnife;
import butterknife.InjectView;
import com.lyndir.masterpassword.model.Avatar;
import com.lyndir.masterpassword.model.User;
import com.lyndir.masterpassword.view.AvatarView;
public class UsersActivity extends Activity {
@InjectView(R.id.users)
LinearLayout users;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate( savedInstanceState );
setContentView( R.layout.activity_users );
ButterKnife.inject( this );
}
@Override
protected void onResume() {
super.onResume();
AvatarView avatarView = new AvatarView( this );
avatarView.setUser( new User( "Maarten Billemont", Avatar.EIGHT ) );
users.addView( avatarView );
}
}

View File

@@ -1,39 +0,0 @@
package com.lyndir.masterpassword.model;
import com.lyndir.masterpassword.R;
/**
* @author lhunath, 2014-08-20
*/
public enum Avatar {
ZERO( R.drawable.avatar0 ),
ONE( R.drawable.avatar1 ),
TWO( R.drawable.avatar2 ),
THREE( R.drawable.avatar3 ),
FOUR( R.drawable.avatar4 ),
FIVE( R.drawable.avatar5 ),
SIX( R.drawable.avatar6 ),
SEVEN( R.drawable.avatar7 ),
EIGHT( R.drawable.avatar8 ),
NINE( R.drawable.avatar9 ),
TEN( R.drawable.avatar10 ),
ELEVEN( R.drawable.avatar11 ),
TWELVE( R.drawable.avatar12 ),
THIRTEEN( R.drawable.avatar13 ),
FOURTEEN( R.drawable.avatar14 ),
FIFTEEN( R.drawable.avatar15 ),
SIXTEEN( R.drawable.avatar16 ),
SEVENTEEN( R.drawable.avatar17 ),
EIGHTEEN( R.drawable.avatar18 );
private final int imageResource;
Avatar(final int imageResource) {
this.imageResource = imageResource;
}
public int getImageResource() {
return imageResource;
}
}

View File

@@ -1,43 +0,0 @@
package com.lyndir.masterpassword.model;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import java.util.Objects;
/**
* @author lhunath, 2014-08-20
*/
public class User {
private String name;
private Avatar avatar;
public User(final String name, final Avatar avatar) {
this.name = name;
this.avatar = avatar;
}
public String getName() {
return name;
}
public Avatar getAvatar() {
return avatar;
}
@Override
public boolean equals(final Object obj) {
return this == obj || obj instanceof User && name.equals( ((User) obj).name );
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public String toString() {
return strf( "{User: %s}", name );
}
}

View File

@@ -1,28 +0,0 @@
package com.lyndir.masterpassword.view;
import android.content.Context;
import android.view.LayoutInflater;
import android.widget.FrameLayout;
import android.widget.TextView;
import com.lyndir.masterpassword.R;
import com.lyndir.masterpassword.model.User;
/**
* @author lhunath, 2014-08-20
*/
public class AvatarView extends FrameLayout {
private final TextView userName;
public AvatarView(final Context context) {
super( context );
addView( userName = (TextView) LayoutInflater.from( context ).inflate( R.layout.view_user_avatar, this, false ) );
}
public void setUser(User user) {
userName.setText( user.getName() );
userName.setCompoundDrawables( null, getResources().getDrawable( user.getAvatar().getImageResource() ), null, null );
}
}

View File

@@ -7,13 +7,12 @@
<parent>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword</artifactId>
<version>GIT-SNAPSHOT</version>
<version>2.2</version>
</parent>
<name>Master Password CLI</name>
<description>A CLI interface to the Master Password algorithm</description>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword-cli</artifactId>
<packaging>jar</packaging>
@@ -37,7 +36,7 @@
<phase>prepare-package</phase>
<configuration>
<target>
<chmod file="${project.build.directory}/install" perm="755"/>
<chmod file="${project.build.directory}/install" perm="755" />
</target>
</configuration>
<goals>
@@ -86,7 +85,7 @@
<dependency>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword-algorithm</artifactId>
<version>GIT-SNAPSHOT</version>
<version>2.2</version>
</dependency>
<dependency>

View File

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

View File

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

View File

@@ -7,13 +7,12 @@
<parent>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword</artifactId>
<version>GIT-SNAPSHOT</version>
<version>2.2</version>
</parent>
<name>Master Password GUI</name>
<description>A GUI interface to the Master Password algorithm</description>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword-gui</artifactId>
<packaging>jar</packaging>
@@ -67,6 +66,67 @@
</plugins>
</build>
<profiles>
<profile>
<id>release</id>
<build>
<plugins>
<plugin>
<groupId>com.jayway.maven.plugins.android.generation2</groupId>
<artifactId>android-maven-plugin</artifactId>
<configuration>
<sign>
<debug>false</debug>
</sign>
</configuration>
<executions>
<execution>
<id>manifest-update</id>
<phase>process-resources</phase>
<goals>
<goal>manifest-update</goal>
</goals>
<configuration>
<manifestVersionCodeUpdateFromVersion>true</manifestVersionCodeUpdateFromVersion>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jarsigner-plugin</artifactId>
<executions>
<execution>
<id>signing</id>
<goals>
<goal>sign</goal>
</goals>
<phase>package</phase>
<inherited>true</inherited>
<configuration>
<archiveDirectory />
<includes>
<include>target/*.jar</include>
</includes>
<keystore>release.jks</keystore>
<storepass>${env.PASSWORD}</storepass>
<keypass>${env.PASSWORD}</keypass>
<alias>masterpassword-desktop</alias>
<arguments>
<argument>-sigalg</argument><argument>MD5withRSA</argument>
<argument>-digestalg</argument><argument>SHA1</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<!-- DEPENDENCY MANAGEMENT -->
<dependencies>
@@ -74,7 +134,7 @@
<dependency>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword-model</artifactId>
<version>GIT-SNAPSHOT</version>
<version>2.2</version>
</dependency>
<!-- EXTERNAL DEPENDENCIES -->

View File

@@ -0,0 +1 @@
/Users/lhunath/SpiderOak Hive/secret/release-com.lyndir.masterpassword.jks

View File

@@ -1,6 +1,7 @@
package com.lyndir.masterpassword.gui;
import com.google.common.collect.ImmutableList;
import com.lyndir.masterpassword.gui.util.Components;
import java.awt.*;
import javax.swing.*;
@@ -8,12 +9,13 @@ import javax.swing.*;
/**
* @author lhunath, 2014-06-11
*/
public abstract class AuthenticationPanel extends JPanel {
public abstract class AuthenticationPanel extends Components.GradientPanel {
protected final UnlockFrame unlockFrame;
protected final JLabel avatarLabel;
public AuthenticationPanel(final UnlockFrame unlockFrame) {
super( null, null );
this.unlockFrame = unlockFrame;
setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) );
@@ -30,7 +32,7 @@ public abstract class AuthenticationPanel extends JPanel {
}
protected void updateUser(boolean repack) {
unlockFrame.setUser( getSelectedUser() );
unlockFrame.updateUser( getSelectedUser() );
validate();
if (repack)
@@ -39,6 +41,8 @@ public abstract class AuthenticationPanel extends JPanel {
protected abstract User getSelectedUser();
public abstract char[] getMasterPassword();
public Component getFocusComponent() {
return null;
}

View File

@@ -39,7 +39,7 @@ public class GUI implements UnlockFrame.SignInCallback {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( GUI.class );
private UnlockFrame unlockFrame = new UnlockFrame( this );
private final UnlockFrame unlockFrame = new UnlockFrame( this );
private PasswordFrame passwordFrame;
public static void main(final String[] args)
@@ -48,14 +48,7 @@ public class GUI implements UnlockFrame.SignInCallback {
if (Config.get().checkForUpdates())
checkUpdate();
GUI gui;
try {
gui = TypeUtils.newInstance( AppleGUI.class );
}
catch (NoClassDefFoundError e) {
gui = new GUI();
}
gui.open();
TypeUtils.<GUI>newInstance( AppleGUI.class ).or( new GUI() ).open();
}
private static void checkUpdate() {
@@ -100,19 +93,9 @@ public class GUI implements UnlockFrame.SignInCallback {
}
@Override
public boolean signedIn(final User user) {
if (!user.hasKey())
return false;
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;
}
public void signedIn(final User user) {
passwordFrame = newPasswordFrame( user );
open();
}
protected PasswordFrame newPasswordFrame(final User user) {

View File

@@ -1,5 +1,6 @@
package com.lyndir.masterpassword.gui;
import com.lyndir.masterpassword.gui.util.Components;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
@@ -20,40 +21,23 @@ public class IncognitoAuthenticationPanel extends AuthenticationPanel implements
// Full Name
super( unlockFrame );
JLabel fullNameLabel = new JLabel( "Full Name:" );
fullNameLabel.setFont( Res.exoRegular().deriveFont( 12f ) );
fullNameLabel.setAlignmentX( LEFT_ALIGNMENT );
fullNameLabel.setHorizontalAlignment( SwingConstants.CENTER );
fullNameLabel.setVerticalAlignment( SwingConstants.BOTTOM );
add( Components.stud() );
JLabel fullNameLabel = Components.label( "Full Name:" );
add( fullNameLabel );
fullNameField = new JTextField() {
@Override
public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
}
};
fullNameField.setFont( Res.sourceCodeProRegular().deriveFont( 12f ) );
fullNameField.setAlignmentX( LEFT_ALIGNMENT );
fullNameField = Components.textField();
fullNameField.setFont( Res.valueFont().deriveFont( 12f ) );
fullNameField.getDocument().addDocumentListener( this );
fullNameField.addActionListener( this );
add( fullNameField );
add( Components.stud() );
// Master Password
JLabel masterPasswordLabel = new JLabel( "Master Password:" );
masterPasswordLabel.setFont( Res.exoRegular().deriveFont( 12f ) );
masterPasswordLabel.setAlignmentX( LEFT_ALIGNMENT );
masterPasswordLabel.setHorizontalAlignment( SwingConstants.CENTER );
masterPasswordLabel.setVerticalAlignment( SwingConstants.BOTTOM );
JLabel masterPasswordLabel = Components.label( "Master Password:" );
add( masterPasswordLabel );
masterPasswordField = new JPasswordField() {
@Override
public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
}
};
masterPasswordField.setAlignmentX( LEFT_ALIGNMENT );
masterPasswordField = Components.passwordField();
masterPasswordField.addActionListener( this );
masterPasswordField.getDocument().addDocumentListener( this );
add( masterPasswordField );
@@ -71,7 +55,12 @@ public class IncognitoAuthenticationPanel extends AuthenticationPanel implements
@Override
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

View File

@@ -1,6 +1,7 @@
package com.lyndir.masterpassword.gui;
import com.lyndir.masterpassword.MPSiteType;
import com.lyndir.masterpassword.MasterKey;
/**
@@ -8,14 +9,17 @@ import com.lyndir.masterpassword.MPSiteType;
*/
public class IncognitoSite extends Site {
private String siteName;
private MPSiteType siteType;
private int siteCounter;
private String siteName;
private MPSiteType siteType;
private int siteCounter;
private MasterKey.Version algorithmVersion;
public IncognitoSite(final String siteName, final MPSiteType siteType, final int siteCounter) {
public IncognitoSite(final String siteName, final MPSiteType siteType, final int siteCounter,
final MasterKey.Version algorithmVersion) {
this.siteName = siteName;
this.siteType = siteType;
this.siteCounter = siteCounter;
this.algorithmVersion = algorithmVersion;
}
public String getSiteName() {
@@ -34,6 +38,16 @@ public class IncognitoSite extends Site {
this.siteType = siteType;
}
@Override
public MasterKey.Version getAlgorithmVersion() {
return algorithmVersion;
}
@Override
public void setAlgorithmVersion(final MasterKey.Version algorithmVersion) {
this.algorithmVersion = algorithmVersion;
}
public int getSiteCounter() {
return siteCounter;
}

View File

@@ -1,6 +1,8 @@
package com.lyndir.masterpassword.gui;
import com.google.common.collect.ImmutableList;
import com.lyndir.masterpassword.model.IncorrectMasterPasswordException;
import javax.annotation.Nullable;
/**
@@ -9,22 +11,28 @@ import com.google.common.collect.ImmutableList;
public class IncognitoUser extends User {
private final String fullName;
private final String masterPassword;
private char[] masterPassword;
public IncognitoUser(final String fullName, final String masterPassword) {
public IncognitoUser(final String fullName) {
this.fullName = fullName;
this.masterPassword = masterPassword;
}
public String getFullName() {
return fullName;
}
@Nullable
@Override
protected String getMasterPassword() {
protected char[] getMasterPassword() {
return masterPassword;
}
@Override
public void authenticate(final char[] masterPassword)
throws IncorrectMasterPasswordException {
this.masterPassword = masterPassword;
}
@Override
public Iterable<Site> findSitesByName(final String siteName) {
return ImmutableList.of();

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

@@ -1,10 +1,12 @@
package com.lyndir.masterpassword.gui;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.collect.*;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.masterpassword.model.MPUser;
import com.lyndir.masterpassword.model.MPUserFileManager;
import com.lyndir.masterpassword.gui.util.Components;
import java.awt.*;
import java.awt.event.*;
import javax.annotation.Nullable;
@@ -27,6 +29,7 @@ public class ModelAuthenticationPanel extends AuthenticationPanel implements Ite
public ModelAuthenticationPanel(final UnlockFrame unlockFrame) {
super( unlockFrame );
add( Components.stud() );
// Avatar
avatarLabel.addMouseListener( new MouseAdapter() {
@@ -41,40 +44,21 @@ public class ModelAuthenticationPanel extends AuthenticationPanel implements Ite
} );
// User
JLabel userLabel = new JLabel( "User:" );
userLabel.setFont( Res.exoRegular().deriveFont( 12f ) );
userLabel.setAlignmentX( LEFT_ALIGNMENT );
userLabel.setHorizontalAlignment( SwingConstants.CENTER );
userLabel.setVerticalAlignment( SwingConstants.BOTTOM );
JLabel userLabel = Components.label( "User:" );
add( userLabel );
userField = new JComboBox<ModelUser>( new DefaultComboBoxModel<>( readConfigUsers() ) ) {
@Override
public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
}
};
userField.setFont( Res.sourceCodeProRegular().deriveFont( 12f ) );
userField.setAlignmentX( LEFT_ALIGNMENT );
userField = Components.comboBox( readConfigUsers() );
userField.setFont( Res.valueFont().deriveFont( 12f ) );
userField.addItemListener( this );
userField.addActionListener( this );
add( userField );
add( Components.stud() );
// Master Password
masterPasswordLabel = new JLabel( "Master Password:" );
masterPasswordLabel.setFont( Res.exoRegular().deriveFont( 12f ) );
masterPasswordLabel.setAlignmentX( LEFT_ALIGNMENT );
masterPasswordLabel.setHorizontalAlignment( SwingConstants.CENTER );
masterPasswordLabel.setVerticalAlignment( SwingConstants.BOTTOM );
masterPasswordLabel = Components.label( "Master Password:" );
add( masterPasswordLabel );
masterPasswordField = new JPasswordField() {
@Override
public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
}
};
masterPasswordField.setAlignmentX( LEFT_ALIGNMENT );
masterPasswordField = Components.passwordField();
masterPasswordField.addActionListener( this );
masterPasswordField.getDocument().addDocumentListener( this );
add( masterPasswordField );
@@ -107,11 +91,12 @@ public class ModelAuthenticationPanel extends AuthenticationPanel implements Ite
if (selectedIndex < 0)
return null;
ModelUser selectedUser = userField.getModel().getElementAt( selectedIndex );
if (selectedUser != null)
selectedUser.setMasterPassword( new String( masterPasswordField.getPassword() ) );
return userField.getModel().getElementAt( selectedIndex );
}
return selectedUser;
@Override
public char[] getMasterPassword() {
return masterPasswordField.getPassword();
}
@Override
@@ -153,8 +138,8 @@ public class ModelAuthenticationPanel extends AuthenticationPanel implements Ite
return FluentIterable.from( MPUserFileManager.get().getUsers() ).transform( new Function<MPUser, ModelUser>() {
@Nullable
@Override
public ModelUser apply(final MPUser model) {
return new ModelUser( model );
public ModelUser apply(@Nullable final MPUser model) {
return new ModelUser( Preconditions.checkNotNull( model ) );
}
} ).toArray( ModelUser.class );
}

View File

@@ -1,6 +1,7 @@
package com.lyndir.masterpassword.gui;
import com.lyndir.masterpassword.MPSiteType;
import com.lyndir.masterpassword.MasterKey;
import com.lyndir.masterpassword.model.*;
@@ -37,6 +38,19 @@ public class ModelSite extends Site {
}
}
@Override
public MasterKey.Version getAlgorithmVersion() {
return model.getAlgorithmVersion();
}
@Override
public void setAlgorithmVersion(final MasterKey.Version algorithmVersion) {
if (algorithmVersion != getAlgorithmVersion()) {
model.setAlgorithmVersion( algorithmVersion );
MPUserFileManager.get().save();
}
}
public int getSiteCounter() {
return model.getSiteCounter();
}

View File

@@ -1,14 +1,10 @@
package com.lyndir.masterpassword.gui;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import com.google.common.base.Function;
import com.google.common.base.*;
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 javax.annotation.Nullable;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import javax.annotation.*;
/**
@@ -17,7 +13,9 @@ import org.jetbrains.annotations.NotNull;
public class ModelUser extends User {
private final MPUser model;
private String masterPassword;
@Nullable
private char[] masterPassword;
public ModelUser(MPUser model) {
this.model = model;
@@ -32,8 +30,9 @@ public class ModelUser extends User {
return model.getFullName();
}
@Nullable
@Override
protected String getMasterPassword() {
protected char[] getMasterPassword() {
return masterPassword;
}
@@ -47,30 +46,20 @@ public class ModelUser extends User {
MPUserFileManager.get().save();
}
public void setMasterPassword(final String masterPassword) {
public void authenticate(final char[] masterPassword)
throws IncorrectMasterPasswordException {
putKey( model.authenticate( 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
public void reset() {
super.reset();
masterPassword = null;
if (masterPassword != null) {
Arrays.fill( masterPassword, (char) 0 );
masterPassword = null;
}
}
@Override
@@ -78,8 +67,8 @@ public class ModelUser extends User {
return FluentIterable.from( model.findSitesByName( query ) ).transform( new Function<MPSiteResult, Site>() {
@Nullable
@Override
public Site apply(final MPSiteResult result) {
return new ModelSite( result );
public Site apply(@Nullable final MPSiteResult result) {
return new ModelSite( Preconditions.checkNotNull( result ) );
}
} );
}
@@ -92,6 +81,7 @@ public class ModelUser extends User {
}
public boolean keySaved() {
// TODO
return false;
}
}

View File

@@ -5,14 +5,14 @@ import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.*;
import com.lyndir.masterpassword.*;
import com.lyndir.masterpassword.util.Components;
import com.lyndir.masterpassword.gui.util.Components;
import java.awt.*;
import java.awt.datatransfer.StringSelection;
import java.awt.event.*;
import java.util.concurrent.Callable;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
@@ -21,83 +21,53 @@ import javax.swing.event.*;
*/
public class PasswordFrame extends JFrame implements DocumentListener {
private final User user;
private final JTextField siteNameField;
private final JButton siteAddButton;
private final JComboBox<MPSiteType> siteTypeField;
private final JSpinner siteCounterField;
private final JPasswordField passwordField;
private final JLabel tipLabel;
private final JCheckBox maskPasswordField;
private final char passwordEchoChar;
private final Font passwordEchoFont;
private boolean updatingUI;
private Site currentSite;
private final User user;
private final Components.GradientPanel root;
private final JTextField siteNameField;
private final JButton siteAddButton;
private final JComboBox<MPSiteType> siteTypeField;
private final JComboBox<MasterKey.Version> siteVersionField;
private final JSpinner siteCounterField;
private final JPasswordField passwordField;
private final JLabel tipLabel;
private final JCheckBox maskPasswordField;
private final char passwordEchoChar;
private final Font passwordEchoFont;
private boolean updatingUI;
private Site currentSite;
public PasswordFrame(User user)
throws HeadlessException {
super( "Master Password" );
this.user = user;
JLabel label;
setDefaultCloseOperation( DISPOSE_ON_CLOSE );
setContentPane( new JPanel( new BorderLayout( 20, 20 ) ) {
{
setBorder( new EmptyBorder( 20, 20, 20, 20 ) );
}
} );
// User
add( label = new JLabel( strf( "Generating passwords for: %s", user.getFullName() ) ), BorderLayout.NORTH );
label.setFont( Res.exoRegular().deriveFont( 12f ) );
label.setAlignmentX( LEFT_ALIGNMENT );
setContentPane( root = Components.gradientPanel( new BorderLayout( 20, 20 ), Res.colors().frameBg() ) );
root.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
// 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 );
JPanel sitePanel = Components.boxLayout( BoxLayout.PAGE_AXIS );
sitePanel.setOpaque( true );
sitePanel.setBackground( Res.colors().controlBg() );
sitePanel.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
add( Components.borderPanel( sitePanel, BorderFactory.createRaisedBevelBorder(), Res.colors().frameBg() ), BorderLayout.CENTER );
// User
sitePanel.add( Components.label( strf( "Generating passwords for: %s", user.getFullName() ), JLabel.CENTER ) );
sitePanel.add( Components.stud() );
// Site Name
sitePanel.add( label = new JLabel( "Site Name:", JLabel.LEADING ) );
label.setFont( Res.exoRegular().deriveFont( 12f ) );
label.setAlignmentX( LEFT_ALIGNMENT );
sitePanel.add( Components.label( "Site Name:" ) );
JComponent siteControls = Components.boxLayout( BoxLayout.LINE_AXIS, //
siteNameField = new JTextField() {
@Override
public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
}
}, siteAddButton = new JButton( "Add Site" ) {
@Override
public Dimension getMaximumSize() {
return new Dimension( 20, getPreferredSize().height );
}
} );
siteAddButton.setVisible( false );
siteAddButton.setFont( Res.exoRegular().deriveFont( 12f ) );
siteAddButton.setAlignmentX( RIGHT_ALIGNMENT );
siteAddButton.setAlignmentY( CENTER_ALIGNMENT );
siteAddButton.addActionListener( new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
PasswordFrame.this.user.addSite( currentSite );
siteAddButton.setVisible( false );
}
} );
siteControls.setAlignmentX( LEFT_ALIGNMENT );
sitePanel.add( siteControls );
siteNameField.setFont( Res.sourceCodeProRegular().deriveFont( 12f ) );
siteNameField.setAlignmentX( LEFT_ALIGNMENT );
siteNameField = Components.textField(), Components.stud(),
siteAddButton = Components.button( "Add Site" ) );
siteNameField.getDocument().addDocumentListener( this );
siteNameField.addActionListener( new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
Futures.addCallback( updatePassword(), new FutureCallback<String>() {
@Override
public void onSuccess(final String sitePassword) {
public void onSuccess(@Nullable final String sitePassword) {
StringSelection clipboardContents = new StringSelection( sitePassword );
Toolkit.getDefaultToolkit().getSystemClipboard().setContents( clipboardContents, null );
@@ -118,23 +88,28 @@ public class PasswordFrame extends JFrame implements DocumentListener {
} );
}
} );
siteAddButton.setVisible( false );
siteAddButton.addActionListener( new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
PasswordFrame.this.user.addSite( currentSite );
siteAddButton.setVisible( false );
}
} );
sitePanel.add( siteControls );
sitePanel.add( Components.stud() );
// Site Type & Counter
MPSiteType[] types = Iterables.toArray( MPSiteType.forClass( MPSiteTypeClass.Generated ), MPSiteType.class );
JComponent siteSettings = Components.boxLayout( BoxLayout.LINE_AXIS, //
siteTypeField = new JComboBox<>( types ), //
siteCounterField = new JSpinner(
new SpinnerNumberModel( 1, 1, Integer.MAX_VALUE, 1 ) ) {
@Override
public Dimension getMaximumSize() {
return new Dimension( 20, getPreferredSize().height );
}
} );
siteSettings.setAlignmentX( LEFT_ALIGNMENT );
siteTypeField = Components.comboBox( types ), //
Components.stud(), //
siteVersionField = Components.comboBox( MasterKey.Version.values() ), //
Components.stud(), //
siteCounterField = Components.spinner(
new SpinnerNumberModel( 1, 1, Integer.MAX_VALUE, 1 ) ) );
sitePanel.add( siteSettings );
siteTypeField.setFont( Res.sourceCodeProRegular().deriveFont( 12f ) );
siteTypeField.setAlignmentX( LEFT_ALIGNMENT );
siteTypeField.setAlignmentY( CENTER_ALIGNMENT );
siteTypeField.setFont( Res.valueFont().deriveFont( 12f ) );
siteTypeField.setSelectedItem( MPSiteType.GeneratedLong );
siteTypeField.addItemListener( new ItemListener() {
@Override
@@ -143,9 +118,18 @@ public class PasswordFrame extends JFrame implements DocumentListener {
}
} );
siteCounterField.setFont( Res.sourceCodeProRegular().deriveFont( 12f ) );
siteVersionField.setFont( Res.valueFont().deriveFont( 12f ) );
siteVersionField.setAlignmentX( RIGHT_ALIGNMENT );
siteVersionField.setSelectedItem( MasterKey.Version.CURRENT );
siteVersionField.addItemListener( new ItemListener() {
@Override
public void itemStateChanged(final ItemEvent e) {
updatePassword();
}
} );
siteCounterField.setFont( Res.valueFont().deriveFont( 12f ) );
siteCounterField.setAlignmentX( RIGHT_ALIGNMENT );
siteCounterField.setAlignmentY( CENTER_ALIGNMENT );
siteCounterField.addChangeListener( new ChangeListener() {
@Override
public void stateChanged(final ChangeEvent e) {
@@ -154,10 +138,8 @@ public class PasswordFrame extends JFrame implements DocumentListener {
} );
// Mask
maskPasswordField = new JCheckBox();
maskPasswordField.setFont( Res.exoRegular().deriveFont( 12f ) );
maskPasswordField = Components.checkBox( "Hide Password" );
maskPasswordField.setAlignmentX( Component.CENTER_ALIGNMENT );
maskPasswordField.setText( "Hide Password" );
maskPasswordField.setSelected( true );
maskPasswordField.addItemListener( new ItemListener() {
@Override
@@ -168,24 +150,27 @@ public class PasswordFrame extends JFrame implements DocumentListener {
// Password
passwordField = new JPasswordField();
passwordField.setHorizontalAlignment( JTextField.CENTER );
passwordField.setAlignmentX( Component.CENTER_ALIGNMENT );
passwordField.setEditable( false );
passwordField.setHorizontalAlignment( JTextField.CENTER );
passwordField.putClientProperty( "JPasswordField.cutCopyAllowed", true );
passwordField.setBorder( null );
passwordEchoChar = passwordField.getEchoChar();
passwordEchoFont = passwordField.getFont().deriveFont( 40f );
updateMask();
// Tip
tipLabel = new JLabel( " ", JLabel.CENTER );
tipLabel.setFont( Res.exoRegular().deriveFont( 9f ) );
tipLabel = Components.label( " ", JLabel.CENTER );
tipLabel.setAlignmentX( Component.CENTER_ALIGNMENT );
add( Components.boxLayout( BoxLayout.PAGE_AXIS, maskPasswordField, passwordField, tipLabel ), BorderLayout.SOUTH );
JPanel passwordContainer = Components.boxLayout( BoxLayout.PAGE_AXIS, maskPasswordField, passwordField, tipLabel );
passwordContainer.setOpaque( true );
passwordContainer.setBackground( Color.white );
passwordContainer.setBorder( BorderFactory.createEmptyBorder( 8, 8, 8, 8 ) );
add( Components.borderPanel( passwordContainer, BorderFactory.createLoweredSoftBevelBorder(), Res.colors().frameBg() ),
BorderLayout.SOUTH );
pack();
setMinimumSize( getSize() );
setPreferredSize( new Dimension( 600, getSize().height ) );
setMinimumSize( new Dimension( Math.max( 600, getPreferredSize().width ), Math.max( 300, getPreferredSize().height ) ) );
pack();
setLocationByPlatform( true );
@@ -194,7 +179,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
private void updateMask() {
passwordField.setEchoChar( maskPasswordField.isSelected()? passwordEchoChar: (char) 0 );
passwordField.setFont( maskPasswordField.isSelected()? passwordEchoFont: Res.sourceCodeProBlack().deriveFont( 40f ) );
passwordField.setFont( maskPasswordField.isSelected()? passwordEchoFont: Res.bigValueFont().deriveFont( 40f ) );
}
@Nonnull
@@ -203,19 +188,22 @@ public class PasswordFrame extends JFrame implements DocumentListener {
final String siteNameQuery = siteNameField.getText();
if (updatingUI)
return Futures.immediateCancelledFuture();
if (siteNameQuery == null || siteNameQuery.isEmpty() || !user.hasKey()) {
if (siteNameQuery == null || siteNameQuery.isEmpty() || !user.isKeyAvailable()) {
tipLabel.setText( null );
passwordField.setText( null );
return Futures.immediateCancelledFuture();
}
MPSiteType siteType = siteTypeField.getModel().getElementAt( siteTypeField.getSelectedIndex() );
final MPSiteType siteType = siteTypeField.getModel().getElementAt( siteTypeField.getSelectedIndex() );
final MasterKey.Version siteVersion = siteVersionField.getItemAt( siteVersionField.getSelectedIndex() );
final int siteCounter = (Integer) siteCounterField.getValue();
final Site site = currentSite != null && currentSite.getSiteName().equals( siteNameQuery )? currentSite
: Iterables.getFirst( user.findSitesByName( siteNameQuery ), new IncognitoSite( siteNameQuery, siteType, siteCounter ) );
: Iterables.getFirst( user.findSitesByName( siteNameQuery ),
new IncognitoSite( siteNameQuery, siteType, siteCounter, siteVersion ) );
assert site != null;
if (site == currentSite) {
site.setSiteType( siteType );
site.setAlgorithmVersion( siteVersion );
site.setSiteCounter( siteCounter );
}
@@ -223,12 +211,13 @@ public class PasswordFrame extends JFrame implements DocumentListener {
@Override
public String call()
throws Exception {
return user.getKey().encode( site.getSiteName(), site.getSiteType(), site.getSiteCounter(), MPSiteVariant.Password, null );
return user.getKey( site.getAlgorithmVersion() )
.encode( site.getSiteName(), site.getSiteType(), site.getSiteCounter(), MPSiteVariant.Password, null );
}
} );
Futures.addCallback( passwordFuture, new FutureCallback<String>() {
@Override
public void onSuccess(final String sitePassword) {
public void onSuccess(@Nullable final String sitePassword) {
SwingUtilities.invokeLater( new Runnable() {
@Override
public void run() {
@@ -236,6 +225,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
currentSite = site;
siteAddButton.setVisible( user instanceof ModelUser && !(currentSite instanceof ModelSite) );
siteTypeField.setSelectedItem( currentSite.getSiteType() );
siteVersionField.setSelectedItem( currentSite.getAlgorithmVersion() );
siteCounterField.setValue( currentSite.getSiteCounter() );
siteNameField.setText( currentSite.getSiteName() );
if (siteNameField.getText().startsWith( siteNameQuery ))

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.*;
import com.google.common.base.Throwables;
import com.google.common.collect.Maps;
import com.google.common.io.Resources;
import com.google.common.util.concurrent.*;
import com.lyndir.lhunath.opal.system.logging.Logger;
@@ -11,7 +12,9 @@ import java.awt.*;
import java.awt.event.*;
import java.awt.image.ImageObserver;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.net.URL;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.*;
import java.util.regex.Matcher;
@@ -26,13 +29,7 @@ public abstract class Res {
private static final WeakHashMap<Window, ExecutorService> executorByWindow = new WeakHashMap<>();
private static final Logger logger = Logger.get( Res.class );
private static Font sourceCodeProRegular;
private static Font sourceCodeProBlack;
private static Font exoBold;
private static Font exoExtraBold;
private static Font exoRegular;
private static Font exoThin;
private static final Colors colors = new Colors();
public static Future<?> execute(final Window host, final Runnable job) {
return getExecutor( host ).submit( new Runnable() {
@@ -100,64 +97,84 @@ public abstract class Res {
return 19;
}
public static Font controlFont() {
return arimoRegular();
}
public static Font valueFont() {
return sourceSansProRegular();
}
public static Font bigValueFont() {
return sourceSansProBlack();
}
public static Font sourceCodeProRegular() {
try {
return sourceCodeProRegular != null? sourceCodeProRegular: (sourceCodeProRegular =
Font.createFont( Font.TRUETYPE_FONT, Resources.getResource( "fonts/SourceCodePro-Regular.otf" ).openStream() ));
}
catch (FontFormatException | IOException e) {
throw Throwables.propagate( e );
}
return font( "fonts/SourceCodePro-Regular.otf" );
}
public static Font sourceCodeProBlack() {
try {
return sourceCodeProBlack != null? sourceCodeProBlack: (sourceCodeProBlack =
Font.createFont( Font.TRUETYPE_FONT, Resources.getResource( "fonts/SourceCodePro-Bold.otf" ).openStream() ));
}
catch (FontFormatException | IOException e) {
throw Throwables.propagate( e );
}
return font( "fonts/SourceCodePro-Bold.otf" );
}
public static Font sourceSansProRegular() {
return font( "fonts/SourceSansPro-Regular.otf" );
}
public static Font sourceSansProBlack() {
return font( "fonts/SourceSansPro-Bold.otf" );
}
public static Font exoBold() {
try {
return exoBold != null? exoBold: (exoBold =
Font.createFont( Font.TRUETYPE_FONT, Resources.getResource( "fonts/Exo2.0-Bold.otf" ).openStream() ));
}
catch (FontFormatException | IOException e) {
throw Throwables.propagate( e );
}
return font( "fonts/Exo2.0-Bold.otf" );
}
public static Font exoExtraBold() {
try {
return exoExtraBold != null? exoExtraBold: (exoExtraBold
= Font.createFont( Font.TRUETYPE_FONT, Resources.getResource( "fonts/Exo2.0-ExtraBold.otf" ).openStream() ));
}
catch (FontFormatException | IOException e) {
throw Throwables.propagate( e );
}
return font( "fonts/Exo2.0-ExtraBold.otf" );
}
public static Font exoRegular() {
try {
return exoRegular != null? exoRegular: (exoRegular =
Font.createFont( Font.TRUETYPE_FONT, Resources.getResource( "fonts/Exo2.0-Regular.otf" ).openStream() ));
}
catch (FontFormatException | IOException e) {
throw Throwables.propagate( e );
}
return font( "fonts/Exo2.0-Regular.otf" );
}
public static Font exoThin() {
try {
return exoThin != null? exoThin: (exoThin =
Font.createFont( Font.TRUETYPE_FONT, Resources.getResource( "fonts/Exo2.0-Thin.otf" ).openStream() ));
}
catch (FontFormatException | IOException e) {
throw Throwables.propagate( e );
}
return font( "fonts/Exo2.0-Thin.otf" );
}
public static Font arimoBold() {
return font( "fonts/Arimo-Bold.ttf" );
}
public static Font arimoBoldItalic() {
return font( "fonts/Arimo-BoldItalic.ttf" );
}
public static Font arimoItalic() {
return font( "fonts/Arimo-Italic.ttf" );
}
public static Font arimoRegular() {
return font( "fonts/Arimo-Regular.ttf" );
}
private static Font font(String fontResourceName) {
Map<String, SoftReference<Font>> fontsByResourceName = Maps.newHashMap();
SoftReference<Font> fontRef = fontsByResourceName.get( fontResourceName );
Font font = fontRef == null? null: fontRef.get();
if (font == null)
try {
fontsByResourceName.put( fontResourceName, new SoftReference<>(
font = Font.createFont( Font.TRUETYPE_FONT, Resources.getResource( fontResourceName ).openStream() ) ) );
}
catch (FontFormatException | IOException e) {
throw Throwables.propagate( e );
}
return font;
}
public static Colors colors() {
return colors;
}
private static final class RetinaIcon extends ImageIcon {
@@ -216,4 +233,24 @@ public abstract class Res {
g2d.dispose();
}
}
public static class Colors {
private final Color frameBg = Color.decode( "#5A5D6B" );
private final Color controlBg = Color.decode( "#ECECEC" );
private final Color controlBorder = Color.decode( "#BFBFBF" );
public Color frameBg() {
return frameBg;
}
public Color controlBg() {
return controlBg;
}
public Color controlBorder() {
return controlBorder;
}
}
}

View File

@@ -3,6 +3,7 @@ package com.lyndir.masterpassword.gui;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import com.lyndir.masterpassword.MPSiteType;
import com.lyndir.masterpassword.MasterKey;
/**
@@ -18,6 +19,10 @@ public abstract class Site {
public abstract void setSiteType(final MPSiteType siteType);
public abstract MasterKey.Version getAlgorithmVersion();
public abstract void setAlgorithmVersion(final MasterKey.Version algorithmVersion);
public abstract int getSiteCounter();
public abstract void setSiteCounter(final int siteCounter);

View File

@@ -2,11 +2,11 @@ package com.lyndir.masterpassword.gui;
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.event.*;
import javax.swing.*;
import javax.swing.border.*;
/**
@@ -14,13 +14,13 @@ import javax.swing.border.*;
*/
public class UnlockFrame extends JFrame {
private final SignInCallback signInCallback;
private final JPanel root;
private final JButton signInButton;
private final JPanel authenticationContainer;
private AuthenticationPanel authenticationPanel;
private boolean incognito;
public User user;
private final SignInCallback signInCallback;
private final Components.GradientPanel root;
private final JButton signInButton;
private final JPanel authenticationContainer;
private AuthenticationPanel authenticationPanel;
private boolean incognito;
public User user;
public UnlockFrame(final SignInCallback signInCallback)
throws HeadlessException {
@@ -28,19 +28,20 @@ public class UnlockFrame extends JFrame {
this.signInCallback = signInCallback;
setDefaultCloseOperation( DISPOSE_ON_CLOSE );
setContentPane( root = new JPanel( new BorderLayout( 20, 20 ) ) );
root.setBorder( new EmptyBorder( 20, 20, 20, 20 ) );
setContentPane( root = Components.gradientPanel( new BorderLayout( 20, 20 ), Res.colors().frameBg() ) );
root.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
authenticationContainer = new JPanel();
authenticationContainer.setLayout( new BoxLayout( authenticationContainer, BoxLayout.PAGE_AXIS ) );
authenticationContainer.setBorder( new CompoundBorder( new EtchedBorder( EtchedBorder.RAISED ), new EmptyBorder( 8, 8, 8, 8 ) ) );
add( authenticationContainer );
authenticationContainer = Components.boxLayout( BoxLayout.PAGE_AXIS );
authenticationContainer.setOpaque( true );
authenticationContainer.setBackground( Res.colors().controlBg() );
authenticationContainer.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
add( Components.borderPanel( authenticationContainer, BorderFactory.createRaisedBevelBorder(), Res.colors().frameBg() ) );
// Sign In
root.add( Components.boxLayout( BoxLayout.LINE_AXIS, Box.createGlue(), signInButton = new JButton( "Sign In" ), Box.createGlue() ),
BorderLayout.SOUTH );
signInButton.setFont( Res.exoRegular().deriveFont( 12f ) );
signInButton.setAlignmentX( LEFT_ALIGNMENT );
JPanel signInBox = Components.boxLayout( BoxLayout.LINE_AXIS, Box.createGlue(), signInButton = Components.button( "Sign In" ),
Box.createGlue() );
signInBox.setBackground( null );
root.add( signInBox, BorderLayout.SOUTH );
signInButton.addActionListener( new AbstractAction() {
@Override
public void actionPerformed(final ActionEvent e) {
@@ -55,10 +56,8 @@ public class UnlockFrame extends JFrame {
}
protected void repack() {
setPreferredSize( null );
pack();
setMinimumSize( getSize() );
setPreferredSize( new Dimension( 300, 300 ) );
setMinimumSize( new Dimension( Math.max( 300, getPreferredSize().width ), Math.max( 300, getPreferredSize().height ) ) );
pack();
}
@@ -71,11 +70,10 @@ public class UnlockFrame extends JFrame {
authenticationPanel = new ModelAuthenticationPanel( this );
}
authenticationPanel.updateUser( false );
authenticationContainer.add( authenticationPanel, BorderLayout.CENTER );
authenticationContainer.add( authenticationPanel );
authenticationContainer.add( Components.stud() );
final JCheckBox incognitoCheckBox = new JCheckBox( "Incognito" );
incognitoCheckBox.setFont( Res.exoRegular().deriveFont( 12f ) );
incognitoCheckBox.setAlignmentX( LEFT_ALIGNMENT );
final JCheckBox incognitoCheckBox = Components.checkBox( "Incognito" );
incognitoCheckBox.setSelected( incognito );
incognitoCheckBox.addItemListener( new ItemListener() {
@Override
@@ -91,7 +89,6 @@ public class UnlockFrame extends JFrame {
} );
JComponent toolsPanel = Components.boxLayout( BoxLayout.LINE_AXIS, incognitoCheckBox, Box.createGlue() );
toolsPanel.setAlignmentX( Component.LEFT_ALIGNMENT );
authenticationContainer.add( toolsPanel );
for (JButton button : authenticationPanel.getButtons()) {
button.setMargin( new Insets( 0, 0, 0, 0 ) );
@@ -112,13 +109,13 @@ public class UnlockFrame extends JFrame {
} );
}
void setUser(User user) {
void updateUser(User user) {
this.user = user;
checkSignIn();
}
boolean checkSignIn() {
boolean enabled = user != null && !user.getFullName().isEmpty() && user.hasKey();
boolean enabled = user != null && !user.getFullName().isEmpty() && authenticationPanel.getMasterPassword().length > 0;
signInButton.setEnabled( enabled );
return enabled;
@@ -137,29 +134,37 @@ public class UnlockFrame extends JFrame {
Res.execute( this, new Runnable() {
@Override
public void run() {
final boolean success = signInCallback.signedIn( user );
try {
user.authenticate( authenticationPanel.getMasterPassword() );
SwingUtilities.invokeLater( new Runnable() {
@Override
public void run() {
if (success) {
SwingUtilities.invokeLater( new Runnable() {
@Override
public void run() {
signInCallback.signedIn( user );
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 {
boolean signedIn(User user);
void signedIn(User user);
}
}

View File

@@ -1,12 +1,13 @@
package com.lyndir.masterpassword.gui;
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import com.lyndir.masterpassword.MasterKey;
import com.lyndir.masterpassword.model.MPUser;
import java.security.KeyException;
import com.lyndir.masterpassword.model.IncorrectMasterPasswordException;
import java.util.EnumMap;
import java.util.Objects;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
@@ -14,38 +15,47 @@ import javax.annotation.Nonnull;
*/
public abstract class User {
private MasterKey key;
@Nonnull
private final EnumMap<MasterKey.Version, MasterKey> keyByVersion = Maps.newEnumMap( MasterKey.Version.class );
public abstract String getFullName();
protected abstract String getMasterPassword();
@Nullable
protected abstract char[] getMasterPassword();
public abstract void authenticate(final char[] masterPassword)
throws IncorrectMasterPasswordException;
public int getAvatar() {
return 0;
}
public boolean hasKey() {
String masterPassword = getMasterPassword();
return key != null || (masterPassword != null && !masterPassword.isEmpty());
public boolean isKeyAvailable() {
return getMasterPassword() != null;
}
@Nonnull
public MasterKey getKey() throws MasterKeyException {
if (key == null) {
String masterPassword = getMasterPassword();
if (masterPassword == null || masterPassword.isEmpty()) {
reset();
throw new MasterKeyException( strf( "Master password unknown for user: %s", getFullName() ) );
}
public MasterKey getKey(MasterKey.Version algorithmVersion) {
char[] masterPassword = Preconditions.checkNotNull( getMasterPassword(), "User is not authenticated: " + getFullName() );
key = new MasterKey( getFullName(), masterPassword );
}
MasterKey key = keyByVersion.get( algorithmVersion );
if (key == null)
putKey( key = MasterKey.create( algorithmVersion, getFullName(), masterPassword ) );
if (!key.isValid())
key.revalidate( masterPassword );
return key;
}
protected void putKey(MasterKey masterKey) {
MasterKey oldKey = keyByVersion.put( masterKey.getAlgorithmVersion(), masterKey );
if (oldKey != null)
oldKey.invalidate();
}
public void reset() {
key = null;
for (MasterKey key : keyByVersion.values())
key.invalidate();
}
public abstract Iterable<Site> findSitesByName(final String siteName);

View File

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

View File

@@ -0,0 +1,218 @@
package com.lyndir.masterpassword.gui.util;
import com.lyndir.masterpassword.gui.Res;
import java.awt.*;
import javax.annotation.Nullable;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
/**
* @author lhunath, 2014-06-08
*/
public abstract class Components {
public static GradientPanel boxLayout(int axis, Component... components) {
GradientPanel container = gradientPanel( null, null );
// container.setBackground( Color.red );
container.setLayout( new BoxLayout( container, axis ) );
for (Component component : components)
container.add( component );
return container;
}
public static GradientPanel borderPanel(final JComponent component, @Nullable final Border border) {
return borderPanel( component, border, null );
}
public static GradientPanel borderPanel(final JComponent component, @Nullable final Border border, @Nullable Color background) {
GradientPanel box = boxLayout( BoxLayout.LINE_AXIS, component );
if (border != null)
box.setBorder( border );
if (background != null)
box.setBackground( background );
return box;
}
public static GradientPanel gradientPanel(@Nullable final LayoutManager layout, @Nullable final Color color) {
return new GradientPanel( layout, color ) {
{
setOpaque( color != null );
setBackground( null );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
}
};
}
public static JTextField textField() {
return new JTextField() {
{
setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) );
setFont( Res.valueFont().deriveFont( 12f ) );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
}
@Override
public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
}
};
}
public static JPasswordField passwordField() {
return new JPasswordField() {
{
setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
}
@Override
public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
}
};
}
public static JButton button(String label) {
return new JButton( label ) {
{
setFont( Res.controlFont().deriveFont( 12f ) );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
}
@Override
public Dimension getMaximumSize() {
return new Dimension( 20, getPreferredSize().height );
}
};
}
public static Component stud() {
Dimension studDimension = new Dimension( 8, 8 );
Box.Filler rigidArea = new Box.Filler( studDimension, studDimension, studDimension );
rigidArea.setAlignmentX( Component.LEFT_ALIGNMENT );
rigidArea.setAlignmentY( Component.BOTTOM_ALIGNMENT );
rigidArea.setBackground( Color.red );
return rigidArea;
}
public static JSpinner spinner(final SpinnerModel model) {
return new JSpinner( model ) {
{
CompoundBorder editorBorder = BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) );
((DefaultEditor) getEditor()).getTextField().setBorder( editorBorder );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
}
@Override
public Dimension getMaximumSize() {
return new Dimension( 20, getPreferredSize().height );
}
};
}
public static JLabel label(final String label) {
return label( label, JLabel.LEADING );
}
public static JLabel label(final String label, final int alignment) {
return new JLabel( label, alignment ) {
{
setFont( Res.controlFont().deriveFont( 12f ) );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
}
@Override
public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
}
};
}
public static JCheckBox checkBox(final String label) {
return new JCheckBox( label ) {
{
setFont( Res.controlFont().deriveFont( 12f ) );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
}
};
}
@SafeVarargs
public static <V> JComboBox<V> comboBox(final V... values) {
return comboBox( new DefaultComboBoxModel<>( values ) );
}
public static <M> JComboBox<M> comboBox(final ComboBoxModel<M> model) {
return new JComboBox<M>( model ) {
{
setFont( Res.controlFont().deriveFont( 12f ) );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
}
@Override
public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
}
};
}
public static class GradientPanel extends JPanel {
@Nullable
private Color gradientColor;
@Nullable
private GradientPaint paint;
protected GradientPanel(@Nullable final LayoutManager layout, @Nullable final Color gradientColor) {
super( layout );
this.gradientColor = gradientColor;
setBackground( null );
}
@Nullable
public Color getGradientColor() {
return gradientColor;
}
public void setGradientColor(@Nullable final Color gradientColor) {
this.gradientColor = gradientColor;
}
@Override
public void doLayout() {
super.doLayout();
if (gradientColor != null)
paint = new GradientPaint( new Point( 0, 0 ), gradientColor, new Point( getWidth(), getHeight() ), gradientColor.darker() );
}
@Override
protected void paintComponent(final Graphics g) {
super.paintComponent( g );
if (paint != null) {
((Graphics2D) g).setPaint( paint );
g.fillRect( 0, 0, getWidth(), getHeight() );
}
}
}
}

View File

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

View File

@@ -1,20 +0,0 @@
package com.lyndir.masterpassword.util;
import java.awt.*;
import javax.swing.*;
/**
* @author lhunath, 2014-06-08
*/
public abstract class Components {
public static JPanel boxLayout(int axis, Component... components) {
JPanel container = new JPanel();
container.setLayout( new BoxLayout( container, axis ) );
for (Component component : components)
container.add( component );
return container;
}
}

Some files were not shown because too many files have changed in this diff Show More