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>
|
<parent>
|
||||||
<groupId>com.lyndir.masterpassword</groupId>
|
<groupId>com.lyndir.masterpassword</groupId>
|
||||||
<artifactId>masterpassword</artifactId>
|
<artifactId>masterpassword</artifactId>
|
||||||
<version>GIT-SNAPSHOT</version>
|
<version>2.2</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<name>Master Password Algorithm Implementation</name>
|
<name>Master Password Algorithm Implementation</name>
|
||||||
<description>The implementation of the Master Password algorithm</description>
|
<description>The implementation of the Master Password algorithm</description>
|
||||||
|
|
||||||
<groupId>com.lyndir.masterpassword</groupId>
|
|
||||||
<artifactId>masterpassword-algorithm</artifactId>
|
<artifactId>masterpassword-algorithm</artifactId>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
@@ -24,12 +23,12 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.lyndir.lhunath.opal</groupId>
|
<groupId>com.lyndir.lhunath.opal</groupId>
|
||||||
<artifactId>opal-system</artifactId>
|
<artifactId>opal-system</artifactId>
|
||||||
<version>1.6-p7</version>
|
<version>1.6-p8</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.lyndir.lhunath.opal</groupId>
|
<groupId>com.lyndir.lhunath.opal</groupId>
|
||||||
<artifactId>opal-crypto</artifactId>
|
<artifactId>opal-crypto</artifactId>
|
||||||
<version>1.6-p7</version>
|
<version>1.6-p8</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- EXTERNAL DEPENDENCIES -->
|
<!-- 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.
|
* @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)
|
if (name == null)
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.lyndir.masterpassword;
|
|||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -61,11 +62,11 @@ public enum MPSiteVariant {
|
|||||||
throw logger.bug( "No variant for option: %s", option );
|
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.
|
* @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)
|
if (name == null)
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -3,10 +3,8 @@ package com.lyndir.masterpassword;
|
|||||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import com.lyndir.lhunath.opal.system.util.MetaObject;
|
import com.lyndir.lhunath.opal.system.util.MetaObject;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package com.lyndir.masterpassword;
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
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;
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
import com.google.common.base.Charsets;
|
|
||||||
import com.google.common.base.Preconditions;
|
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.*;
|
||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
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 java.util.Arrays;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author lhunath, 2014-08-30
|
* @author lhunath, 2014-08-30
|
||||||
*/
|
*/
|
||||||
public class MasterKey {
|
public abstract class MasterKey {
|
||||||
|
|
||||||
public static final int ALGORITHM = 1;
|
|
||||||
public static final String VERSION = "2.1";
|
|
||||||
|
|
||||||
@SuppressWarnings("UnusedDeclaration")
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
private static final Logger logger = Logger.get( MasterKey.class );
|
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;
|
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
private final String fullName;
|
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;
|
this.fullName = fullName;
|
||||||
logger.trc( "fullName: %s", 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() {
|
public String getFullName() {
|
||||||
|
|
||||||
return fullName;
|
return fullName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
protected byte[] getKey() {
|
||||||
|
|
||||||
|
return Preconditions.checkNotNull( masterKey );
|
||||||
|
}
|
||||||
|
|
||||||
public byte[] getKeyID() {
|
public byte[] getKeyID() {
|
||||||
|
|
||||||
Preconditions.checkState( valid );
|
return idForBytes( getKey() );
|
||||||
return idForBytes( masterKey );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String encode(final String siteName, final MPSiteType siteType, int siteCounter, final MPSiteVariant siteVariant,
|
public abstract String encode(@Nonnull final String siteName, final MPSiteType siteType, int siteCounter,
|
||||||
@Nullable final String siteContext) {
|
final MPSiteVariant siteVariant, @Nullable final String siteContext);
|
||||||
Preconditions.checkState( valid );
|
|
||||||
Preconditions.checkArgument( siteType.getTypeClass() == MPSiteTypeClass.Generated );
|
|
||||||
Preconditions.checkArgument( !siteName.isEmpty() );
|
|
||||||
|
|
||||||
logger.trc( "siteName: %s", siteName );
|
public boolean isValid() {
|
||||||
logger.trc( "siteCounter: %d", siteCounter );
|
return masterKey != null;
|
||||||
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( 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 void invalidate() {
|
public void invalidate() {
|
||||||
|
|
||||||
valid = false;
|
if (masterKey != null) {
|
||||||
Arrays.fill( masterKey, (byte) 0 );
|
Arrays.fill( masterKey, (byte) 0 );
|
||||||
|
masterKey = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] bytesForInt(final int integer) {
|
public MasterKey revalidate(final char[] masterPassword) {
|
||||||
return ByteBuffer.allocate( MP_intLen / Byte.SIZE ).order( MP_byteOrder ).putInt( integer ).array();
|
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) {
|
protected abstract byte[] bytesForInt(final int integer);
|
||||||
return MP_hash.of( bytes );
|
|
||||||
|
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
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String get() {
|
public String get() {
|
||||||
return parentCase.getMasterPassword();
|
return new String( parentCase.getMasterPassword() );
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
keyID = ifNotNullElse( keyID, new NNSupplier<String>() {
|
keyID = ifNotNullElse( keyID, new NNSupplier<String>() {
|
||||||
@@ -148,8 +148,8 @@ public class MPWTests {
|
|||||||
return fullName;
|
return fullName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMasterPassword() {
|
public char[] getMasterPassword() {
|
||||||
return masterPassword;
|
return masterPassword == null? null: masterPassword.toCharArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getKeyID() {
|
public String getKeyID() {
|
||||||
@@ -161,7 +161,7 @@ public class MPWTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int getSiteCounter() {
|
public int getSiteCounter() {
|
||||||
return siteCounter;
|
return ifNotNullElse( siteCounter, 1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
public MPSiteType getSiteType() {
|
public MPSiteType getSiteType() {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ public class MasterKeyTest {
|
|||||||
throws Exception {
|
throws Exception {
|
||||||
|
|
||||||
for (MPWTests.Case testCase : tests.getCases()) {
|
for (MPWTests.Case testCase : tests.getCases()) {
|
||||||
MasterKey masterKey = new MasterKey( testCase.getFullName(), testCase.getMasterPassword() );
|
MasterKey masterKey = MasterKey.create( testCase.getFullName(), testCase.getMasterPassword() );
|
||||||
assertEquals(
|
assertEquals(
|
||||||
masterKey.encode( testCase.getSiteName(), testCase.getSiteType(), testCase.getSiteCounter(), testCase.getSiteVariant(),
|
masterKey.encode( testCase.getSiteName(), testCase.getSiteType(), testCase.getSiteCounter(), testCase.getSiteVariant(),
|
||||||
testCase.getSiteContext() ), testCase.getResult(), "Failed test case: " + testCase );
|
testCase.getSiteContext() ), testCase.getResult(), "Failed test case: " + testCase );
|
||||||
@@ -46,7 +46,7 @@ public class MasterKeyTest {
|
|||||||
public void testGetUserName()
|
public void testGetUserName()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
|
|
||||||
assertEquals( new MasterKey( defaultCase.getFullName(), defaultCase.getMasterPassword() ).getFullName(),
|
assertEquals( MasterKey.create( defaultCase.getFullName(), defaultCase.getMasterPassword() ).getFullName(),
|
||||||
defaultCase.getFullName() );
|
defaultCase.getFullName() );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ public class MasterKeyTest {
|
|||||||
throws Exception {
|
throws Exception {
|
||||||
|
|
||||||
for (MPWTests.Case testCase : tests.getCases()) {
|
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 );
|
assertEquals( CodeUtils.encodeHex( masterKey.getKeyID() ), testCase.getKeyID(), "Failed test case: " + testCase );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,7 +65,7 @@ public class MasterKeyTest {
|
|||||||
throws Exception {
|
throws Exception {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
MasterKey masterKey = new MasterKey( defaultCase.getFullName(), defaultCase.getMasterPassword() );
|
MasterKey masterKey = MasterKey.create( defaultCase.getFullName(), defaultCase.getMasterPassword() );
|
||||||
masterKey.invalidate();
|
masterKey.invalidate();
|
||||||
masterKey.encode( defaultCase.getSiteName(), defaultCase.getSiteType(), defaultCase.getSiteCounter(),
|
masterKey.encode( defaultCase.getSiteName(), defaultCase.getSiteType(), defaultCase.getSiteCounter(),
|
||||||
defaultCase.getSiteVariant(), defaultCase.getSiteContext() );
|
defaultCase.getSiteVariant(), defaultCase.getSiteContext() );
|
||||||
|
|||||||
@@ -2,17 +2,17 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.lyndir.masterpassword"
|
package="com.lyndir.masterpassword"
|
||||||
android:versionCode="1"
|
android:versionCode="1"
|
||||||
android:versionName="GIT-SNAPSHOT">
|
android:versionName="2.2">
|
||||||
|
|
||||||
<uses-sdk
|
<uses-sdk
|
||||||
android:minSdkVersion="14"
|
android:minSdkVersion="19"
|
||||||
android:targetSdkVersion="19" />
|
android:targetSdkVersion="21" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:icon="@drawable/icon"
|
android:icon="@drawable/icon"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:allowBackup="true">
|
android:allowBackup="true">
|
||||||
<activity android:name=".EmergencyActivity">
|
<activity android:name=".EmergencyActivity" android:theme="@style/MPTheme">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
# File used by Eclipse to determine the target system
|
|
||||||
# Project target.
|
|
||||||
target=android-16
|
|
||||||
@@ -7,13 +7,12 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.lyndir.masterpassword</groupId>
|
<groupId>com.lyndir.masterpassword</groupId>
|
||||||
<artifactId>masterpassword</artifactId>
|
<artifactId>masterpassword</artifactId>
|
||||||
<version>GIT-SNAPSHOT</version>
|
<version>2.2</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<name>Master Password Android</name>
|
<name>Master Password Android</name>
|
||||||
<description>An Android application to the Master Password algorithm</description>
|
<description>An Android application to the Master Password algorithm</description>
|
||||||
|
|
||||||
<groupId>com.lyndir.masterpassword</groupId>
|
|
||||||
<artifactId>masterpassword-android</artifactId>
|
<artifactId>masterpassword-android</artifactId>
|
||||||
<packaging>apk</packaging>
|
<packaging>apk</packaging>
|
||||||
|
|
||||||
@@ -30,7 +29,7 @@
|
|||||||
<skip>false</skip>
|
<skip>false</skip>
|
||||||
</zipalign>
|
</zipalign>
|
||||||
<sdk>
|
<sdk>
|
||||||
<platform>19</platform>
|
<platform>21</platform>
|
||||||
</sdk>
|
</sdk>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
@@ -39,9 +38,32 @@
|
|||||||
|
|
||||||
<profiles>
|
<profiles>
|
||||||
<profile>
|
<profile>
|
||||||
<id>sign</id>
|
<id>release</id>
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<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>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-jarsigner-plugin</artifactId>
|
<artifactId>maven-jarsigner-plugin</artifactId>
|
||||||
@@ -54,14 +76,14 @@
|
|||||||
<phase>package</phase>
|
<phase>package</phase>
|
||||||
<inherited>true</inherited>
|
<inherited>true</inherited>
|
||||||
<configuration>
|
<configuration>
|
||||||
<archiveDirectory></archiveDirectory>
|
<archiveDirectory />
|
||||||
<includes>
|
<includes>
|
||||||
<include>target/*.apk</include>
|
<include>target/*.apk</include>
|
||||||
</includes>
|
</includes>
|
||||||
<keystore>release.jks</keystore>
|
<keystore>release.jks</keystore>
|
||||||
<storepass>${env.PASSWORD}</storepass>
|
<storepass>${env.PASSWORD}</storepass>
|
||||||
<keypass>${env.PASSWORD}</keypass>
|
<keypass>${env.PASSWORD}</keypass>
|
||||||
<alias>android</alias>
|
<alias>masterpassword-android</alias>
|
||||||
<arguments>
|
<arguments>
|
||||||
<argument>-sigalg</argument><argument>MD5withRSA</argument>
|
<argument>-sigalg</argument><argument>MD5withRSA</argument>
|
||||||
<argument>-digestalg</argument><argument>SHA1</argument>
|
<argument>-digestalg</argument><argument>SHA1</argument>
|
||||||
@@ -70,16 +92,6 @@
|
|||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</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>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
</profile>
|
</profile>
|
||||||
@@ -92,7 +104,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.lyndir.masterpassword</groupId>
|
<groupId>com.lyndir.masterpassword</groupId>
|
||||||
<artifactId>masterpassword-algorithm</artifactId>
|
<artifactId>masterpassword-algorithm</artifactId>
|
||||||
<version>GIT-SNAPSHOT</version>
|
<version>2.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<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
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="20dp"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:gravity="center">
|
android:gravity="center">
|
||||||
|
|
||||||
@@ -16,55 +17,79 @@
|
|||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1" />
|
android:layout_weight="1" />
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/progressView"
|
|
||||||
android:layout_width="300dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="20dp"
|
|
||||||
android:indeterminate="true" />
|
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/userNameField"
|
android:id="@+id/fullNameField"
|
||||||
android:layout_width="300dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:nextFocusForward="@+id/masterPasswordField"
|
||||||
android:inputType="text|textCapWords|textPersonName"
|
android:inputType="text|textCapWords|textPersonName"
|
||||||
android:hint="@string/userName_hint"
|
android:hint="@string/fullName_hint"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:textColor="#FFFFFF"
|
android:textColor="#FFFFFF"
|
||||||
android:textSize="26sp" />
|
android:textSize="26sp" />
|
||||||
|
|
||||||
<EditText
|
<CheckBox
|
||||||
android:id="@+id/masterPasswordField"
|
android:id="@+id/rememberFullNameField"
|
||||||
android:layout_width="300dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:nextFocusForward="@+id/rememberPasswordField"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="@android:color/tertiary_text_dark"
|
||||||
|
android:text="@string/remember" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@id/masterPasswordField"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:nextFocusForward="@+id/siteNameField"
|
||||||
android:inputType="text|textPassword"
|
android:inputType="text|textPassword"
|
||||||
android:hint="@string/masterPassword_hint"
|
android:hint="@string/masterPassword_hint"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:textColor="#FFFFFF"
|
android:textColor="#FFFFFF"
|
||||||
android:textSize="18sp" />
|
android:textSize="18sp" />
|
||||||
|
|
||||||
<EditText
|
<CheckBox
|
||||||
android:id="@+id/siteNameField"
|
android:id="@id/rememberPasswordField"
|
||||||
android:layout_width="300dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="@android:color/tertiary_text_dark"
|
||||||
|
android:text="@string/forgetOnClose" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@id/siteNameField"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:nextFocusForward="@+id/sitePasswordField"
|
||||||
android:inputType="text|textNoSuggestions|textUri"
|
android:inputType="text|textNoSuggestions|textUri"
|
||||||
android:hint="@string/siteName_hint"
|
android:hint="@string/siteName_hint"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:textColor="#FFFFFF"
|
android:textColor="#FFFFFF"
|
||||||
android:textSize="26sp" />
|
android:textSize="18sp" />
|
||||||
|
|
||||||
<ImageView
|
<FrameLayout
|
||||||
android:layout_width="300dp"
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progressView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="20dp"
|
android:layout_gravity="center"
|
||||||
android:layout_marginBottom="30dp"
|
android:layout_margin="20dp"
|
||||||
android:src="@drawable/double_"
|
android:indeterminate="true" />
|
||||||
android:contentDescription="@string/empty" />
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/sitePasswordField"
|
android:id="@id/sitePasswordField"
|
||||||
android:layout_width="300dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:nextFocusForward="@+id/siteTypeField"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:background="@android:color/transparent"
|
android:background="@android:color/transparent"
|
||||||
android:textColor="#FFFFFF"
|
android:textColor="#FFFFFF"
|
||||||
@@ -72,33 +97,78 @@
|
|||||||
android:text="LuxdZozvDuma4["
|
android:text="LuxdZozvDuma4["
|
||||||
android:onClick="copySitePassword" />
|
android:onClick="copySitePassword" />
|
||||||
|
|
||||||
<Spinner
|
<TextView
|
||||||
android:id="@+id/typeField"
|
android:id="@+id/sitePasswordTip"
|
||||||
android:layout_width="300dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
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:textSize="14sp"
|
||||||
|
android:textColor="@android:color/tertiary_text_dark"
|
||||||
|
android:text="@string/maskPassword" />
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@id/siteTypeField"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:nextFocusForward="@+id/counterField"
|
||||||
android:gravity="center" />
|
android:gravity="center" />
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/counterField"
|
android:id="@id/counterField"
|
||||||
android:layout_width="300dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:nextFocusForward="@+id/siteVersionField"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:inputType="text|textNoSuggestions"
|
android:inputType="text|textNoSuggestions"
|
||||||
android:textColor="#FFFFFF"
|
android:textColor="#FFFFFF"
|
||||||
android:textSize="26sp"
|
android:textSize="18sp"
|
||||||
android:text="1" />
|
android:text="1" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:labelFor="@id/counterField"
|
||||||
|
android:gravity="center"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="@android:color/tertiary_text_dark"
|
||||||
|
android:text="@string/siteCounter_hint" />
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@id/siteVersionField"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:nextFocusForward="@id/rememberFullNameField"
|
||||||
|
android:gravity="center" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:labelFor="@id/siteVersionField"
|
||||||
|
android:gravity="center"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="@android:color/tertiary_text_dark"
|
||||||
|
android:text="@string/siteVersion_hint" />
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:layout_width="1dp"
|
android:layout_width="1dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1" />
|
android:layout_weight="1" />
|
||||||
|
|
||||||
<CheckBox
|
|
||||||
android:id="@+id/rememberPasswordField"
|
|
||||||
android:layout_width="300dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/remember" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|||||||
@@ -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"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Master Password</string>
|
<string name="app_name">Master Password</string>
|
||||||
<string name="avatar">User Avatar</string>
|
<string name="remember">Remember</string>
|
||||||
<string name="remember">Remember Password</string>
|
<string name="forgetOnClose">Forget on close</string>
|
||||||
<string name="siteName_hint">Site Name</string>
|
<string name="maskPassword">Hide password</string>
|
||||||
<string name="userName_hint">Your Name</string>
|
<string name="fullName_hint">Your full name</string>
|
||||||
<string name="masterPassword_hint">Your Master Password</string>
|
<string name="masterPassword_hint">Your master password</string>
|
||||||
|
<string name="siteName_hint">eg. google.com</string>
|
||||||
|
<string name="sitePassword_hint">Tap to copy</string>
|
||||||
|
<string name="siteCounter_hint">Password #</string>
|
||||||
|
<string name="siteVersion_hint">Algorithm</string>
|
||||||
<string name="empty" />
|
<string name="empty" />
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -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.app.Activity;
|
||||||
import android.content.*;
|
import android.content.*;
|
||||||
|
import android.content.ClipboardManager;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.Editable;
|
import android.text.*;
|
||||||
import android.text.TextWatcher;
|
import android.text.method.PasswordTransformationMethod;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.widget.*;
|
import android.widget.*;
|
||||||
@@ -17,7 +18,9 @@ import com.google.common.base.Throwables;
|
|||||||
import com.google.common.util.concurrent.*;
|
import com.google.common.util.concurrent.*;
|
||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
|
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
|
||||||
public class EmergencyActivity extends Activity {
|
public class EmergencyActivity extends Activity {
|
||||||
@@ -44,8 +47,8 @@ public class EmergencyActivity extends Activity {
|
|||||||
@InjectView(R.id.progressView)
|
@InjectView(R.id.progressView)
|
||||||
ProgressBar progressView;
|
ProgressBar progressView;
|
||||||
|
|
||||||
@InjectView(R.id.userNameField)
|
@InjectView(R.id.fullNameField)
|
||||||
EditText userNameField;
|
EditText fullNameField;
|
||||||
|
|
||||||
@InjectView(R.id.masterPasswordField)
|
@InjectView(R.id.masterPasswordField)
|
||||||
EditText masterPasswordField;
|
EditText masterPasswordField;
|
||||||
@@ -53,23 +56,36 @@ public class EmergencyActivity extends Activity {
|
|||||||
@InjectView(R.id.siteNameField)
|
@InjectView(R.id.siteNameField)
|
||||||
EditText siteNameField;
|
EditText siteNameField;
|
||||||
|
|
||||||
@InjectView(R.id.typeField)
|
@InjectView(R.id.siteTypeField)
|
||||||
Spinner typeField;
|
Spinner siteTypeField;
|
||||||
|
|
||||||
@InjectView(R.id.counterField)
|
@InjectView(R.id.counterField)
|
||||||
EditText counterField;
|
EditText counterField;
|
||||||
|
|
||||||
|
@InjectView(R.id.siteVersionField)
|
||||||
|
Spinner siteVersionField;
|
||||||
|
|
||||||
@InjectView(R.id.sitePasswordField)
|
@InjectView(R.id.sitePasswordField)
|
||||||
TextView sitePasswordField;
|
TextView sitePasswordField;
|
||||||
|
|
||||||
|
@InjectView(R.id.sitePasswordTip)
|
||||||
|
TextView sitePasswordTip;
|
||||||
|
|
||||||
|
@InjectView(R.id.rememberFullNameField)
|
||||||
|
CheckBox rememberFullNameField;
|
||||||
|
|
||||||
@InjectView(R.id.rememberPasswordField)
|
@InjectView(R.id.rememberPasswordField)
|
||||||
CheckBox rememberPasswordField;
|
CheckBox forgetPasswordField;
|
||||||
|
|
||||||
|
@InjectView(R.id.maskPasswordField)
|
||||||
|
CheckBox maskPasswordField;
|
||||||
|
|
||||||
private int hc_userName;
|
private int hc_userName;
|
||||||
private int hc_masterPassword;
|
private int hc_masterPassword;
|
||||||
|
private String sitePassword;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate( savedInstanceState );
|
super.onCreate( savedInstanceState );
|
||||||
Res.init( getResources() );
|
Res.init( getResources() );
|
||||||
|
|
||||||
@@ -77,14 +93,25 @@ public class EmergencyActivity extends Activity {
|
|||||||
setContentView( R.layout.activity_emergency );
|
setContentView( R.layout.activity_emergency );
|
||||||
ButterKnife.inject( this );
|
ButterKnife.inject( this );
|
||||||
|
|
||||||
userNameField.setOnFocusChangeListener( updateMasterKey );
|
fullNameField.setOnFocusChangeListener( updateMasterKey );
|
||||||
masterPasswordField.setOnFocusChangeListener( updateMasterKey );
|
masterPasswordField.setOnFocusChangeListener( updateMasterKey );
|
||||||
siteNameField.addTextChangedListener( updateSitePassword );
|
siteNameField.addTextChangedListener( updateSitePassword );
|
||||||
typeField.setOnItemSelectedListener( updateSitePassword );
|
siteTypeField.setOnItemSelectedListener( updateSitePassword );
|
||||||
counterField.addTextChangedListener( updateSitePassword );
|
counterField.addTextChangedListener( updateSitePassword );
|
||||||
|
siteVersionField.setOnItemSelectedListener( updateMasterKey );
|
||||||
|
sitePasswordField.addTextChangedListener( new ValueChangedListener() {
|
||||||
|
@Override
|
||||||
|
void update() {
|
||||||
|
boolean noPassword = TextUtils.isEmpty( sitePasswordField.getText() );
|
||||||
|
sitePasswordTip.setVisibility( noPassword? View.INVISIBLE: View.VISIBLE );
|
||||||
|
|
||||||
userNameField.setTypeface( Res.exo_Thin );
|
if (noPassword)
|
||||||
userNameField.setPaintFlags( userNameField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
|
sitePassword = null;
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
fullNameField.setTypeface( Res.exo_Thin );
|
||||||
|
fullNameField.setPaintFlags( fullNameField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
|
||||||
masterPasswordField.setTypeface( Res.sourceCodePro_ExtraLight );
|
masterPasswordField.setTypeface( Res.sourceCodePro_ExtraLight );
|
||||||
masterPasswordField.setPaintFlags( masterPasswordField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
|
masterPasswordField.setPaintFlags( masterPasswordField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
|
||||||
siteNameField.setTypeface( Res.exo_Regular );
|
siteNameField.setTypeface( Res.exo_Regular );
|
||||||
@@ -92,13 +119,33 @@ public class EmergencyActivity extends Activity {
|
|||||||
sitePasswordField.setTypeface( Res.sourceCodePro_Black );
|
sitePasswordField.setTypeface( Res.sourceCodePro_Black );
|
||||||
sitePasswordField.setPaintFlags( sitePasswordField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
|
sitePasswordField.setPaintFlags( sitePasswordField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
|
||||||
|
|
||||||
typeField.setAdapter( new ArrayAdapter<>( this, R.layout.type_item, MPSiteType.forClass( MPSiteTypeClass.Generated ) ) );
|
siteTypeField.setAdapter( new ArrayAdapter<>( this, R.layout.spinner_item, MPSiteType.forClass( MPSiteTypeClass.Generated ) ) );
|
||||||
typeField.setSelection( MPSiteType.GeneratedLong.ordinal() );
|
siteTypeField.setSelection( MPSiteType.GeneratedLong.ordinal() );
|
||||||
|
|
||||||
rememberPasswordField.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() {
|
siteVersionField.setAdapter( new ArrayAdapter<>( this, R.layout.spinner_item, MasterKey.Version.values() ) );
|
||||||
|
siteVersionField.setSelection( MasterKey.Version.CURRENT.ordinal() );
|
||||||
|
|
||||||
|
rememberFullNameField.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
|
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
|
||||||
getPreferences( MODE_PRIVATE ).edit().putBoolean( "rememberPassword", isChecked ).apply();
|
getPreferences( MODE_PRIVATE ).edit().putBoolean( "rememberFullName", isChecked ).apply();
|
||||||
|
if (isChecked)
|
||||||
|
getPreferences( MODE_PRIVATE ).edit().putString( "fullName", fullNameField.getText().toString() ).apply();
|
||||||
|
else
|
||||||
|
getPreferences( MODE_PRIVATE ).edit().putString( "fullName", "" ).apply();
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
forgetPasswordField.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
|
||||||
|
getPreferences( MODE_PRIVATE ).edit().putBoolean( "forgetPassword", isChecked ).apply();
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
maskPasswordField.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
|
||||||
|
getPreferences( MODE_PRIVATE ).edit().putBoolean( "maskPassword", isChecked ).apply();
|
||||||
|
sitePasswordField.setTransformationMethod( isChecked? new PasswordTransformationMethod(): null );
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
@@ -107,14 +154,21 @@ public class EmergencyActivity extends Activity {
|
|||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
userNameField.setText( getPreferences( MODE_PRIVATE ).getString( "userName", "" ) );
|
fullNameField.setText( getPreferences( MODE_PRIVATE ).getString( "fullName", "" ) );
|
||||||
rememberPasswordField.setSelected( isRememberPasswordEnabled() );
|
rememberFullNameField.setChecked( isRememberFullNameEnabled() );
|
||||||
|
forgetPasswordField.setChecked( isForgetPasswordEnabled() );
|
||||||
|
maskPasswordField.setChecked( isMaskPasswordEnabled() );
|
||||||
|
sitePasswordField.setTransformationMethod( isMaskPasswordEnabled()? new PasswordTransformationMethod(): null );
|
||||||
|
|
||||||
|
if (TextUtils.isEmpty( masterPasswordField.getText() ))
|
||||||
masterPasswordField.requestFocus();
|
masterPasswordField.requestFocus();
|
||||||
|
else
|
||||||
|
siteNameField.requestFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPause() {
|
protected void onPause() {
|
||||||
if (!isRememberPasswordEnabled()) {
|
if (isForgetPasswordEnabled()) {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
hc_userName = hc_masterPassword = 0;
|
hc_userName = hc_masterPassword = 0;
|
||||||
if (masterKeyFuture != null) {
|
if (masterKeyFuture != null) {
|
||||||
@@ -122,44 +176,64 @@ public class EmergencyActivity extends Activity {
|
|||||||
masterKeyFuture = null;
|
masterKeyFuture = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
masterPasswordField.setText( "" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
siteNameField.setText( "" );
|
||||||
sitePasswordField.setText( "" );
|
sitePasswordField.setText( "" );
|
||||||
progressView.setVisibility( View.INVISIBLE );
|
progressView.setVisibility( View.INVISIBLE );
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
super.onPause();
|
super.onPause();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isRememberPasswordEnabled() {
|
private boolean isRememberFullNameEnabled() {
|
||||||
return getPreferences( MODE_PRIVATE ).getBoolean( "rememberPassword", false );
|
return getPreferences( MODE_PRIVATE ).getBoolean( "rememberFullName", false );
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isForgetPasswordEnabled() {
|
||||||
|
return getPreferences( MODE_PRIVATE ).getBoolean( "forgetPassword", false );
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isMaskPasswordEnabled() {
|
||||||
|
return getPreferences( MODE_PRIVATE ).getBoolean( "maskPassword", false );
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void updateMasterKey() {
|
private synchronized void updateMasterKey() {
|
||||||
final String userName = userNameField.getText().toString();
|
final String fullName = fullNameField.getText().toString();
|
||||||
final String masterPassword = masterPasswordField.getText().toString();
|
final char[] masterPassword = masterPasswordField.getText().toString().toCharArray();
|
||||||
if (userName.hashCode() == hc_userName && masterPassword.hashCode() == hc_masterPassword)
|
final MasterKey.Version version = (MasterKey.Version) siteVersionField.getSelectedItem();
|
||||||
|
try {
|
||||||
|
if (fullName.hashCode() == hc_userName && Arrays.hashCode( masterPassword ) == hc_masterPassword &&
|
||||||
|
masterKeyFuture != null && masterKeyFuture.get().getAlgorithmVersion() == version)
|
||||||
return;
|
return;
|
||||||
hc_userName = userName.hashCode();
|
}
|
||||||
hc_masterPassword = masterPassword.hashCode();
|
catch (InterruptedException | ExecutionException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
hc_userName = fullName.hashCode();
|
||||||
|
hc_masterPassword = Arrays.hashCode( masterPassword );
|
||||||
|
|
||||||
getPreferences( MODE_PRIVATE ).edit().putString( "userName", userName ).apply();
|
if (isRememberFullNameEnabled())
|
||||||
|
getPreferences( MODE_PRIVATE ).edit().putString( "fullName", fullName ).apply();
|
||||||
|
|
||||||
if (masterKeyFuture != null)
|
if (masterKeyFuture != null)
|
||||||
masterKeyFuture.cancel( true );
|
masterKeyFuture.cancel( true );
|
||||||
|
|
||||||
if (userName.isEmpty() || masterPassword.isEmpty()) {
|
if (fullName.isEmpty() || masterPassword.length == 0) {
|
||||||
sitePasswordField.setText( "" );
|
sitePasswordField.setText( "" );
|
||||||
progressView.setVisibility( View.INVISIBLE );
|
progressView.setVisibility( View.INVISIBLE );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sitePasswordField.setText( "" );
|
||||||
progressView.setVisibility( View.VISIBLE );
|
progressView.setVisibility( View.VISIBLE );
|
||||||
(masterKeyFuture = executor.submit( new Callable<MasterKey>() {
|
(masterKeyFuture = executor.submit( new Callable<MasterKey>() {
|
||||||
@Override
|
@Override
|
||||||
public MasterKey call()
|
public MasterKey call()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
try {
|
try {
|
||||||
return new MasterKey( userName, masterPassword );
|
return MasterKey.create( version, fullName, masterPassword );
|
||||||
}
|
}
|
||||||
catch (RuntimeException e) {
|
catch (RuntimeException e) {
|
||||||
sitePasswordField.setText( "" );
|
sitePasswordField.setText( "" );
|
||||||
@@ -183,21 +257,25 @@ public class EmergencyActivity extends Activity {
|
|||||||
|
|
||||||
private void updateSitePassword() {
|
private void updateSitePassword() {
|
||||||
final String siteName = siteNameField.getText().toString();
|
final String siteName = siteNameField.getText().toString();
|
||||||
final MPSiteType type = (MPSiteType) typeField.getSelectedItem();
|
final MPSiteType type = (MPSiteType) siteTypeField.getSelectedItem();
|
||||||
final int counter = ConversionUtils.toIntegerNN( counterField.getText() );
|
final int counter = ConversionUtils.toIntegerNN( counterField.getText() );
|
||||||
|
|
||||||
if (masterKeyFuture == null || siteName.isEmpty() || type == null) {
|
if (masterKeyFuture == null || siteName.isEmpty() || type == null) {
|
||||||
sitePasswordField.setText( "" );
|
sitePasswordField.setText( "" );
|
||||||
progressView.setVisibility( View.INVISIBLE );
|
progressView.setVisibility( View.INVISIBLE );
|
||||||
|
|
||||||
|
if (masterKeyFuture == null)
|
||||||
|
updateMasterKey();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sitePasswordField.setText( "" );
|
||||||
progressView.setVisibility( View.VISIBLE );
|
progressView.setVisibility( View.VISIBLE );
|
||||||
executor.submit( new Runnable() {
|
executor.submit( new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
final String sitePassword = masterKeyFuture.get().encode( siteName, type, counter, MPSiteVariant.Password, null );
|
sitePassword = masterKeyFuture.get().encode( siteName, type, counter, MPSiteVariant.Password, null );
|
||||||
|
|
||||||
runOnUiThread( new Runnable() {
|
runOnUiThread( new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@@ -228,8 +306,7 @@ public class EmergencyActivity extends Activity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void copySitePassword(View view) {
|
public void copySitePassword(View view) {
|
||||||
String sitePassword = sitePasswordField.getText().toString();
|
if (TextUtils.isEmpty( sitePassword ))
|
||||||
if (sitePassword.isEmpty())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ClipDescription description = new ClipDescription( strf( "Password for %s", siteNameField.getText() ),
|
ClipDescription description = new ClipDescription( strf( "Password for %s", siteNameField.getText() ),
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.lyndir.masterpassword;
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.Typeface;
|
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>
|
<parent>
|
||||||
<groupId>com.lyndir.masterpassword</groupId>
|
<groupId>com.lyndir.masterpassword</groupId>
|
||||||
<artifactId>masterpassword</artifactId>
|
<artifactId>masterpassword</artifactId>
|
||||||
<version>GIT-SNAPSHOT</version>
|
<version>2.2</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<name>Master Password CLI</name>
|
<name>Master Password CLI</name>
|
||||||
<description>A CLI interface to the Master Password algorithm</description>
|
<description>A CLI interface to the Master Password algorithm</description>
|
||||||
|
|
||||||
<groupId>com.lyndir.masterpassword</groupId>
|
|
||||||
<artifactId>masterpassword-cli</artifactId>
|
<artifactId>masterpassword-cli</artifactId>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
@@ -37,7 +36,7 @@
|
|||||||
<phase>prepare-package</phase>
|
<phase>prepare-package</phase>
|
||||||
<configuration>
|
<configuration>
|
||||||
<target>
|
<target>
|
||||||
<chmod file="${project.build.directory}/install" perm="755"/>
|
<chmod file="${project.build.directory}/install" perm="755" />
|
||||||
</target>
|
</target>
|
||||||
</configuration>
|
</configuration>
|
||||||
<goals>
|
<goals>
|
||||||
@@ -86,7 +85,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.lyndir.masterpassword</groupId>
|
<groupId>com.lyndir.masterpassword</groupId>
|
||||||
<artifactId>masterpassword-algorithm</artifactId>
|
<artifactId>masterpassword-algorithm</artifactId>
|
||||||
<version>GIT-SNAPSHOT</version>
|
<version>2.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
@@ -45,7 +45,8 @@ public class CLI {
|
|||||||
throws IOException {
|
throws IOException {
|
||||||
|
|
||||||
// Read information from the environment.
|
// Read information from the environment.
|
||||||
String siteName = null, masterPassword, context = null;
|
char[] masterPassword;
|
||||||
|
String siteName = null, context = null;
|
||||||
String userName = System.getenv( ENV_USERNAME );
|
String userName = System.getenv( ENV_USERNAME );
|
||||||
String siteTypeName = ifNotNullElse( System.getenv( ENV_SITETYPE ), "" );
|
String siteTypeName = ifNotNullElse( System.getenv( ENV_SITETYPE ), "" );
|
||||||
MPSiteType siteType = siteTypeName.isEmpty()? MPSiteType.GeneratedLong: MPSiteType.forOption( siteTypeName );
|
MPSiteType siteType = siteTypeName.isEmpty()? MPSiteType.GeneratedLong: MPSiteType.forOption( siteTypeName );
|
||||||
@@ -174,15 +175,15 @@ public class CLI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (console != null)
|
if (console != null)
|
||||||
masterPassword = new String( console.readPassword( "%s's master password: ", userName ) );
|
masterPassword = console.readPassword( "%s's master password: ", userName );
|
||||||
|
|
||||||
else {
|
else {
|
||||||
System.err.format( "%s's master password: ", userName );
|
System.err.format( "%s's master password: ", userName );
|
||||||
masterPassword = lineReader.readLine();
|
masterPassword = lineReader.readLine().toCharArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode and write out the site password.
|
// 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>
|
<parent>
|
||||||
<groupId>com.lyndir.masterpassword</groupId>
|
<groupId>com.lyndir.masterpassword</groupId>
|
||||||
<artifactId>masterpassword</artifactId>
|
<artifactId>masterpassword</artifactId>
|
||||||
<version>GIT-SNAPSHOT</version>
|
<version>2.2</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<name>Master Password GUI</name>
|
<name>Master Password GUI</name>
|
||||||
<description>A GUI interface to the Master Password algorithm</description>
|
<description>A GUI interface to the Master Password algorithm</description>
|
||||||
|
|
||||||
<groupId>com.lyndir.masterpassword</groupId>
|
|
||||||
<artifactId>masterpassword-gui</artifactId>
|
<artifactId>masterpassword-gui</artifactId>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
@@ -67,6 +66,67 @@
|
|||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</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 -->
|
<!-- DEPENDENCY MANAGEMENT -->
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|
||||||
@@ -74,7 +134,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.lyndir.masterpassword</groupId>
|
<groupId>com.lyndir.masterpassword</groupId>
|
||||||
<artifactId>masterpassword-model</artifactId>
|
<artifactId>masterpassword-model</artifactId>
|
||||||
<version>GIT-SNAPSHOT</version>
|
<version>2.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- EXTERNAL DEPENDENCIES -->
|
<!-- 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;
|
package com.lyndir.masterpassword.gui;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.lyndir.masterpassword.gui.util.Components;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
|
||||||
@@ -8,12 +9,13 @@ import javax.swing.*;
|
|||||||
/**
|
/**
|
||||||
* @author lhunath, 2014-06-11
|
* @author lhunath, 2014-06-11
|
||||||
*/
|
*/
|
||||||
public abstract class AuthenticationPanel extends JPanel {
|
public abstract class AuthenticationPanel extends Components.GradientPanel {
|
||||||
|
|
||||||
protected final UnlockFrame unlockFrame;
|
protected final UnlockFrame unlockFrame;
|
||||||
protected final JLabel avatarLabel;
|
protected final JLabel avatarLabel;
|
||||||
|
|
||||||
public AuthenticationPanel(final UnlockFrame unlockFrame) {
|
public AuthenticationPanel(final UnlockFrame unlockFrame) {
|
||||||
|
super( null, null );
|
||||||
this.unlockFrame = unlockFrame;
|
this.unlockFrame = unlockFrame;
|
||||||
|
|
||||||
setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) );
|
setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) );
|
||||||
@@ -30,7 +32,7 @@ public abstract class AuthenticationPanel extends JPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void updateUser(boolean repack) {
|
protected void updateUser(boolean repack) {
|
||||||
unlockFrame.setUser( getSelectedUser() );
|
unlockFrame.updateUser( getSelectedUser() );
|
||||||
validate();
|
validate();
|
||||||
|
|
||||||
if (repack)
|
if (repack)
|
||||||
@@ -39,6 +41,8 @@ public abstract class AuthenticationPanel extends JPanel {
|
|||||||
|
|
||||||
protected abstract User getSelectedUser();
|
protected abstract User getSelectedUser();
|
||||||
|
|
||||||
|
public abstract char[] getMasterPassword();
|
||||||
|
|
||||||
public Component getFocusComponent() {
|
public Component getFocusComponent() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ public class GUI implements UnlockFrame.SignInCallback {
|
|||||||
@SuppressWarnings("UnusedDeclaration")
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
private static final Logger logger = Logger.get( GUI.class );
|
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;
|
private PasswordFrame passwordFrame;
|
||||||
|
|
||||||
public static void main(final String[] args)
|
public static void main(final String[] args)
|
||||||
@@ -48,14 +48,7 @@ public class GUI implements UnlockFrame.SignInCallback {
|
|||||||
if (Config.get().checkForUpdates())
|
if (Config.get().checkForUpdates())
|
||||||
checkUpdate();
|
checkUpdate();
|
||||||
|
|
||||||
GUI gui;
|
TypeUtils.<GUI>newInstance( AppleGUI.class ).or( new GUI() ).open();
|
||||||
try {
|
|
||||||
gui = TypeUtils.newInstance( AppleGUI.class );
|
|
||||||
}
|
|
||||||
catch (NoClassDefFoundError e) {
|
|
||||||
gui = new GUI();
|
|
||||||
}
|
|
||||||
gui.open();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void checkUpdate() {
|
private static void checkUpdate() {
|
||||||
@@ -100,19 +93,9 @@ public class GUI implements UnlockFrame.SignInCallback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean signedIn(final User user) {
|
public void signedIn(final User user) {
|
||||||
if (!user.hasKey())
|
|
||||||
return false;
|
|
||||||
try {
|
|
||||||
user.getKey();
|
|
||||||
passwordFrame = newPasswordFrame( user );
|
passwordFrame = newPasswordFrame( user );
|
||||||
|
|
||||||
open();
|
open();
|
||||||
return true;
|
|
||||||
} catch (MasterKeyException e) {
|
|
||||||
JOptionPane.showMessageDialog( null, e.getLocalizedMessage(), "Sign In Failed", JOptionPane.ERROR_MESSAGE );
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected PasswordFrame newPasswordFrame(final User user) {
|
protected PasswordFrame newPasswordFrame(final User user) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.lyndir.masterpassword.gui;
|
package com.lyndir.masterpassword.gui;
|
||||||
|
|
||||||
|
import com.lyndir.masterpassword.gui.util.Components;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
@@ -20,40 +21,23 @@ public class IncognitoAuthenticationPanel extends AuthenticationPanel implements
|
|||||||
|
|
||||||
// Full Name
|
// Full Name
|
||||||
super( unlockFrame );
|
super( unlockFrame );
|
||||||
JLabel fullNameLabel = new JLabel( "Full Name:" );
|
add( Components.stud() );
|
||||||
fullNameLabel.setFont( Res.exoRegular().deriveFont( 12f ) );
|
|
||||||
fullNameLabel.setAlignmentX( LEFT_ALIGNMENT );
|
JLabel fullNameLabel = Components.label( "Full Name:" );
|
||||||
fullNameLabel.setHorizontalAlignment( SwingConstants.CENTER );
|
|
||||||
fullNameLabel.setVerticalAlignment( SwingConstants.BOTTOM );
|
|
||||||
add( fullNameLabel );
|
add( fullNameLabel );
|
||||||
|
|
||||||
fullNameField = new JTextField() {
|
fullNameField = Components.textField();
|
||||||
@Override
|
fullNameField.setFont( Res.valueFont().deriveFont( 12f ) );
|
||||||
public Dimension getMaximumSize() {
|
|
||||||
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
fullNameField.setFont( Res.sourceCodeProRegular().deriveFont( 12f ) );
|
|
||||||
fullNameField.setAlignmentX( LEFT_ALIGNMENT );
|
|
||||||
fullNameField.getDocument().addDocumentListener( this );
|
fullNameField.getDocument().addDocumentListener( this );
|
||||||
fullNameField.addActionListener( this );
|
fullNameField.addActionListener( this );
|
||||||
add( fullNameField );
|
add( fullNameField );
|
||||||
|
add( Components.stud() );
|
||||||
|
|
||||||
// Master Password
|
// Master Password
|
||||||
JLabel masterPasswordLabel = new JLabel( "Master Password:" );
|
JLabel masterPasswordLabel = Components.label( "Master Password:" );
|
||||||
masterPasswordLabel.setFont( Res.exoRegular().deriveFont( 12f ) );
|
|
||||||
masterPasswordLabel.setAlignmentX( LEFT_ALIGNMENT );
|
|
||||||
masterPasswordLabel.setHorizontalAlignment( SwingConstants.CENTER );
|
|
||||||
masterPasswordLabel.setVerticalAlignment( SwingConstants.BOTTOM );
|
|
||||||
add( masterPasswordLabel );
|
add( masterPasswordLabel );
|
||||||
|
|
||||||
masterPasswordField = new JPasswordField() {
|
masterPasswordField = Components.passwordField();
|
||||||
@Override
|
|
||||||
public Dimension getMaximumSize() {
|
|
||||||
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
masterPasswordField.setAlignmentX( LEFT_ALIGNMENT );
|
|
||||||
masterPasswordField.addActionListener( this );
|
masterPasswordField.addActionListener( this );
|
||||||
masterPasswordField.getDocument().addDocumentListener( this );
|
masterPasswordField.getDocument().addDocumentListener( this );
|
||||||
add( masterPasswordField );
|
add( masterPasswordField );
|
||||||
@@ -71,7 +55,12 @@ public class IncognitoAuthenticationPanel extends AuthenticationPanel implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected User getSelectedUser() {
|
protected User getSelectedUser() {
|
||||||
return new IncognitoUser( fullNameField.getText(), new String( masterPasswordField.getPassword() ) );
|
return new IncognitoUser( fullNameField.getText() );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public char[] getMasterPassword() {
|
||||||
|
return masterPasswordField.getPassword();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.lyndir.masterpassword.gui;
|
package com.lyndir.masterpassword.gui;
|
||||||
|
|
||||||
import com.lyndir.masterpassword.MPSiteType;
|
import com.lyndir.masterpassword.MPSiteType;
|
||||||
|
import com.lyndir.masterpassword.MasterKey;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -11,11 +12,14 @@ public class IncognitoSite extends Site {
|
|||||||
private String siteName;
|
private String siteName;
|
||||||
private MPSiteType siteType;
|
private MPSiteType siteType;
|
||||||
private int siteCounter;
|
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.siteName = siteName;
|
||||||
this.siteType = siteType;
|
this.siteType = siteType;
|
||||||
this.siteCounter = siteCounter;
|
this.siteCounter = siteCounter;
|
||||||
|
this.algorithmVersion = algorithmVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSiteName() {
|
public String getSiteName() {
|
||||||
@@ -34,6 +38,16 @@ public class IncognitoSite extends Site {
|
|||||||
this.siteType = siteType;
|
this.siteType = siteType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MasterKey.Version getAlgorithmVersion() {
|
||||||
|
return algorithmVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAlgorithmVersion(final MasterKey.Version algorithmVersion) {
|
||||||
|
this.algorithmVersion = algorithmVersion;
|
||||||
|
}
|
||||||
|
|
||||||
public int getSiteCounter() {
|
public int getSiteCounter() {
|
||||||
return siteCounter;
|
return siteCounter;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.lyndir.masterpassword.gui;
|
package com.lyndir.masterpassword.gui;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.lyndir.masterpassword.model.IncorrectMasterPasswordException;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -9,22 +11,28 @@ import com.google.common.collect.ImmutableList;
|
|||||||
public class IncognitoUser extends User {
|
public class IncognitoUser extends User {
|
||||||
|
|
||||||
private final String fullName;
|
private final String fullName;
|
||||||
private final String masterPassword;
|
private char[] masterPassword;
|
||||||
|
|
||||||
public IncognitoUser(final String fullName, final String masterPassword) {
|
public IncognitoUser(final String fullName) {
|
||||||
this.fullName = fullName;
|
this.fullName = fullName;
|
||||||
this.masterPassword = masterPassword;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getFullName() {
|
public String getFullName() {
|
||||||
return fullName;
|
return fullName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
protected String getMasterPassword() {
|
protected char[] getMasterPassword() {
|
||||||
return masterPassword;
|
return masterPassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void authenticate(final char[] masterPassword)
|
||||||
|
throws IncorrectMasterPasswordException {
|
||||||
|
this.masterPassword = masterPassword;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterable<Site> findSitesByName(final String siteName) {
|
public Iterable<Site> findSitesByName(final String siteName) {
|
||||||
return ImmutableList.of();
|
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;
|
package com.lyndir.masterpassword.gui;
|
||||||
|
|
||||||
import com.google.common.base.Function;
|
import com.google.common.base.Function;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.collect.*;
|
import com.google.common.collect.*;
|
||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
import com.lyndir.masterpassword.model.MPUser;
|
import com.lyndir.masterpassword.model.MPUser;
|
||||||
import com.lyndir.masterpassword.model.MPUserFileManager;
|
import com.lyndir.masterpassword.model.MPUserFileManager;
|
||||||
|
import com.lyndir.masterpassword.gui.util.Components;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.*;
|
import java.awt.event.*;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
@@ -27,6 +29,7 @@ public class ModelAuthenticationPanel extends AuthenticationPanel implements Ite
|
|||||||
|
|
||||||
public ModelAuthenticationPanel(final UnlockFrame unlockFrame) {
|
public ModelAuthenticationPanel(final UnlockFrame unlockFrame) {
|
||||||
super( unlockFrame );
|
super( unlockFrame );
|
||||||
|
add( Components.stud() );
|
||||||
|
|
||||||
// Avatar
|
// Avatar
|
||||||
avatarLabel.addMouseListener( new MouseAdapter() {
|
avatarLabel.addMouseListener( new MouseAdapter() {
|
||||||
@@ -41,40 +44,21 @@ public class ModelAuthenticationPanel extends AuthenticationPanel implements Ite
|
|||||||
} );
|
} );
|
||||||
|
|
||||||
// User
|
// User
|
||||||
JLabel userLabel = new JLabel( "User:" );
|
JLabel userLabel = Components.label( "User:" );
|
||||||
userLabel.setFont( Res.exoRegular().deriveFont( 12f ) );
|
|
||||||
userLabel.setAlignmentX( LEFT_ALIGNMENT );
|
|
||||||
userLabel.setHorizontalAlignment( SwingConstants.CENTER );
|
|
||||||
userLabel.setVerticalAlignment( SwingConstants.BOTTOM );
|
|
||||||
add( userLabel );
|
add( userLabel );
|
||||||
|
|
||||||
userField = new JComboBox<ModelUser>( new DefaultComboBoxModel<>( readConfigUsers() ) ) {
|
userField = Components.comboBox( readConfigUsers() );
|
||||||
@Override
|
userField.setFont( Res.valueFont().deriveFont( 12f ) );
|
||||||
public Dimension getMaximumSize() {
|
|
||||||
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
userField.setFont( Res.sourceCodeProRegular().deriveFont( 12f ) );
|
|
||||||
userField.setAlignmentX( LEFT_ALIGNMENT );
|
|
||||||
userField.addItemListener( this );
|
userField.addItemListener( this );
|
||||||
userField.addActionListener( this );
|
userField.addActionListener( this );
|
||||||
add( userField );
|
add( userField );
|
||||||
|
add( Components.stud() );
|
||||||
|
|
||||||
// Master Password
|
// Master Password
|
||||||
masterPasswordLabel = new JLabel( "Master Password:" );
|
masterPasswordLabel = Components.label( "Master Password:" );
|
||||||
masterPasswordLabel.setFont( Res.exoRegular().deriveFont( 12f ) );
|
|
||||||
masterPasswordLabel.setAlignmentX( LEFT_ALIGNMENT );
|
|
||||||
masterPasswordLabel.setHorizontalAlignment( SwingConstants.CENTER );
|
|
||||||
masterPasswordLabel.setVerticalAlignment( SwingConstants.BOTTOM );
|
|
||||||
add( masterPasswordLabel );
|
add( masterPasswordLabel );
|
||||||
|
|
||||||
masterPasswordField = new JPasswordField() {
|
masterPasswordField = Components.passwordField();
|
||||||
@Override
|
|
||||||
public Dimension getMaximumSize() {
|
|
||||||
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
masterPasswordField.setAlignmentX( LEFT_ALIGNMENT );
|
|
||||||
masterPasswordField.addActionListener( this );
|
masterPasswordField.addActionListener( this );
|
||||||
masterPasswordField.getDocument().addDocumentListener( this );
|
masterPasswordField.getDocument().addDocumentListener( this );
|
||||||
add( masterPasswordField );
|
add( masterPasswordField );
|
||||||
@@ -107,11 +91,12 @@ public class ModelAuthenticationPanel extends AuthenticationPanel implements Ite
|
|||||||
if (selectedIndex < 0)
|
if (selectedIndex < 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
ModelUser selectedUser = userField.getModel().getElementAt( selectedIndex );
|
return userField.getModel().getElementAt( selectedIndex );
|
||||||
if (selectedUser != null)
|
}
|
||||||
selectedUser.setMasterPassword( new String( masterPasswordField.getPassword() ) );
|
|
||||||
|
|
||||||
return selectedUser;
|
@Override
|
||||||
|
public char[] getMasterPassword() {
|
||||||
|
return masterPasswordField.getPassword();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -153,8 +138,8 @@ public class ModelAuthenticationPanel extends AuthenticationPanel implements Ite
|
|||||||
return FluentIterable.from( MPUserFileManager.get().getUsers() ).transform( new Function<MPUser, ModelUser>() {
|
return FluentIterable.from( MPUserFileManager.get().getUsers() ).transform( new Function<MPUser, ModelUser>() {
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public ModelUser apply(final MPUser model) {
|
public ModelUser apply(@Nullable final MPUser model) {
|
||||||
return new ModelUser( model );
|
return new ModelUser( Preconditions.checkNotNull( model ) );
|
||||||
}
|
}
|
||||||
} ).toArray( ModelUser.class );
|
} ).toArray( ModelUser.class );
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.lyndir.masterpassword.gui;
|
package com.lyndir.masterpassword.gui;
|
||||||
|
|
||||||
import com.lyndir.masterpassword.MPSiteType;
|
import com.lyndir.masterpassword.MPSiteType;
|
||||||
|
import com.lyndir.masterpassword.MasterKey;
|
||||||
import com.lyndir.masterpassword.model.*;
|
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() {
|
public int getSiteCounter() {
|
||||||
return model.getSiteCounter();
|
return model.getSiteCounter();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
package com.lyndir.masterpassword.gui;
|
package com.lyndir.masterpassword.gui;
|
||||||
|
|
||||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
import com.google.common.base.*;
|
||||||
|
|
||||||
import com.google.common.base.Function;
|
|
||||||
import com.google.common.collect.FluentIterable;
|
import com.google.common.collect.FluentIterable;
|
||||||
import com.lyndir.lhunath.opal.system.util.ObjectUtils;
|
|
||||||
import com.lyndir.masterpassword.MasterKey;
|
|
||||||
import com.lyndir.masterpassword.model.*;
|
import com.lyndir.masterpassword.model.*;
|
||||||
import javax.annotation.Nullable;
|
import java.util.Arrays;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import javax.annotation.*;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -17,7 +13,9 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
public class ModelUser extends User {
|
public class ModelUser extends User {
|
||||||
|
|
||||||
private final MPUser model;
|
private final MPUser model;
|
||||||
private String masterPassword;
|
|
||||||
|
@Nullable
|
||||||
|
private char[] masterPassword;
|
||||||
|
|
||||||
public ModelUser(MPUser model) {
|
public ModelUser(MPUser model) {
|
||||||
this.model = model;
|
this.model = model;
|
||||||
@@ -32,8 +30,9 @@ public class ModelUser extends User {
|
|||||||
return model.getFullName();
|
return model.getFullName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
protected String getMasterPassword() {
|
protected char[] getMasterPassword() {
|
||||||
return masterPassword;
|
return masterPassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,39 +46,29 @@ public class ModelUser extends User {
|
|||||||
MPUserFileManager.get().save();
|
MPUserFileManager.get().save();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMasterPassword(final String masterPassword) {
|
public void authenticate(final char[] masterPassword)
|
||||||
|
throws IncorrectMasterPasswordException {
|
||||||
|
putKey( model.authenticate( masterPassword ) );
|
||||||
this.masterPassword = masterPassword;
|
this.masterPassword = masterPassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
public MasterKey getKey() throws MasterKeyException {
|
|
||||||
MasterKey key = super.getKey();
|
|
||||||
if (!model.hasKeyID()) {
|
|
||||||
model.setKeyID( key.getKeyID() );
|
|
||||||
MPUserFileManager.get().save();
|
|
||||||
} else if (!model.hasKeyID( key.getKeyID() )) {
|
|
||||||
reset();
|
|
||||||
throw new MasterKeyException( strf( "Incorrect master password for user: %s", getFullName() ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reset() {
|
public void reset() {
|
||||||
super.reset();
|
super.reset();
|
||||||
|
|
||||||
|
if (masterPassword != null) {
|
||||||
|
Arrays.fill( masterPassword, (char) 0 );
|
||||||
masterPassword = null;
|
masterPassword = null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterable<Site> findSitesByName(final String query) {
|
public Iterable<Site> findSitesByName(final String query) {
|
||||||
return FluentIterable.from( model.findSitesByName( query ) ).transform( new Function<MPSiteResult, Site>() {
|
return FluentIterable.from( model.findSitesByName( query ) ).transform( new Function<MPSiteResult, Site>() {
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public Site apply(final MPSiteResult result) {
|
public Site apply(@Nullable final MPSiteResult result) {
|
||||||
return new ModelSite( result );
|
return new ModelSite( Preconditions.checkNotNull( result ) );
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
@@ -92,6 +81,7 @@ public class ModelUser extends User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean keySaved() {
|
public boolean keySaved() {
|
||||||
|
// TODO
|
||||||
return false;
|
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.collect.Iterables;
|
||||||
import com.google.common.util.concurrent.*;
|
import com.google.common.util.concurrent.*;
|
||||||
import com.lyndir.masterpassword.*;
|
import com.lyndir.masterpassword.*;
|
||||||
import com.lyndir.masterpassword.util.Components;
|
import com.lyndir.masterpassword.gui.util.Components;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.datatransfer.StringSelection;
|
import java.awt.datatransfer.StringSelection;
|
||||||
import java.awt.event.*;
|
import java.awt.event.*;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.border.*;
|
|
||||||
import javax.swing.event.*;
|
import javax.swing.event.*;
|
||||||
|
|
||||||
|
|
||||||
@@ -22,9 +22,11 @@ import javax.swing.event.*;
|
|||||||
public class PasswordFrame extends JFrame implements DocumentListener {
|
public class PasswordFrame extends JFrame implements DocumentListener {
|
||||||
|
|
||||||
private final User user;
|
private final User user;
|
||||||
|
private final Components.GradientPanel root;
|
||||||
private final JTextField siteNameField;
|
private final JTextField siteNameField;
|
||||||
private final JButton siteAddButton;
|
private final JButton siteAddButton;
|
||||||
private final JComboBox<MPSiteType> siteTypeField;
|
private final JComboBox<MPSiteType> siteTypeField;
|
||||||
|
private final JComboBox<MasterKey.Version> siteVersionField;
|
||||||
private final JSpinner siteCounterField;
|
private final JSpinner siteCounterField;
|
||||||
private final JPasswordField passwordField;
|
private final JPasswordField passwordField;
|
||||||
private final JLabel tipLabel;
|
private final JLabel tipLabel;
|
||||||
@@ -39,65 +41,33 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
|||||||
super( "Master Password" );
|
super( "Master Password" );
|
||||||
this.user = user;
|
this.user = user;
|
||||||
|
|
||||||
JLabel label;
|
|
||||||
|
|
||||||
setDefaultCloseOperation( DISPOSE_ON_CLOSE );
|
setDefaultCloseOperation( DISPOSE_ON_CLOSE );
|
||||||
setContentPane( new JPanel( new BorderLayout( 20, 20 ) ) {
|
setContentPane( root = Components.gradientPanel( new BorderLayout( 20, 20 ), Res.colors().frameBg() ) );
|
||||||
{
|
root.setBorder( BorderFactory.createEmptyBorder( 20, 20, 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 );
|
|
||||||
|
|
||||||
// Site
|
// Site
|
||||||
JPanel sitePanel = new JPanel();
|
JPanel sitePanel = Components.boxLayout( BoxLayout.PAGE_AXIS );
|
||||||
sitePanel.setLayout( new BoxLayout( sitePanel, BoxLayout.PAGE_AXIS ) );
|
sitePanel.setOpaque( true );
|
||||||
sitePanel.setBorder( new CompoundBorder( new EtchedBorder( EtchedBorder.RAISED ), new EmptyBorder( 8, 8, 8, 8 ) ) );
|
sitePanel.setBackground( Res.colors().controlBg() );
|
||||||
add( sitePanel, BorderLayout.CENTER );
|
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
|
// Site Name
|
||||||
sitePanel.add( label = new JLabel( "Site Name:", JLabel.LEADING ) );
|
sitePanel.add( Components.label( "Site Name:" ) );
|
||||||
label.setFont( Res.exoRegular().deriveFont( 12f ) );
|
|
||||||
label.setAlignmentX( LEFT_ALIGNMENT );
|
|
||||||
|
|
||||||
JComponent siteControls = Components.boxLayout( BoxLayout.LINE_AXIS, //
|
JComponent siteControls = Components.boxLayout( BoxLayout.LINE_AXIS, //
|
||||||
siteNameField = new JTextField() {
|
siteNameField = Components.textField(), Components.stud(),
|
||||||
@Override
|
siteAddButton = Components.button( "Add Site" ) );
|
||||||
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.getDocument().addDocumentListener( this );
|
siteNameField.getDocument().addDocumentListener( this );
|
||||||
siteNameField.addActionListener( new ActionListener() {
|
siteNameField.addActionListener( new ActionListener() {
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(final ActionEvent e) {
|
public void actionPerformed(final ActionEvent e) {
|
||||||
Futures.addCallback( updatePassword(), new FutureCallback<String>() {
|
Futures.addCallback( updatePassword(), new FutureCallback<String>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(final String sitePassword) {
|
public void onSuccess(@Nullable final String sitePassword) {
|
||||||
StringSelection clipboardContents = new StringSelection( sitePassword );
|
StringSelection clipboardContents = new StringSelection( sitePassword );
|
||||||
Toolkit.getDefaultToolkit().getSystemClipboard().setContents( clipboardContents, null );
|
Toolkit.getDefaultToolkit().getSystemClipboard().setContents( clipboardContents, null );
|
||||||
|
|
||||||
@@ -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
|
// Site Type & Counter
|
||||||
MPSiteType[] types = Iterables.toArray( MPSiteType.forClass( MPSiteTypeClass.Generated ), MPSiteType.class );
|
MPSiteType[] types = Iterables.toArray( MPSiteType.forClass( MPSiteTypeClass.Generated ), MPSiteType.class );
|
||||||
JComponent siteSettings = Components.boxLayout( BoxLayout.LINE_AXIS, //
|
JComponent siteSettings = Components.boxLayout( BoxLayout.LINE_AXIS, //
|
||||||
siteTypeField = new JComboBox<>( types ), //
|
siteTypeField = Components.comboBox( types ), //
|
||||||
siteCounterField = new JSpinner(
|
Components.stud(), //
|
||||||
new SpinnerNumberModel( 1, 1, Integer.MAX_VALUE, 1 ) ) {
|
siteVersionField = Components.comboBox( MasterKey.Version.values() ), //
|
||||||
@Override
|
Components.stud(), //
|
||||||
public Dimension getMaximumSize() {
|
siteCounterField = Components.spinner(
|
||||||
return new Dimension( 20, getPreferredSize().height );
|
new SpinnerNumberModel( 1, 1, Integer.MAX_VALUE, 1 ) ) );
|
||||||
}
|
|
||||||
} );
|
|
||||||
siteSettings.setAlignmentX( LEFT_ALIGNMENT );
|
|
||||||
sitePanel.add( siteSettings );
|
sitePanel.add( siteSettings );
|
||||||
siteTypeField.setFont( Res.sourceCodeProRegular().deriveFont( 12f ) );
|
siteTypeField.setFont( Res.valueFont().deriveFont( 12f ) );
|
||||||
siteTypeField.setAlignmentX( LEFT_ALIGNMENT );
|
|
||||||
siteTypeField.setAlignmentY( CENTER_ALIGNMENT );
|
|
||||||
siteTypeField.setSelectedItem( MPSiteType.GeneratedLong );
|
siteTypeField.setSelectedItem( MPSiteType.GeneratedLong );
|
||||||
siteTypeField.addItemListener( new ItemListener() {
|
siteTypeField.addItemListener( new ItemListener() {
|
||||||
@Override
|
@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.setAlignmentX( RIGHT_ALIGNMENT );
|
||||||
siteCounterField.setAlignmentY( CENTER_ALIGNMENT );
|
|
||||||
siteCounterField.addChangeListener( new ChangeListener() {
|
siteCounterField.addChangeListener( new ChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void stateChanged(final ChangeEvent e) {
|
public void stateChanged(final ChangeEvent e) {
|
||||||
@@ -154,10 +138,8 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
|||||||
} );
|
} );
|
||||||
|
|
||||||
// Mask
|
// Mask
|
||||||
maskPasswordField = new JCheckBox();
|
maskPasswordField = Components.checkBox( "Hide Password" );
|
||||||
maskPasswordField.setFont( Res.exoRegular().deriveFont( 12f ) );
|
|
||||||
maskPasswordField.setAlignmentX( Component.CENTER_ALIGNMENT );
|
maskPasswordField.setAlignmentX( Component.CENTER_ALIGNMENT );
|
||||||
maskPasswordField.setText( "Hide Password" );
|
|
||||||
maskPasswordField.setSelected( true );
|
maskPasswordField.setSelected( true );
|
||||||
maskPasswordField.addItemListener( new ItemListener() {
|
maskPasswordField.addItemListener( new ItemListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -168,24 +150,27 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
|||||||
|
|
||||||
// Password
|
// Password
|
||||||
passwordField = new JPasswordField();
|
passwordField = new JPasswordField();
|
||||||
passwordField.setHorizontalAlignment( JTextField.CENTER );
|
|
||||||
passwordField.setAlignmentX( Component.CENTER_ALIGNMENT );
|
passwordField.setAlignmentX( Component.CENTER_ALIGNMENT );
|
||||||
passwordField.setEditable( false );
|
passwordField.setEditable( false );
|
||||||
|
passwordField.setHorizontalAlignment( JTextField.CENTER );
|
||||||
passwordField.putClientProperty( "JPasswordField.cutCopyAllowed", true );
|
passwordField.putClientProperty( "JPasswordField.cutCopyAllowed", true );
|
||||||
|
passwordField.setBorder( null );
|
||||||
passwordEchoChar = passwordField.getEchoChar();
|
passwordEchoChar = passwordField.getEchoChar();
|
||||||
passwordEchoFont = passwordField.getFont().deriveFont( 40f );
|
passwordEchoFont = passwordField.getFont().deriveFont( 40f );
|
||||||
updateMask();
|
updateMask();
|
||||||
|
|
||||||
// Tip
|
// Tip
|
||||||
tipLabel = new JLabel( " ", JLabel.CENTER );
|
tipLabel = Components.label( " ", JLabel.CENTER );
|
||||||
tipLabel.setFont( Res.exoRegular().deriveFont( 9f ) );
|
|
||||||
tipLabel.setAlignmentX( Component.CENTER_ALIGNMENT );
|
tipLabel.setAlignmentX( Component.CENTER_ALIGNMENT );
|
||||||
|
JPanel passwordContainer = Components.boxLayout( BoxLayout.PAGE_AXIS, maskPasswordField, passwordField, tipLabel );
|
||||||
add( Components.boxLayout( BoxLayout.PAGE_AXIS, maskPasswordField, passwordField, tipLabel ), BorderLayout.SOUTH );
|
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();
|
pack();
|
||||||
setMinimumSize( getSize() );
|
setMinimumSize( new Dimension( Math.max( 600, getPreferredSize().width ), Math.max( 300, getPreferredSize().height ) ) );
|
||||||
setPreferredSize( new Dimension( 600, getSize().height ) );
|
|
||||||
pack();
|
pack();
|
||||||
|
|
||||||
setLocationByPlatform( true );
|
setLocationByPlatform( true );
|
||||||
@@ -194,7 +179,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
|||||||
|
|
||||||
private void updateMask() {
|
private void updateMask() {
|
||||||
passwordField.setEchoChar( maskPasswordField.isSelected()? passwordEchoChar: (char) 0 );
|
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
|
@Nonnull
|
||||||
@@ -203,19 +188,22 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
|||||||
final String siteNameQuery = siteNameField.getText();
|
final String siteNameQuery = siteNameField.getText();
|
||||||
if (updatingUI)
|
if (updatingUI)
|
||||||
return Futures.immediateCancelledFuture();
|
return Futures.immediateCancelledFuture();
|
||||||
if (siteNameQuery == null || siteNameQuery.isEmpty() || !user.hasKey()) {
|
if (siteNameQuery == null || siteNameQuery.isEmpty() || !user.isKeyAvailable()) {
|
||||||
tipLabel.setText( null );
|
tipLabel.setText( null );
|
||||||
passwordField.setText( null );
|
passwordField.setText( null );
|
||||||
return Futures.immediateCancelledFuture();
|
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 int siteCounter = (Integer) siteCounterField.getValue();
|
||||||
final Site site = currentSite != null && currentSite.getSiteName().equals( siteNameQuery )? currentSite
|
final Site site = currentSite != null && currentSite.getSiteName().equals( siteNameQuery )? currentSite
|
||||||
: Iterables.getFirst( user.findSitesByName( siteNameQuery ), new IncognitoSite( siteNameQuery, siteType, siteCounter ) );
|
: Iterables.getFirst( user.findSitesByName( siteNameQuery ),
|
||||||
|
new IncognitoSite( siteNameQuery, siteType, siteCounter, siteVersion ) );
|
||||||
assert site != null;
|
assert site != null;
|
||||||
if (site == currentSite) {
|
if (site == currentSite) {
|
||||||
site.setSiteType( siteType );
|
site.setSiteType( siteType );
|
||||||
|
site.setAlgorithmVersion( siteVersion );
|
||||||
site.setSiteCounter( siteCounter );
|
site.setSiteCounter( siteCounter );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,12 +211,13 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
|||||||
@Override
|
@Override
|
||||||
public String call()
|
public String call()
|
||||||
throws Exception {
|
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>() {
|
Futures.addCallback( passwordFuture, new FutureCallback<String>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(final String sitePassword) {
|
public void onSuccess(@Nullable final String sitePassword) {
|
||||||
SwingUtilities.invokeLater( new Runnable() {
|
SwingUtilities.invokeLater( new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
@@ -236,6 +225,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
|||||||
currentSite = site;
|
currentSite = site;
|
||||||
siteAddButton.setVisible( user instanceof ModelUser && !(currentSite instanceof ModelSite) );
|
siteAddButton.setVisible( user instanceof ModelUser && !(currentSite instanceof ModelSite) );
|
||||||
siteTypeField.setSelectedItem( currentSite.getSiteType() );
|
siteTypeField.setSelectedItem( currentSite.getSiteType() );
|
||||||
|
siteVersionField.setSelectedItem( currentSite.getAlgorithmVersion() );
|
||||||
siteCounterField.setValue( currentSite.getSiteCounter() );
|
siteCounterField.setValue( currentSite.getSiteCounter() );
|
||||||
siteNameField.setText( currentSite.getSiteName() );
|
siteNameField.setText( currentSite.getSiteName() );
|
||||||
if (siteNameField.getText().startsWith( siteNameQuery ))
|
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 static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||||
|
|
||||||
import com.google.common.base.Throwables;
|
import com.google.common.base.Throwables;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.io.Resources;
|
import com.google.common.io.Resources;
|
||||||
import com.google.common.util.concurrent.*;
|
import com.google.common.util.concurrent.*;
|
||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
@@ -11,7 +12,9 @@ import java.awt.*;
|
|||||||
import java.awt.event.*;
|
import java.awt.event.*;
|
||||||
import java.awt.image.ImageObserver;
|
import java.awt.image.ImageObserver;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.lang.ref.SoftReference;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
import java.util.regex.Matcher;
|
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 WeakHashMap<Window, ExecutorService> executorByWindow = new WeakHashMap<>();
|
||||||
private static final Logger logger = Logger.get( Res.class );
|
private static final Logger logger = Logger.get( Res.class );
|
||||||
|
private static final Colors colors = new Colors();
|
||||||
private static Font sourceCodeProRegular;
|
|
||||||
private static Font sourceCodeProBlack;
|
|
||||||
private static Font exoBold;
|
|
||||||
private static Font exoExtraBold;
|
|
||||||
private static Font exoRegular;
|
|
||||||
private static Font exoThin;
|
|
||||||
|
|
||||||
public static Future<?> execute(final Window host, final Runnable job) {
|
public static Future<?> execute(final Window host, final Runnable job) {
|
||||||
return getExecutor( host ).submit( new Runnable() {
|
return getExecutor( host ).submit( new Runnable() {
|
||||||
@@ -100,64 +97,84 @@ public abstract class Res {
|
|||||||
return 19;
|
return 19;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Font controlFont() {
|
||||||
|
return arimoRegular();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Font valueFont() {
|
||||||
|
return sourceSansProRegular();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Font bigValueFont() {
|
||||||
|
return sourceSansProBlack();
|
||||||
|
}
|
||||||
|
|
||||||
public static Font sourceCodeProRegular() {
|
public static Font sourceCodeProRegular() {
|
||||||
try {
|
return font( "fonts/SourceCodePro-Regular.otf" );
|
||||||
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 );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Font sourceCodeProBlack() {
|
public static Font sourceCodeProBlack() {
|
||||||
try {
|
return font( "fonts/SourceCodePro-Bold.otf" );
|
||||||
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 );
|
public static Font sourceSansProRegular() {
|
||||||
|
return font( "fonts/SourceSansPro-Regular.otf" );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Font sourceSansProBlack() {
|
||||||
|
return font( "fonts/SourceSansPro-Bold.otf" );
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Font exoBold() {
|
public static Font exoBold() {
|
||||||
try {
|
return font( "fonts/Exo2.0-Bold.otf" );
|
||||||
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 );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Font exoExtraBold() {
|
public static Font exoExtraBold() {
|
||||||
try {
|
return font( "fonts/Exo2.0-ExtraBold.otf" );
|
||||||
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 );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Font exoRegular() {
|
public static Font exoRegular() {
|
||||||
try {
|
return font( "fonts/Exo2.0-Regular.otf" );
|
||||||
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 );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Font exoThin() {
|
public static Font exoThin() {
|
||||||
|
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 {
|
try {
|
||||||
return exoThin != null? exoThin: (exoThin =
|
fontsByResourceName.put( fontResourceName, new SoftReference<>(
|
||||||
Font.createFont( Font.TRUETYPE_FONT, Resources.getResource( "fonts/Exo2.0-Thin.otf" ).openStream() ));
|
font = Font.createFont( Font.TRUETYPE_FONT, Resources.getResource( fontResourceName ).openStream() ) ) );
|
||||||
}
|
}
|
||||||
catch (FontFormatException | IOException e) {
|
catch (FontFormatException | IOException e) {
|
||||||
throw Throwables.propagate( e );
|
throw Throwables.propagate( e );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return font;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Colors colors() {
|
||||||
|
return colors;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class RetinaIcon extends ImageIcon {
|
private static final class RetinaIcon extends ImageIcon {
|
||||||
@@ -216,4 +233,24 @@ public abstract class Res {
|
|||||||
g2d.dispose();
|
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 static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
||||||
|
|
||||||
import com.lyndir.masterpassword.MPSiteType;
|
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 void setSiteType(final MPSiteType siteType);
|
||||||
|
|
||||||
|
public abstract MasterKey.Version getAlgorithmVersion();
|
||||||
|
|
||||||
|
public abstract void setAlgorithmVersion(final MasterKey.Version algorithmVersion);
|
||||||
|
|
||||||
public abstract int getSiteCounter();
|
public abstract int getSiteCounter();
|
||||||
|
|
||||||
public abstract void setSiteCounter(final int siteCounter);
|
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 static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||||
|
|
||||||
import com.lyndir.masterpassword.util.Components;
|
import com.lyndir.masterpassword.gui.util.Components;
|
||||||
|
import com.lyndir.masterpassword.model.IncorrectMasterPasswordException;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.*;
|
import java.awt.event.*;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.border.*;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -15,7 +15,7 @@ import javax.swing.border.*;
|
|||||||
public class UnlockFrame extends JFrame {
|
public class UnlockFrame extends JFrame {
|
||||||
|
|
||||||
private final SignInCallback signInCallback;
|
private final SignInCallback signInCallback;
|
||||||
private final JPanel root;
|
private final Components.GradientPanel root;
|
||||||
private final JButton signInButton;
|
private final JButton signInButton;
|
||||||
private final JPanel authenticationContainer;
|
private final JPanel authenticationContainer;
|
||||||
private AuthenticationPanel authenticationPanel;
|
private AuthenticationPanel authenticationPanel;
|
||||||
@@ -28,19 +28,20 @@ public class UnlockFrame extends JFrame {
|
|||||||
this.signInCallback = signInCallback;
|
this.signInCallback = signInCallback;
|
||||||
|
|
||||||
setDefaultCloseOperation( DISPOSE_ON_CLOSE );
|
setDefaultCloseOperation( DISPOSE_ON_CLOSE );
|
||||||
setContentPane( root = new JPanel( new BorderLayout( 20, 20 ) ) );
|
setContentPane( root = Components.gradientPanel( new BorderLayout( 20, 20 ), Res.colors().frameBg() ) );
|
||||||
root.setBorder( new EmptyBorder( 20, 20, 20, 20 ) );
|
root.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
|
||||||
|
|
||||||
authenticationContainer = new JPanel();
|
authenticationContainer = Components.boxLayout( BoxLayout.PAGE_AXIS );
|
||||||
authenticationContainer.setLayout( new BoxLayout( authenticationContainer, BoxLayout.PAGE_AXIS ) );
|
authenticationContainer.setOpaque( true );
|
||||||
authenticationContainer.setBorder( new CompoundBorder( new EtchedBorder( EtchedBorder.RAISED ), new EmptyBorder( 8, 8, 8, 8 ) ) );
|
authenticationContainer.setBackground( Res.colors().controlBg() );
|
||||||
add( authenticationContainer );
|
authenticationContainer.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
|
||||||
|
add( Components.borderPanel( authenticationContainer, BorderFactory.createRaisedBevelBorder(), Res.colors().frameBg() ) );
|
||||||
|
|
||||||
// Sign In
|
// Sign In
|
||||||
root.add( Components.boxLayout( BoxLayout.LINE_AXIS, Box.createGlue(), signInButton = new JButton( "Sign In" ), Box.createGlue() ),
|
JPanel signInBox = Components.boxLayout( BoxLayout.LINE_AXIS, Box.createGlue(), signInButton = Components.button( "Sign In" ),
|
||||||
BorderLayout.SOUTH );
|
Box.createGlue() );
|
||||||
signInButton.setFont( Res.exoRegular().deriveFont( 12f ) );
|
signInBox.setBackground( null );
|
||||||
signInButton.setAlignmentX( LEFT_ALIGNMENT );
|
root.add( signInBox, BorderLayout.SOUTH );
|
||||||
signInButton.addActionListener( new AbstractAction() {
|
signInButton.addActionListener( new AbstractAction() {
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(final ActionEvent e) {
|
public void actionPerformed(final ActionEvent e) {
|
||||||
@@ -55,10 +56,8 @@ public class UnlockFrame extends JFrame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void repack() {
|
protected void repack() {
|
||||||
setPreferredSize( null );
|
|
||||||
pack();
|
pack();
|
||||||
setMinimumSize( getSize() );
|
setMinimumSize( new Dimension( Math.max( 300, getPreferredSize().width ), Math.max( 300, getPreferredSize().height ) ) );
|
||||||
setPreferredSize( new Dimension( 300, 300 ) );
|
|
||||||
pack();
|
pack();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,11 +70,10 @@ public class UnlockFrame extends JFrame {
|
|||||||
authenticationPanel = new ModelAuthenticationPanel( this );
|
authenticationPanel = new ModelAuthenticationPanel( this );
|
||||||
}
|
}
|
||||||
authenticationPanel.updateUser( false );
|
authenticationPanel.updateUser( false );
|
||||||
authenticationContainer.add( authenticationPanel, BorderLayout.CENTER );
|
authenticationContainer.add( authenticationPanel );
|
||||||
|
authenticationContainer.add( Components.stud() );
|
||||||
|
|
||||||
final JCheckBox incognitoCheckBox = new JCheckBox( "Incognito" );
|
final JCheckBox incognitoCheckBox = Components.checkBox( "Incognito" );
|
||||||
incognitoCheckBox.setFont( Res.exoRegular().deriveFont( 12f ) );
|
|
||||||
incognitoCheckBox.setAlignmentX( LEFT_ALIGNMENT );
|
|
||||||
incognitoCheckBox.setSelected( incognito );
|
incognitoCheckBox.setSelected( incognito );
|
||||||
incognitoCheckBox.addItemListener( new ItemListener() {
|
incognitoCheckBox.addItemListener( new ItemListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -91,7 +89,6 @@ public class UnlockFrame extends JFrame {
|
|||||||
} );
|
} );
|
||||||
|
|
||||||
JComponent toolsPanel = Components.boxLayout( BoxLayout.LINE_AXIS, incognitoCheckBox, Box.createGlue() );
|
JComponent toolsPanel = Components.boxLayout( BoxLayout.LINE_AXIS, incognitoCheckBox, Box.createGlue() );
|
||||||
toolsPanel.setAlignmentX( Component.LEFT_ALIGNMENT );
|
|
||||||
authenticationContainer.add( toolsPanel );
|
authenticationContainer.add( toolsPanel );
|
||||||
for (JButton button : authenticationPanel.getButtons()) {
|
for (JButton button : authenticationPanel.getButtons()) {
|
||||||
button.setMargin( new Insets( 0, 0, 0, 0 ) );
|
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;
|
this.user = user;
|
||||||
checkSignIn();
|
checkSignIn();
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean 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 );
|
signInButton.setEnabled( enabled );
|
||||||
|
|
||||||
return enabled;
|
return enabled;
|
||||||
@@ -137,16 +134,22 @@ public class UnlockFrame extends JFrame {
|
|||||||
Res.execute( this, new Runnable() {
|
Res.execute( this, new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
final boolean success = signInCallback.signedIn( user );
|
try {
|
||||||
|
user.authenticate( authenticationPanel.getMasterPassword() );
|
||||||
|
|
||||||
SwingUtilities.invokeLater( new Runnable() {
|
SwingUtilities.invokeLater( new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (success) {
|
signInCallback.signedIn( user );
|
||||||
dispose();
|
dispose();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
catch (final IncorrectMasterPasswordException e) {
|
||||||
|
SwingUtilities.invokeLater( new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
JOptionPane.showMessageDialog( null, e.getLocalizedMessage(), "Sign In Failed", JOptionPane.ERROR_MESSAGE );
|
||||||
authenticationPanel.reset();
|
authenticationPanel.reset();
|
||||||
signInButton.setText( "Sign In" );
|
signInButton.setText( "Sign In" );
|
||||||
for (JComponent signInComponent : signInComponents)
|
for (JComponent signInComponent : signInComponents)
|
||||||
@@ -155,11 +158,13 @@ public class UnlockFrame extends JFrame {
|
|||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SignInCallback {
|
interface SignInCallback {
|
||||||
|
|
||||||
boolean signedIn(User user);
|
void signedIn(User user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
package com.lyndir.masterpassword.gui;
|
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.MasterKey;
|
||||||
import com.lyndir.masterpassword.model.MPUser;
|
import com.lyndir.masterpassword.model.IncorrectMasterPasswordException;
|
||||||
import java.security.KeyException;
|
import java.util.EnumMap;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -14,38 +15,47 @@ import javax.annotation.Nonnull;
|
|||||||
*/
|
*/
|
||||||
public abstract class User {
|
public abstract class User {
|
||||||
|
|
||||||
private MasterKey key;
|
@Nonnull
|
||||||
|
private final EnumMap<MasterKey.Version, MasterKey> keyByVersion = Maps.newEnumMap( MasterKey.Version.class );
|
||||||
|
|
||||||
public abstract String getFullName();
|
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() {
|
public int getAvatar() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasKey() {
|
public boolean isKeyAvailable() {
|
||||||
String masterPassword = getMasterPassword();
|
return getMasterPassword() != null;
|
||||||
return key != null || (masterPassword != null && !masterPassword.isEmpty());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public MasterKey getKey() throws MasterKeyException {
|
public MasterKey getKey(MasterKey.Version algorithmVersion) {
|
||||||
if (key == null) {
|
char[] masterPassword = Preconditions.checkNotNull( getMasterPassword(), "User is not authenticated: " + getFullName() );
|
||||||
String masterPassword = getMasterPassword();
|
|
||||||
if (masterPassword == null || masterPassword.isEmpty()) {
|
|
||||||
reset();
|
|
||||||
throw new MasterKeyException( strf( "Master password unknown for user: %s", 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;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void putKey(MasterKey masterKey) {
|
||||||
|
MasterKey oldKey = keyByVersion.put( masterKey.getAlgorithmVersion(), masterKey );
|
||||||
|
if (oldKey != null)
|
||||||
|
oldKey.invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
public void reset() {
|
public void reset() {
|
||||||
key = null;
|
for (MasterKey key : keyByVersion.values())
|
||||||
|
key.invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract Iterable<Site> findSitesByName(final String siteName);
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||