Compare commits
9 Commits
2.2
...
2.2-androi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73372f507a | ||
|
|
feddd038e3 | ||
|
|
3f6773f3a9 | ||
|
|
12b1610dc7 | ||
|
|
e20b33a051 | ||
|
|
b84ae532f2 | ||
|
|
145008406d | ||
|
|
a6ab9b9194 | ||
|
|
78c593fc08 |
@@ -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 -->
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
*
|
||||
* @author lhunath, 15-02-04
|
||||
*/
|
||||
|
||||
|
||||
@ParametersAreNonnullByDefault package com.lyndir.masterpassword;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() );
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# File used by Eclipse to determine the target system
|
||||
# Project target.
|
||||
target=android-16
|
||||
@@ -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>
|
||||
|
||||
1
MasterPassword/Java/masterpassword-android/release.jks
Symbolic link
@@ -0,0 +1 @@
|
||||
/Users/lhunath/SpiderOak Hive/secret/release-com.lyndir.masterpassword.jks
|
||||
|
Before Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 244 KiB After Width: | Height: | Size: 292 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 49 KiB |
@@ -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>
|
||||
|
Before Width: | Height: | Size: 3.6 KiB |
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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" />
|
||||
@@ -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>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="MPTheme" parent="android:Theme.Holo.Dialog.MinWidth">
|
||||
</style>
|
||||
</resources>
|
||||
@@ -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() ),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Typeface;
|
||||
|
||||
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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 ) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
*
|
||||
* @author lhunath, 15-02-04
|
||||
*/
|
||||
|
||||
|
||||
@ParametersAreNonnullByDefault
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
@@ -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 -->
|
||||
|
||||
1
MasterPassword/Java/masterpassword-gui/release.jks
Symbolic link
@@ -0,0 +1 @@
|
||||
/Users/lhunath/SpiderOak Hive/secret/release-com.lyndir.masterpassword.jks
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 ))
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
*
|
||||
* @author lhunath, 15-02-04
|
||||
*/
|
||||
|
||||
@ParametersAreNonnullByDefault
|
||||
package com.lyndir.masterpassword.gui;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
@@ -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() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
*
|
||||
* @author lhunath, 15-02-04
|
||||
*/
|
||||
|
||||
@ParametersAreNonnullByDefault
|
||||
package com.lyndir.masterpassword.gui.util;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||