2
0

Many UI improvements to the Java GUI.

This commit is contained in:
Maarten Billemont 2015-02-04 11:25:18 -05:00
parent 5b08149ca6
commit 78c593fc08
41 changed files with 785 additions and 297 deletions

View File

@ -24,12 +24,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 -->

View File

@ -143,7 +143,7 @@ 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.
*/ */

View File

@ -61,7 +61,7 @@ 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.
*/ */

View File

@ -1,145 +1,155 @@
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 String masterPassword) {
return create( Version.CURRENT, fullName, masterPassword );
}
public static MasterKey create(Version version, final String fullName, final String 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 String masterPassword);
protected abstract Version getAlgorithm();
@NotNull
public String getFullName() { public String getFullName() {
return fullName; return fullName;
} }
@Nonnull
protected byte[] getMasterKey() {
return Preconditions.checkNotNull( masterKey );
}
public byte[] getKeyID() { public byte[] getKeyID() {
Preconditions.checkState( valid ); return idForBytes( getMasterKey() );
return idForBytes( masterKey );
} }
public String encode(final String siteName, final MPSiteType siteType, int siteCounter, final MPSiteVariant siteVariant, public abstract String encode(final String siteName, final MPSiteType siteType, int siteCounter, final MPSiteVariant siteVariant,
@Nullable final String siteContext) { @Nullable final String siteContext);
Preconditions.checkState( valid );
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( 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 String masterPassword) {
return ByteBuffer.allocate( MP_intLen / Byte.SIZE ).order( MP_byteOrder ).putInt( integer ).array(); invalidate();
logger.trc( "masterPassword: %s", masterPassword );
long start = System.currentTimeMillis();
masterKey = deriveKey( masterPassword );
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 );
}
} }
} }

View File

@ -0,0 +1,130 @@
package com.lyndir.masterpassword;
import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
import com.google.common.primitives.Bytes;
import com.lambdaworks.crypto.SCrypt;
import com.lyndir.lhunath.opal.system.*;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import 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
protected Version getAlgorithm() {
return Version.V0;
}
@Nullable
@Override
protected byte[] deriveKey(final String 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 ) ) );
try {
return SCrypt.scrypt( masterPassword.getBytes( MP_charset ), masterKeySalt, MP_N, MP_r, MP_p, MP_dkLen );
}
catch (GeneralSecurityException e) {
logger.bug( e );
return null;
}
}
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( getMasterKey(), sitePasswordInfo );
logger.trc( "sitePasswordSeed ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordSeed ) ) );
Preconditions.checkState( sitePasswordSeed.length > 0 );
int templateIndex = sitePasswordSeed[0];
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];
MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i );
char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex );
logger.trc( "class %c, index %d (0x%02X) -> character: %c", characterClass.getIdentifier(), characterIndex,
sitePasswordSeed[i + 1], passwordCharacter );
password.append( passwordCharacter );
}
return password.toString();
}
@Override
protected byte[] bytesForInt(final int integer) {
return ByteBuffer.allocate( MP_intLen / Byte.SIZE ).order( MP_byteOrder ).putInt( integer ).array();
}
@Override
protected byte[] idForBytes(final byte[] bytes) {
return MP_hash.of( bytes );
}
}

View File

@ -0,0 +1,88 @@
package com.lyndir.masterpassword;
import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
import com.google.common.primitives.Bytes;
import com.lambdaworks.crypto.SCrypt;
import com.lyndir.lhunath.opal.system.*;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import 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
protected Version getAlgorithm() {
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( getMasterKey(), sitePasswordInfo );
logger.trc( "sitePasswordSeed ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordSeed ) ) );
Preconditions.checkState( sitePasswordSeed.length > 0 );
int templateIndex = sitePasswordSeed[0] & 0xFF; // Mask the integer's sign.
MPTemplate template = siteType.getTemplateAtRollingIndex( templateIndex );
logger.trc( "type %s, template: %s", siteType, template.getTemplateString() );
StringBuilder password = new StringBuilder( template.length() );
for (int i = 0; i < template.length(); ++i) {
int characterIndex = sitePasswordSeed[i + 1] & 0xFF; // Mask the integer's sign.
MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i );
char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex );
logger.trc( "class %c, index %d (0x%02X) -> character: %c", characterClass.getIdentifier(), characterIndex,
sitePasswordSeed[i + 1], passwordCharacter );
password.append( passwordCharacter );
}
return password.toString();
}
}

View File

@ -0,0 +1,81 @@
package com.lyndir.masterpassword;
import com.google.common.base.Preconditions;
import com.google.common.primitives.Bytes;
import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.lhunath.opal.system.logging.Logger;
import javax.annotation.Nullable;
/**
* bugs:
* - miscounted the byte-length fromInt multi-byte full names.
*
* @author lhunath, 2014-08-30
*/
public class MasterKeyV2 extends MasterKeyV0 {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MasterKeyV2.class );
public MasterKeyV2(final String fullName) {
super( fullName );
}
@Override
protected Version getAlgorithm() {
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( getMasterKey(), sitePasswordInfo );
logger.trc( "sitePasswordSeed ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordSeed ) ) );
Preconditions.checkState( sitePasswordSeed.length > 0 );
int templateIndex = sitePasswordSeed[0] & 0xFF; // Mask the integer's sign.
MPTemplate template = siteType.getTemplateAtRollingIndex( templateIndex );
logger.trc( "type %s, template: %s", siteType, template.getTemplateString() );
StringBuilder password = new StringBuilder( template.length() );
for (int i = 0; i < template.length(); ++i) {
int characterIndex = sitePasswordSeed[i + 1] & 0xFF; // Mask the integer's sign.
MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i );
char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex );
logger.trc( "class %c, index %d (0x%02X) -> character: %c", characterClass.getIdentifier(), characterIndex,
sitePasswordSeed[i + 1], passwordCharacter );
password.append( passwordCharacter );
}
return password.toString();
}
}

View File

@ -0,0 +1,51 @@
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.security.GeneralSecurityException;
import javax.annotation.Nullable;
/**
* bugs:
* - no known issues.
*
* @author lhunath, 2014-08-30
*/
public class MasterKeyV3 extends MasterKeyV0 {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MasterKeyV3.class );
public MasterKeyV3(final String fullName) {
super( fullName );
}
@Override
protected Version getAlgorithm() {
return Version.V3;
}
@Nullable
@Override
protected byte[] deriveKey(final String 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 ) ) );
try {
return SCrypt.scrypt( masterPassword.getBytes( MP_charset ), masterKeySalt, MP_N, MP_r, MP_p, MP_dkLen );
}
catch (GeneralSecurityException e) {
logger.bug( e );
return null;
}
}
}

View File

@ -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() );

View File

@ -159,7 +159,7 @@ public class EmergencyActivity extends Activity {
public MasterKey call() public MasterKey call()
throws Exception { throws Exception {
try { try {
return new MasterKey( userName, masterPassword ); return MasterKey.create( userName, masterPassword );
} }
catch (RuntimeException e) { catch (RuntimeException e) {
sitePasswordField.setText( "" ); sitePasswordField.setText( "" );

View File

@ -183,6 +183,6 @@ public class CLI {
} }
// 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 ) );
} }
} }

View File

@ -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() {

View File

@ -1,5 +1,6 @@
package com.lyndir.masterpassword.gui; package com.lyndir.masterpassword.gui;
import com.lyndir.masterpassword.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,39 +21,30 @@ 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 ) );
JLabel fullNameLabel = Components.label( "Full Name:" );
fullNameLabel.setAlignmentX( LEFT_ALIGNMENT ); fullNameLabel.setAlignmentX( LEFT_ALIGNMENT );
fullNameLabel.setHorizontalAlignment( SwingConstants.CENTER ); fullNameLabel.setHorizontalAlignment( SwingConstants.CENTER );
fullNameLabel.setVerticalAlignment( SwingConstants.BOTTOM ); 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.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.setAlignmentX( LEFT_ALIGNMENT );
masterPasswordLabel.setHorizontalAlignment( SwingConstants.CENTER ); masterPasswordLabel.setHorizontalAlignment( SwingConstants.CENTER );
masterPasswordLabel.setVerticalAlignment( SwingConstants.BOTTOM ); 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.setAlignmentX( LEFT_ALIGNMENT );
masterPasswordField.addActionListener( this ); masterPasswordField.addActionListener( this );
masterPasswordField.getDocument().addDocumentListener( this ); masterPasswordField.getDocument().addDocumentListener( this );

View File

@ -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.MasterKey;
/** /**
@ -25,6 +26,11 @@ public class IncognitoUser extends User {
return masterPassword; return masterPassword;
} }
@Override
public MasterKey.Version getAlgorithmVersion() {
return MasterKey.Version.CURRENT;
}
@Override @Override
public Iterable<Site> findSitesByName(final String siteName) { public Iterable<Site> findSitesByName(final String siteName) {
return ImmutableList.of(); return ImmutableList.of();

View File

@ -5,6 +5,7 @@ import com.google.common.collect.*;
import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.masterpassword.model.MPUser; import com.lyndir.masterpassword.model.MPUser;
import com.lyndir.masterpassword.model.MPUserFileManager; import com.lyndir.masterpassword.model.MPUserFileManager;
import com.lyndir.masterpassword.util.Components;
import java.awt.*; import java.awt.*;
import java.awt.event.*; import java.awt.event.*;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -27,6 +28,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,8 +43,7 @@ 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.setAlignmentX( LEFT_ALIGNMENT );
userLabel.setHorizontalAlignment( SwingConstants.CENTER ); userLabel.setHorizontalAlignment( SwingConstants.CENTER );
userLabel.setVerticalAlignment( SwingConstants.BOTTOM ); userLabel.setVerticalAlignment( SwingConstants.BOTTOM );
@ -54,26 +55,21 @@ public class ModelAuthenticationPanel extends AuthenticationPanel implements Ite
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height ); return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
} }
}; };
userField.setFont( Res.sourceCodeProRegular().deriveFont( 12f ) ); userField.setFont( Res.valueFont().deriveFont( 12f ) );
userField.setAlignmentX( LEFT_ALIGNMENT ); 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.setAlignmentX( LEFT_ALIGNMENT );
masterPasswordLabel.setHorizontalAlignment( SwingConstants.CENTER ); masterPasswordLabel.setHorizontalAlignment( SwingConstants.CENTER );
masterPasswordLabel.setVerticalAlignment( SwingConstants.BOTTOM ); 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.setAlignmentX( LEFT_ALIGNMENT );
masterPasswordField.addActionListener( this ); masterPasswordField.addActionListener( this );
masterPasswordField.getDocument().addDocumentListener( this ); masterPasswordField.getDocument().addDocumentListener( this );

View File

@ -37,6 +37,11 @@ public class ModelUser extends User {
return masterPassword; return masterPassword;
} }
@Override
public MasterKey.Version getAlgorithmVersion() {
return model.getAlgorithmVersion();
}
@Override @Override
public int getAvatar() { public int getAvatar() {
return model.getAvatar(); return model.getAvatar();

View File

@ -12,7 +12,6 @@ import java.awt.event.*;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*; import javax.swing.event.*;
@ -44,52 +43,28 @@ public class PasswordFrame extends JFrame implements DocumentListener {
setDefaultCloseOperation( DISPOSE_ON_CLOSE ); setDefaultCloseOperation( DISPOSE_ON_CLOSE );
setContentPane( new JPanel( new BorderLayout( 20, 20 ) ) { setContentPane( new JPanel( new BorderLayout( 20, 20 ) ) {
{ {
setBorder( new EmptyBorder( 20, 20, 20, 20 ) ); setBackground( Res.colors().frameBg() );
setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
} }
} ); } );
// User // User
add( label = new JLabel( strf( "Generating passwords for: %s", user.getFullName() ) ), BorderLayout.NORTH ); add( label = Components.label( strf( "Generating passwords for: %s", user.getFullName() ) ), BorderLayout.NORTH );
label.setFont( Res.exoRegular().deriveFont( 12f ) );
label.setAlignmentX( LEFT_ALIGNMENT ); 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.setBackground( Res.colors().controlBg() );
sitePanel.setBorder( new CompoundBorder( new EtchedBorder( EtchedBorder.RAISED ), new EmptyBorder( 8, 8, 8, 8 ) ) ); sitePanel.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
add( sitePanel, BorderLayout.CENTER ); add( Components.bordered( sitePanel, BorderFactory.createRaisedBevelBorder(), Res.colors().frameBg() ), BorderLayout.CENTER );
// Site Name // Site Name
sitePanel.add( label = new JLabel( "Site Name:", JLabel.LEADING ) ); sitePanel.add( label = Components.label( "Site Name:" ) );
label.setFont( Res.exoRegular().deriveFont( 12f ) );
label.setAlignmentX( LEFT_ALIGNMENT ); 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.setAlignmentX( LEFT_ALIGNMENT );
siteNameField.getDocument().addDocumentListener( this ); siteNameField.getDocument().addDocumentListener( this );
siteNameField.addActionListener( new ActionListener() { siteNameField.addActionListener( new ActionListener() {
@ -118,21 +93,31 @@ public class PasswordFrame extends JFrame implements DocumentListener {
} ); } );
} }
} ); } );
siteAddButton.setVisible( false );
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.setBackground( null );
siteControls.setAlignmentX( LEFT_ALIGNMENT );
sitePanel.add( siteControls );
// 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 = new JComboBox<>( types ), //
siteCounterField = new JSpinner( Components.stud(), //
new SpinnerNumberModel( 1, 1, Integer.MAX_VALUE, 1 ) ) { siteCounterField = Components.spinner(
@Override new SpinnerNumberModel( 1, 1, Integer.MAX_VALUE, 1 ) ) );
public Dimension getMaximumSize() { siteSettings.setBackground( null );
return new Dimension( 20, getPreferredSize().height );
}
} );
siteSettings.setAlignmentX( LEFT_ALIGNMENT ); 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.setAlignmentX( LEFT_ALIGNMENT );
siteTypeField.setAlignmentY( CENTER_ALIGNMENT ); siteTypeField.setAlignmentY( CENTER_ALIGNMENT );
siteTypeField.setSelectedItem( MPSiteType.GeneratedLong ); siteTypeField.setSelectedItem( MPSiteType.GeneratedLong );
@ -143,7 +128,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
} }
} ); } );
siteCounterField.setFont( Res.sourceCodeProRegular().deriveFont( 12f ) ); siteCounterField.setFont( Res.valueFont().deriveFont( 12f ) );
siteCounterField.setAlignmentX( RIGHT_ALIGNMENT ); siteCounterField.setAlignmentX( RIGHT_ALIGNMENT );
siteCounterField.setAlignmentY( CENTER_ALIGNMENT ); siteCounterField.setAlignmentY( CENTER_ALIGNMENT );
siteCounterField.addChangeListener( new ChangeListener() { siteCounterField.addChangeListener( new ChangeListener() {
@ -154,10 +139,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,20 +151,22 @@ 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.setEditable( false ); passwordField.setEditable( false );
passwordField.setHorizontalAlignment( JTextField.CENTER );
passwordField.putClientProperty( "JPasswordField.cutCopyAllowed", true ); passwordField.putClientProperty( "JPasswordField.cutCopyAllowed", true );
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 );
add( Components.boxLayout( BoxLayout.PAGE_AXIS, maskPasswordField, passwordField, tipLabel ), BorderLayout.SOUTH ); JPanel passwordContainer = Components.boxLayout( BoxLayout.PAGE_AXIS, maskPasswordField,
Components.bordered( passwordField, BorderFactory.createLoweredSoftBevelBorder(),
Res.colors().frameBg() ), tipLabel );
passwordContainer.setBackground( null );
add( passwordContainer, BorderLayout.SOUTH );
pack(); pack();
setMinimumSize( getSize() ); setMinimumSize( getSize() );
@ -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

View File

@ -4,6 +4,7 @@ import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse;
import static com.lyndir.lhunath.opal.system.util.StringUtils.*; import 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() ));
} public static Font sourceSansProRegular() {
catch (FontFormatException | IOException e) { return font( "fonts/SourceSansPro-Regular.otf" );
throw Throwables.propagate( e ); }
}
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() {
try { return font( "fonts/Exo2.0-Thin.otf" );
return exoThin != null? exoThin: (exoThin = }
Font.createFont( Font.TRUETYPE_FONT, Resources.getResource( "fonts/Exo2.0-Thin.otf" ).openStream() ));
} public static Font arimoBold() {
catch (FontFormatException | IOException e) { return font( "fonts/Arimo-Bold.ttf" );
throw Throwables.propagate( e ); }
}
public static Font arimoBoldItalic() {
return font( "fonts/Arimo-BoldItalic.ttf" );
}
public static Font arimoItalic() {
return font( "fonts/Arimo-Italic.ttf" );
}
public static Font arimoRegular() {
return font( "fonts/Arimo-Regular.ttf" );
}
private static Font font(String fontResourceName) {
Map<String, SoftReference<Font>> fontsByResourceName = Maps.newHashMap();
SoftReference<Font> fontRef = fontsByResourceName.get( fontResourceName );
Font font = fontRef == null? null: fontRef.get();
if (font == null)
try {
fontsByResourceName.put( fontResourceName, new SoftReference<>(
font = Font.createFont( Font.TRUETYPE_FONT, Resources.getResource( fontResourceName ).openStream() ) ) );
}
catch (FontFormatException | IOException e) {
throw Throwables.propagate( e );
}
return font;
}
public static Colors colors() {
return colors;
} }
private static final class RetinaIcon extends ImageIcon { 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;
}
}
} }

View File

@ -14,13 +14,13 @@ 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 JPanel root;
private final JButton signInButton; private final JButton signInButton;
private final JPanel authenticationContainer; private final JPanel authenticationContainer;
private AuthenticationPanel authenticationPanel; private AuthenticationPanel authenticationPanel;
private boolean incognito; private boolean incognito;
public User user; public User user;
public UnlockFrame(final SignInCallback signInCallback) public UnlockFrame(final SignInCallback signInCallback)
throws HeadlessException { throws HeadlessException {
@ -29,17 +29,19 @@ public class UnlockFrame extends JFrame {
setDefaultCloseOperation( DISPOSE_ON_CLOSE ); setDefaultCloseOperation( DISPOSE_ON_CLOSE );
setContentPane( root = new JPanel( new BorderLayout( 20, 20 ) ) ); setContentPane( root = new JPanel( new BorderLayout( 20, 20 ) ) );
root.setBackground( Res.colors().frameBg() );
root.setBorder( new EmptyBorder( 20, 20, 20, 20 ) ); root.setBorder( new EmptyBorder( 20, 20, 20, 20 ) );
authenticationContainer = new JPanel(); authenticationContainer = Components.boxLayout( BoxLayout.PAGE_AXIS );
authenticationContainer.setLayout( new BoxLayout( authenticationContainer, BoxLayout.PAGE_AXIS ) ); authenticationContainer.setBackground( Res.colors().controlBg() );
authenticationContainer.setBorder( new CompoundBorder( new EtchedBorder( EtchedBorder.RAISED ), new EmptyBorder( 8, 8, 8, 8 ) ) ); authenticationContainer.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
add( authenticationContainer ); add( Components.bordered( 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 );
root.add( signInBox, BorderLayout.SOUTH );
signInButton.setAlignmentX( LEFT_ALIGNMENT ); signInButton.setAlignmentX( LEFT_ALIGNMENT );
signInButton.addActionListener( new AbstractAction() { signInButton.addActionListener( new AbstractAction() {
@Override @Override
@ -55,10 +57,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,10 +71,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.setAlignmentX( LEFT_ALIGNMENT );
incognitoCheckBox.setSelected( incognito ); incognitoCheckBox.setSelected( incognito );
incognitoCheckBox.addItemListener( new ItemListener() { incognitoCheckBox.addItemListener( new ItemListener() {

View File

@ -20,6 +20,8 @@ public abstract class User {
protected abstract String getMasterPassword(); protected abstract String getMasterPassword();
public abstract MasterKey.Version getAlgorithmVersion();
public int getAvatar() { public int getAvatar() {
return 0; return 0;
} }
@ -38,7 +40,7 @@ public abstract class User {
throw new MasterKeyException( strf( "Master password unknown for user: %s", getFullName() ) ); throw new MasterKeyException( strf( "Master password unknown for user: %s", getFullName() ) );
} }
key = new MasterKey( getFullName(), masterPassword ); key = MasterKey.create( getAlgorithmVersion(), getFullName(), masterPassword );
} }
return key; return key;

View File

@ -1,7 +1,10 @@
package com.lyndir.masterpassword.util; package com.lyndir.masterpassword.util;
import com.lyndir.masterpassword.gui.Res;
import java.awt.*; import java.awt.*;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
/** /**
@ -17,4 +20,102 @@ public abstract class Components {
return container; return container;
} }
public static JPanel bordered(final JComponent component, final Border border) {
return bordered( component, border, null );
}
public static JPanel bordered(final JComponent component, final Border border, Color background) {
JPanel box = boxLayout( BoxLayout.LINE_AXIS, component );
if (border != null)
box.setBorder( border );
if (background != null)
box.setBackground( background );
return box;
}
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 ) );
}
@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 ) ) );
}
@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 ) );
}
@Override
public Dimension getMaximumSize() {
return new Dimension( 20, getPreferredSize().height );
}
};
}
public static Component stud() {
return Box.createRigidArea( new Dimension( 8, 8 ) );
}
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 );
}
@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 ) );
}
};
}
public static JCheckBox checkBox(final String label) {
return new JCheckBox( label ) {
{
setFont( Res.controlFont().deriveFont( 12f ) );
}
};
}
} }

View File

@ -18,14 +18,14 @@ public class MPSite {
public static final MPSiteType DEFAULT_TYPE = MPSiteType.GeneratedLong; public static final MPSiteType DEFAULT_TYPE = MPSiteType.GeneratedLong;
public static final int DEFAULT_COUNTER = 1; public static final int DEFAULT_COUNTER = 1;
private final MPUser user; private final MPUser user;
private int mpVersion; private MasterKey.Version mpVersion;
private Instant lastUsed; private Instant lastUsed;
private String siteName; private String siteName;
private MPSiteType siteType; private MPSiteType siteType;
private int siteCounter; private int siteCounter;
private int uses; private int uses;
private String loginName; private String loginName;
public MPSite(final MPUser user, final String siteName) { public MPSite(final MPUser user, final String siteName) {
this( user, siteName, DEFAULT_TYPE, DEFAULT_COUNTER ); this( user, siteName, DEFAULT_TYPE, DEFAULT_COUNTER );
@ -33,14 +33,14 @@ public class MPSite {
public MPSite(final MPUser user, final String siteName, final MPSiteType siteType, final int siteCounter) { public MPSite(final MPUser user, final String siteName, final MPSiteType siteType, final int siteCounter) {
this.user = user; this.user = user;
this.mpVersion = MasterKey.ALGORITHM; this.mpVersion = MasterKey.Version.CURRENT;
this.lastUsed = new Instant(); this.lastUsed = new Instant();
this.siteName = siteName; this.siteName = siteName;
this.siteType = siteType; this.siteType = siteType;
this.siteCounter = siteCounter; this.siteCounter = siteCounter;
} }
protected MPSite(final MPUser user, final int mpVersion, final Instant lastUsed, final String siteName, final MPSiteType siteType, final int siteCounter, protected MPSite(final MPUser user, final MasterKey.Version mpVersion, final Instant lastUsed, final String siteName, final MPSiteType siteType, final int siteCounter,
final int uses, final String loginName, final String importContent) { final int uses, final String loginName, final String importContent) {
this.user = user; this.user = user;
this.mpVersion = mpVersion; this.mpVersion = mpVersion;
@ -69,11 +69,11 @@ public class MPSite {
return null; return null;
} }
public int getMPVersion() { public MasterKey.Version getMPVersion() {
return mpVersion; return mpVersion;
} }
public void setMPVersion(final int mpVersion) { public void setMPVersion(final MasterKey.Version mpVersion) {
this.mpVersion = mpVersion; this.mpVersion = mpVersion;
} }

View File

@ -63,8 +63,8 @@ public class MPSiteMarshaller {
header.append( "# Full Name: " ).append( user.getFullName() ).append( '\n' ); header.append( "# Full Name: " ).append( user.getFullName() ).append( '\n' );
header.append( "# Avatar: " ).append( user.getAvatar() ).append( '\n' ); header.append( "# Avatar: " ).append( user.getAvatar() ).append( '\n' );
header.append( "# Key ID: " ).append( user.exportKeyID() ).append( '\n' ); header.append( "# Key ID: " ).append( user.exportKeyID() ).append( '\n' );
header.append( "# Version: " ).append( MasterKey.VERSION ).append( '\n' ); header.append( "# Version: " ).append( user.getAlgorithmVersion().toBundleVersion() ).append( '\n' );
header.append( "# Algorithm: " ).append( MasterKey.ALGORITHM ).append( '\n' ); header.append( "# Algorithm: " ).append( user.getAlgorithmVersion().toInt() ).append( '\n' );
header.append( "# Default Type: " ).append( user.getDefaultType().getType() ).append( '\n' ); header.append( "# Default Type: " ).append( user.getDefaultType().getType() ).append( '\n' );
header.append( "# Passwords: " ).append( contentMode.name() ).append( '\n' ); header.append( "# Passwords: " ).append( contentMode.name() ).append( '\n' );
header.append( "##\n" ); header.append( "##\n" );

View File

@ -10,6 +10,7 @@ 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 com.lyndir.lhunath.opal.system.util.NNOperation; import com.lyndir.lhunath.opal.system.util.NNOperation;
import com.lyndir.masterpassword.MPSiteType; import com.lyndir.masterpassword.MPSiteType;
import com.lyndir.masterpassword.MasterKey;
import java.io.*; import java.io.*;
import java.util.List; import java.util.List;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@ -110,7 +111,7 @@ public class MPSiteUnmarshaller {
this.mpVersion = mpVersion; this.mpVersion = mpVersion;
this.clearContent = clearContent; this.clearContent = clearContent;
user = new MPUser( fullName, keyID, avatar, defaultType, new DateTime( 0 ) ); user = new MPUser( fullName, keyID, MasterKey.Version.fromInt( mpVersion ), avatar, defaultType, new DateTime( 0 ) );
} }
@Nullable @Nullable
@ -123,11 +124,10 @@ public class MPSiteUnmarshaller {
switch (importFormat) { switch (importFormat) {
case 0: case 0:
site = new MPSite( user, // site = new MPSite( user, //
ConversionUtils.toIntegerNN( siteMatcher.group( 4 ).replace( ":", "" ) ), // MasterKey.Version.fromInt( ConversionUtils.toIntegerNN( siteMatcher.group( 4 ).replace( ":", "" ) ) ), //
rfc3339.parseDateTime( siteMatcher.group( 1 ) ).toInstant(), // rfc3339.parseDateTime( siteMatcher.group( 1 ) ).toInstant(), //
siteMatcher.group( 5 ), // siteMatcher.group( 5 ), //
MPSiteType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ), MPSiteType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ), MPSite.DEFAULT_COUNTER, //
MPSite.DEFAULT_COUNTER, //
ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ), // ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ), //
null, // null, //
siteMatcher.group( 6 ) ); siteMatcher.group( 6 ) );
@ -135,7 +135,7 @@ public class MPSiteUnmarshaller {
case 1: case 1:
site = new MPSite( user, // site = new MPSite( user, //
ConversionUtils.toIntegerNN( siteMatcher.group( 4 ).replace( ":", "" ) ), // MasterKey.Version.fromInt( ConversionUtils.toIntegerNN( siteMatcher.group( 4 ).replace( ":", "" ) ) ), //
rfc3339.parseDateTime( siteMatcher.group( 1 ) ).toInstant(), // rfc3339.parseDateTime( siteMatcher.group( 1 ) ).toInstant(), //
siteMatcher.group( 7 ), // siteMatcher.group( 7 ), //
MPSiteType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ), MPSiteType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),

View File

@ -6,6 +6,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.lyndir.lhunath.opal.system.CodeUtils; import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.masterpassword.MPSiteType; import com.lyndir.masterpassword.MPSiteType;
import com.lyndir.masterpassword.MasterKey;
import java.util.*; import java.util.*;
import org.joda.time.*; import org.joda.time.*;
@ -18,23 +19,25 @@ public class MPUser implements Comparable<MPUser> {
private final String fullName; private final String fullName;
private final Collection<MPSite> sites = Sets.newHashSet(); private final Collection<MPSite> sites = Sets.newHashSet();
private byte[] keyID; private byte[] keyID;
private int avatar; private MasterKey.Version algorithmVersion;
private MPSiteType defaultType; private int avatar;
private ReadableInstant lastUsed; private MPSiteType defaultType;
private ReadableInstant lastUsed;
public MPUser(final String fullName) { public MPUser(final String fullName) {
this( fullName, null ); this( fullName, null );
} }
public MPUser(final String fullName, final byte[] keyID) { public MPUser(final String fullName, final byte[] keyID) {
this( fullName, keyID, 0, MPSiteType.GeneratedLong, new DateTime() ); this( fullName, keyID, MasterKey.Version.CURRENT, 0, MPSiteType.GeneratedLong, new DateTime() );
} }
public MPUser(final String fullName, final byte[] keyID, final int avatar, final MPSiteType defaultType, public MPUser(final String fullName, final byte[] keyID, final MasterKey.Version algorithmVersion, final int avatar, final MPSiteType defaultType,
final ReadableInstant lastUsed) { final ReadableInstant lastUsed) {
this.fullName = fullName; this.fullName = fullName;
this.keyID = keyID; this.keyID = keyID;
this.algorithmVersion = algorithmVersion;
this.avatar = avatar; this.avatar = avatar;
this.defaultType = defaultType; this.defaultType = defaultType;
this.lastUsed = lastUsed; this.lastUsed = lastUsed;
@ -73,6 +76,14 @@ public class MPUser implements Comparable<MPUser> {
this.keyID = keyID; this.keyID = keyID;
} }
public MasterKey.Version getAlgorithmVersion() {
return algorithmVersion;
}
public void setAlgorithmVersion(final MasterKey.Version algorithmVersion) {
this.algorithmVersion = algorithmVersion;
}
public int getAvatar() { public int getAvatar() {
return avatar; return avatar;
} }