2
0

Marshal refactoring to prepare for new format.

This commit is contained in:
Maarten Billemont 2017-09-20 17:45:49 -04:00
parent 70bb30ba0c
commit 05a9ba46d0
29 changed files with 715 additions and 760 deletions

View File

@ -18,6 +18,9 @@
package com.lyndir.masterpassword; package com.lyndir.masterpassword;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
/** /**
* @author lhunath, 2016-10-29 * @author lhunath, 2016-10-29
@ -38,4 +41,6 @@ public final class MPConstant {
/* Algorithm */ /* Algorithm */
public static final int MS_PER_S = 1000; public static final int MS_PER_S = 1000;
public static final DateTimeFormatter dateTimeFormatter = ISODateTimeFormat.dateTimeNoMillis();
} }

View File

@ -0,0 +1,41 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword;
import com.google.common.primitives.UnsignedInteger;
import java.nio.ByteBuffer;
/**
* @author lhunath, 2017-09-20
*/
public final class MPUtils {
public static byte[] bytesForInt(final int number) {
return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( MasterKeyV0.mpw_byteOrder ).putInt( number ).array();
}
public static byte[] bytesForInt(final UnsignedInteger number) {
return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( MasterKeyV0.mpw_byteOrder ).putInt( number.intValue() ).array();
}
public static byte[] idForBytes(final byte[] bytes) {
return MasterKeyV0.mpw_hash.of( bytes );
}
}

View File

@ -18,13 +18,11 @@
package com.lyndir.masterpassword; package com.lyndir.masterpassword;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf; import static com.lyndir.masterpassword.MPUtils.idForBytes;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.primitives.UnsignedInteger; import com.google.common.primitives.UnsignedInteger;
import com.lyndir.lhunath.opal.system.*;
import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.logging.Logger;
import java.util.Arrays;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -32,86 +30,25 @@ import javax.annotation.Nullable;
/** /**
* @author lhunath, 2014-08-30 * @author lhunath, 2014-08-30
*/ */
public abstract class MasterKey { public class MasterKey {
@SuppressWarnings("UnusedDeclaration") @SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MasterKey.class ); private static final Logger logger = Logger.get( MasterKey.class );
private static boolean allowNativeByDefault = true;
@Nonnull
private final String fullName; private final String fullName;
private boolean allowNative = allowNativeByDefault; private final char[] masterPassword;
@Nullable @SuppressWarnings("AssignmentToCollectionOrArrayFieldFromParameter")
private byte[] masterKey; public MasterKey(final String fullName, final char[] masterPassword) {
@SuppressWarnings("MethodCanBeVariableArityMethod")
public static MasterKey create(final String fullName, final char[] masterPassword) {
return create( Version.CURRENT, fullName, masterPassword );
}
@Nonnull
@SuppressWarnings("MethodCanBeVariableArityMethod")
public static MasterKey create(final 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( strf( "Unsupported version: %s", version ) );
}
public static boolean isAllowNativeByDefault() {
return allowNativeByDefault;
}
/**
* Native libraries are useful for speeding up the performance of cryptographical functions.
* Sometimes, however, we may prefer to use Java-only code.
* For instance, for auditability / trust or because the native code doesn't work on our CPU/platform.
* <p/>
* This setter affects the default setting for any newly created {@link MasterKey}s.
*
* @param allowNative false to disallow the use of native libraries.
*/
public static void setAllowNativeByDefault(final boolean allowNative) {
allowNativeByDefault = allowNative;
}
protected MasterKey(final String fullName) {
Preconditions.checkArgument( !fullName.isEmpty() );
this.fullName = fullName; this.fullName = fullName;
logger.trc( "fullName: %s", fullName ); this.masterPassword = masterPassword;
} }
/** private byte[] getKey(final Version algorithmVersion) {
* Derive the master key for a user based on their name and master password. // TODO: Cache keys.
* return algorithmVersion.getAlgorithm().deriveKey( fullName, masterPassword );
* @param masterPassword The user's master password. }
*/
@Nullable
@SuppressWarnings("MethodCanBeVariableArityMethod")
protected abstract byte[] deriveKey(char[] masterPassword);
/**
* Derive the site key for a user's site from the given master key and site parameters.
*
* @param siteName A site identifier.
* @param siteCounter The result identifier.
* @param keyPurpose The intended purpose for this site key.
* @param keyContext A site-scoped key modifier.
*/
protected abstract byte[] siteKey(String siteName, UnsignedInteger siteCounter, MPKeyPurpose keyPurpose,
@Nullable String keyContext);
/** /**
* Generate a site result token. * Generate a site result token.
@ -122,16 +59,14 @@ public abstract class MasterKey {
* @param keyContext A site-scoped result modifier. * @param keyContext A site-scoped result modifier.
* @param resultType The type of result to generate. * @param resultType The type of result to generate.
* @param resultParam A parameter for the resultType. For stateful result types, the output of * @param resultParam A parameter for the resultType. For stateful result types, the output of
* {@link #siteState(String, UnsignedInteger, MPKeyPurpose, String, MPResultType, String)}. * {@link #siteState(String, UnsignedInteger, MPKeyPurpose, String, MPResultType, String, Version)}.
*/ */
public abstract String siteResult(String siteName, UnsignedInteger siteCounter, MPKeyPurpose keyPurpose, public String siteResult(final String siteName, final UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose,
@Nullable String keyContext, MPResultType resultType, @Nullable String resultParam); @Nullable final String keyContext, final MPResultType resultType, @Nullable final String resultParam,
final Version algorithmVersion) {
protected abstract String sitePasswordFromTemplate(byte[] siteKey, MPResultType resultType, @Nullable String resultParam); return algorithmVersion.getAlgorithm().siteResult(
getKey( algorithmVersion ), siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam );
protected abstract String sitePasswordFromCrypt(byte[] siteKey, MPResultType resultType, @Nullable String resultParam); }
protected abstract String sitePasswordFromDerive(byte[] siteKey, MPResultType resultType, @Nullable String resultParam);
/** /**
* Encrypt a stateful site token for persistence. * Encrypt a stateful site token for persistence.
@ -142,12 +77,14 @@ public abstract class MasterKey {
* @param keyContext A site-scoped key modifier. * @param keyContext A site-scoped key modifier.
* @param resultType The type of result token to encrypt. * @param resultType The type of result token to encrypt.
* @param resultParam The result token desired from * @param resultParam The result token desired from
* {@link #siteResult(String, UnsignedInteger, MPKeyPurpose, String, MPResultType, String)}. * {@link #siteResult(String, UnsignedInteger, MPKeyPurpose, String, MPResultType, String, Version)}.
*/ */
public abstract String siteState(String siteName, UnsignedInteger siteCounter, MPKeyPurpose keyPurpose, public String siteState(final String siteName, final UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose,
@Nullable String keyContext, MPResultType resultType, @Nullable String resultParam); @Nullable final String keyContext, final MPResultType resultType, @Nullable final String resultParam,
final Version algorithmVersion) {
public abstract Version getAlgorithmVersion(); return algorithmVersion.getAlgorithm().siteState(
getKey( algorithmVersion ), siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam );
}
@Nonnull @Nonnull
public String getFullName() { public String getFullName() {
@ -155,63 +92,11 @@ public abstract class MasterKey {
return fullName; return fullName;
} }
public boolean isAllowNative() { public byte[] getKeyID(final Version algorithmVersion) {
return allowNative;
return idForBytes( getKey( algorithmVersion ) );
} }
public MasterKey setAllowNative(final boolean allowNative) {
this.allowNative = allowNative;
return this;
}
@Nonnull
protected byte[] getKey() {
Preconditions.checkState( isValid() );
return Preconditions.checkNotNull( masterKey );
}
public byte[] getKeyID() {
return idForBytes( getKey() );
}
public boolean isValid() {
return masterKey != null;
}
public void invalidate() {
if (masterKey != null) {
Arrays.fill( masterKey, (byte) 0 );
masterKey = null;
}
}
@SuppressWarnings("MethodCanBeVariableArityMethod")
public MasterKey revalidate(final char[] masterPassword) {
invalidate();
logger.trc( "masterPassword: %s", new String( masterPassword ) );
long start = System.currentTimeMillis();
masterKey = deriveKey( masterPassword );
if (masterKey == null)
logger.dbg( "masterKey calculation failed after %.2fs.", (double) (System.currentTimeMillis() - start) / MPConstant.MS_PER_S );
else
logger.trc( "masterKey ID: %s (derived in %.2fs)", CodeUtils.encodeHex( idForBytes( masterKey ) ),
(double) (System.currentTimeMillis() - start) / MPConstant.MS_PER_S );
return this;
}
protected abstract byte[] bytesForInt(int number);
protected abstract byte[] bytesForInt(UnsignedInteger number);
protected abstract byte[] idForBytes(byte[] bytes);
public enum Version { public enum Version {
/** /**
* bugs: * bugs:
@ -219,26 +104,36 @@ public abstract class MasterKey {
* - miscounted the byte-length for multi-byte site names. * - miscounted the byte-length for multi-byte site names.
* - miscounted the byte-length for multi-byte full names. * - miscounted the byte-length for multi-byte full names.
*/ */
V0, V0( new MasterKeyV0() ),
/** /**
* bugs: * bugs:
* - miscounted the byte-length for multi-byte site names. * - miscounted the byte-length for multi-byte site names.
* - miscounted the byte-length for multi-byte full names. * - miscounted the byte-length for multi-byte full names.
*/ */
V1, V1( new MasterKeyV1() ),
/** /**
* bugs: * bugs:
* - miscounted the byte-length for multi-byte full names. * - miscounted the byte-length for multi-byte full names.
*/ */
V2, V2( new MasterKeyV2() ),
/** /**
* bugs: * bugs:
* - no known issues. * - no known issues.
*/ */
V3; V3( new MasterKeyV3() );
public static final Version CURRENT = V3; public static final Version CURRENT = V3;
private final MasterKeyAlgorithm algorithm;
Version(final MasterKeyAlgorithm algorithm) {
this.algorithm = algorithm;
}
public MasterKeyAlgorithm getAlgorithm() {
return algorithm;
}
public static Version fromInt(final int algorithmVersion) { public static Version fromInt(final int algorithmVersion) {
return values()[algorithmVersion]; return values()[algorithmVersion];

View File

@ -0,0 +1,49 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword;
import com.google.common.primitives.UnsignedInteger;
import java.io.Serializable;
import javax.annotation.Nullable;
/**
* @see MasterKey.Version
*/
public interface MasterKeyAlgorithm extends Serializable {
MasterKey.Version getAlgorithmVersion();
byte[] deriveKey(String fullName, char[] masterPassword);
byte[] siteKey(byte[] masterKey, String siteName, UnsignedInteger siteCounter, MPKeyPurpose keyPurpose,
@Nullable String keyContext);
String siteResult(byte[] masterKey, String siteName, UnsignedInteger siteCounter, MPKeyPurpose keyPurpose,
@Nullable String keyContext, MPResultType resultType, @Nullable String resultParam);
String sitePasswordFromTemplate(byte[] masterKey, byte[] siteKey, MPResultType resultType, @Nullable String resultParam);
String sitePasswordFromCrypt(byte[] masterKey, byte[] siteKey, MPResultType resultType, @Nullable String resultParam);
String sitePasswordFromDerive(byte[] masterKey, byte[] siteKey, MPResultType resultType, @Nullable String resultParam);
String siteState(byte[] masterKey, String siteName, UnsignedInteger siteCounter, MPKeyPurpose keyPurpose,
@Nullable String keyContext, MPResultType resultType, @Nullable String resultParam);
}

View File

@ -18,6 +18,8 @@
package com.lyndir.masterpassword; package com.lyndir.masterpassword;
import static com.lyndir.masterpassword.MPUtils.*;
import com.google.common.base.*; import com.google.common.base.*;
import com.google.common.primitives.Bytes; import com.google.common.primitives.Bytes;
import com.google.common.primitives.UnsignedInteger; import com.google.common.primitives.UnsignedInteger;
@ -36,14 +38,11 @@ import javax.crypto.IllegalBlockSizeException;
/** /**
* bugs: * @see MasterKey.Version#V0
* - V2: miscounted the byte-length for multi-byte full names.
* - V1: miscounted the byte-length for multi-byte site names.
* - V0: does math with chars whose signedness was platform-dependent.
* *
* @author lhunath, 2014-08-30 * @author lhunath, 2014-08-30
*/ */
public class MasterKeyV0 extends MasterKey { public class MasterKeyV0 implements MasterKeyAlgorithm {
/** /**
* mpw: validity for the time-based rolling counter. * mpw: validity for the time-based rolling counter.
@ -82,25 +81,18 @@ public class MasterKeyV0 extends MasterKey {
*/ */
protected static final int scrypt_N = 32768; protected static final int scrypt_N = 32768;
@SuppressWarnings("UnusedDeclaration") protected final Logger logger = Logger.get( getClass() );
private static final Logger logger = Logger.get( MasterKeyV0.class );
public MasterKeyV0(final String fullName) { @Override
super( fullName ); public MasterKey.Version getAlgorithmVersion() {
return MasterKey.Version.V0;
} }
@Override @Override
public Version getAlgorithmVersion() { public byte[] deriveKey(final String fullName, final char[] masterPassword) {
return Version.V0;
}
@Nullable
@Override
protected byte[] deriveKey(final char[] masterPassword) {
Preconditions.checkArgument( masterPassword.length > 0 ); Preconditions.checkArgument( masterPassword.length > 0 );
String fullName = getFullName();
byte[] fullNameBytes = fullName.getBytes( mpw_charset ); byte[] fullNameBytes = fullName.getBytes( mpw_charset );
byte[] fullNameLengthBytes = bytesForInt( fullName.length() ); byte[] fullNameLengthBytes = bytesForInt( fullName.length() );
ByteBuffer mpBytesBuf = mpw_charset.encode( CharBuffer.wrap( masterPassword ) ); ByteBuffer mpBytesBuf = mpw_charset.encode( CharBuffer.wrap( masterPassword ) );
@ -127,28 +119,26 @@ public class MasterKeyV0 extends MasterKey {
byte[] masterKey = scrypt( masterKeySalt, mpBytes ); // TODO: Why not mpBytesBuf.array()? byte[] masterKey = scrypt( masterKeySalt, mpBytes ); // TODO: Why not mpBytesBuf.array()?
Arrays.fill( masterKeySalt, (byte) 0 ); Arrays.fill( masterKeySalt, (byte) 0 );
Arrays.fill( mpBytes, (byte) 0 ); Arrays.fill( mpBytes, (byte) 0 );
logger.trc( " => masterKey.id: %s", (masterKey == null)? null: (Object) idForBytes( masterKey ) ); logger.trc( " => masterKey.id: %s", (Object) idForBytes( masterKey ) );
return masterKey; return masterKey;
} }
@Nullable
protected byte[] scrypt(final byte[] masterKeySalt, final byte[] mpBytes) { protected byte[] scrypt(final byte[] masterKeySalt, final byte[] mpBytes) {
try { try {
if (isAllowNative()) // if (isAllowNative())
return SCrypt.scrypt( mpBytes, masterKeySalt, scrypt_N, scrypt_r, scrypt_p, mpw_dkLen ); return SCrypt.scrypt( mpBytes, masterKeySalt, scrypt_N, scrypt_r, scrypt_p, mpw_dkLen );
else // else
return SCrypt.scryptJ( mpBytes, masterKeySalt, scrypt_N, scrypt_r, scrypt_p, mpw_dkLen ); // return SCrypt.scryptJ( mpBytes, masterKeySalt, scrypt_N, scrypt_r, scrypt_p, mpw_dkLen );
} }
catch (final GeneralSecurityException e) { catch (final GeneralSecurityException e) {
logger.bug( e ); throw logger.bug( e );
return null;
} }
} }
@Override @Override
protected byte[] siteKey(final String siteName, UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose, public byte[] siteKey(final byte[] masterKey, final String siteName, UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose,
@Nullable final String keyContext) { @Nullable final String keyContext) {
Preconditions.checkArgument( !siteName.isEmpty() ); Preconditions.checkArgument( !siteName.isEmpty() );
logger.trc( "-- mpw_siteKey (algorithm: %u)", getAlgorithmVersion().toInt() ); logger.trc( "-- mpw_siteKey (algorithm: %u)", getAlgorithmVersion().toInt() );
@ -179,7 +169,6 @@ public class MasterKeyV0 extends MasterKey {
sitePasswordInfo = Bytes.concat( sitePasswordInfo, keyContextLengthBytes, keyContextBytes ); sitePasswordInfo = Bytes.concat( sitePasswordInfo, keyContextLengthBytes, keyContextBytes );
logger.trc( " => siteSalt.id: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) ); logger.trc( " => siteSalt.id: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) );
byte[] masterKey = getKey();
logger.trc( "siteKey: hmac-sha256( masterKey.id=%s, siteSalt )", (Object) idForBytes( masterKey ) ); logger.trc( "siteKey: hmac-sha256( masterKey.id=%s, siteSalt )", (Object) idForBytes( masterKey ) );
byte[] sitePasswordSeedBytes = mpw_digest.of( masterKey, sitePasswordInfo ); byte[] sitePasswordSeedBytes = mpw_digest.of( masterKey, sitePasswordInfo );
logger.trc( " => siteKey.id: %s", (Object) idForBytes( sitePasswordSeedBytes ) ); logger.trc( " => siteKey.id: %s", (Object) idForBytes( sitePasswordSeedBytes ) );
@ -188,10 +177,10 @@ public class MasterKeyV0 extends MasterKey {
} }
@Override @Override
public String siteResult(final String siteName, final UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose, public String siteResult(final byte[] masterKey, final String siteName, final UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose,
@Nullable final String keyContext, final MPResultType resultType, @Nullable final String resultParam) { @Nullable final String keyContext, final MPResultType resultType, @Nullable final String resultParam) {
byte[] siteKey = siteKey( siteName, siteCounter, keyPurpose, keyContext ); byte[] siteKey = siteKey( masterKey, siteName, siteCounter, keyPurpose, keyContext );
logger.trc( "-- mpw_siteResult (algorithm: %u)", getAlgorithmVersion().toInt() ); logger.trc( "-- mpw_siteResult (algorithm: %u)", getAlgorithmVersion().toInt() );
logger.trc( "resultType: %d (%s)", resultType.toInt(), resultType.getShortName() ); logger.trc( "resultType: %d (%s)", resultType.toInt(), resultType.getShortName() );
@ -199,18 +188,18 @@ public class MasterKeyV0 extends MasterKey {
switch (resultType.getTypeClass()) { switch (resultType.getTypeClass()) {
case Template: case Template:
return sitePasswordFromTemplate( siteKey, resultType, resultParam ); return sitePasswordFromTemplate( masterKey, siteKey, resultType, resultParam );
case Stateful: case Stateful:
return sitePasswordFromCrypt( siteKey, resultType, resultParam ); return sitePasswordFromCrypt( masterKey, siteKey, resultType, resultParam );
case Derive: case Derive:
return sitePasswordFromDerive( siteKey, resultType, resultParam ); return sitePasswordFromDerive( masterKey, siteKey, resultType, resultParam );
} }
throw logger.bug( "Unsupported result type class: %s", resultType.getTypeClass() ); throw logger.bug( "Unsupported result type class: %s", resultType.getTypeClass() );
} }
@Override @Override
protected String sitePasswordFromTemplate(final byte[] siteKey, final MPResultType resultType, @Nullable final String resultParam) { public String sitePasswordFromTemplate(final byte[] masterKey, final byte[] siteKey, final MPResultType resultType, @Nullable final String resultParam) {
int[] _siteKey = new int[siteKey.length]; int[] _siteKey = new int[siteKey.length];
for (int i = 0; i < siteKey.length; ++i) { for (int i = 0; i < siteKey.length; ++i) {
@ -244,7 +233,7 @@ public class MasterKeyV0 extends MasterKey {
} }
@Override @Override
protected String sitePasswordFromCrypt(final byte[] siteKey, final MPResultType resultType, @Nullable final String resultParam) { public String sitePasswordFromCrypt(final byte[] masterKey, final byte[] siteKey, final MPResultType resultType, @Nullable final String resultParam) {
Preconditions.checkNotNull( resultParam ); Preconditions.checkNotNull( resultParam );
Preconditions.checkArgument( !resultParam.isEmpty() ); Preconditions.checkArgument( !resultParam.isEmpty() );
@ -255,7 +244,7 @@ public class MasterKeyV0 extends MasterKey {
logger.trc( "b64 decoded: %zu bytes = %s", cipherBuf.length, CodeUtils.encodeHex( cipherBuf ) ); logger.trc( "b64 decoded: %zu bytes = %s", cipherBuf.length, CodeUtils.encodeHex( cipherBuf ) );
// Decrypt // Decrypt
byte[] plainBuf = CryptUtils.decrypt( cipherBuf, getKey(), true ); byte[] plainBuf = CryptUtils.decrypt( cipherBuf, masterKey, true );
String plainText = mpw_charset.decode( ByteBuffer.wrap( plainBuf ) ).toString(); String plainText = mpw_charset.decode( ByteBuffer.wrap( plainBuf ) ).toString();
logger.trc( "decrypted -> plainText: %s", plainText ); logger.trc( "decrypted -> plainText: %s", plainText );
@ -267,7 +256,7 @@ public class MasterKeyV0 extends MasterKey {
} }
@Override @Override
protected String sitePasswordFromDerive(final byte[] siteKey, final MPResultType resultType, @Nullable final String resultParam) { public String sitePasswordFromDerive(final byte[] masterKey, final byte[] siteKey, final MPResultType resultType, @Nullable final String resultParam) {
if (resultType == MPResultType.DeriveKey) { if (resultType == MPResultType.DeriveKey) {
Preconditions.checkNotNull( resultParam ); Preconditions.checkNotNull( resultParam );
@ -296,7 +285,7 @@ public class MasterKeyV0 extends MasterKey {
} }
@Override @Override
public String siteState(final String siteName, final UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose, public String siteState(final byte[] masterKey, final String siteName, final UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose,
@Nullable final String keyContext, final MPResultType resultType, @Nullable final String resultParam) { @Nullable final String keyContext, final MPResultType resultType, @Nullable final String resultParam) {
Preconditions.checkNotNull( resultParam ); Preconditions.checkNotNull( resultParam );
@ -305,7 +294,7 @@ public class MasterKeyV0 extends MasterKey {
try { try {
// Encrypt // Encrypt
ByteBuffer plainText = mpw_charset.encode( CharBuffer.wrap( resultParam ) ); ByteBuffer plainText = mpw_charset.encode( CharBuffer.wrap( resultParam ) );
byte[] cipherBuf = CryptUtils.encrypt( plainText.array(), getKey(), true ); byte[] cipherBuf = CryptUtils.encrypt( plainText.array(), masterKey, true );
logger.trc( "cipherBuf: %zu bytes = %s", cipherBuf.length, CodeUtils.encodeHex( cipherBuf ) ); logger.trc( "cipherBuf: %zu bytes = %s", cipherBuf.length, CodeUtils.encodeHex( cipherBuf ) );
// Base64-encode // Base64-encode
@ -318,19 +307,4 @@ public class MasterKeyV0 extends MasterKey {
throw logger.bug( e ); throw logger.bug( e );
} }
} }
@Override
protected byte[] bytesForInt(final int number) {
return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( mpw_byteOrder ).putInt( number ).array();
}
@Override
protected byte[] bytesForInt(final UnsignedInteger number) {
return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( mpw_byteOrder ).putInt( number.intValue() ).array();
}
@Override
protected byte[] idForBytes(final byte[] bytes) {
return mpw_hash.of( bytes );
}
} }

View File

@ -19,8 +19,6 @@
package com.lyndir.masterpassword; package com.lyndir.masterpassword;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.primitives.UnsignedInteger;
import com.lyndir.lhunath.opal.system.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -33,21 +31,14 @@ import javax.annotation.Nullable;
*/ */
public class MasterKeyV1 extends MasterKeyV0 { public class MasterKeyV1 extends MasterKeyV0 {
@SuppressWarnings("UnusedDeclaration") @Override
private static final Logger logger = Logger.get( MasterKeyV1.class ); public MasterKey.Version getAlgorithmVersion() {
public MasterKeyV1(final String fullName) { return MasterKey.Version.V1;
super( fullName );
} }
@Override @Override
public Version getAlgorithmVersion() { public String sitePasswordFromTemplate(final byte[] masterKey, final byte[] siteKey, final MPResultType resultType, @Nullable final String resultParam) {
return Version.V1;
}
@Override
protected String sitePasswordFromTemplate(final byte[] siteKey, final MPResultType resultType, @Nullable final String resultParam) {
logger.trc( "-- mpw_siteResult (algorithm: %u)", getAlgorithmVersion().toInt() ); logger.trc( "-- mpw_siteResult (algorithm: %u)", getAlgorithmVersion().toInt() );
logger.trc( "resultType: %d (%s)", resultType.toInt(), resultType.getShortName() ); logger.trc( "resultType: %d (%s)", resultType.toInt(), resultType.getShortName() );

View File

@ -18,11 +18,12 @@
package com.lyndir.masterpassword; package com.lyndir.masterpassword;
import static com.lyndir.masterpassword.MPUtils.*;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.primitives.Bytes; import com.google.common.primitives.Bytes;
import com.google.common.primitives.UnsignedInteger; import com.google.common.primitives.UnsignedInteger;
import com.lyndir.lhunath.opal.system.CodeUtils; import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.lhunath.opal.system.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -34,21 +35,14 @@ import javax.annotation.Nullable;
*/ */
public class MasterKeyV2 extends MasterKeyV1 { public class MasterKeyV2 extends MasterKeyV1 {
@SuppressWarnings("UnusedDeclaration") @Override
private static final Logger logger = Logger.get( MasterKeyV2.class ); public MasterKey.Version getAlgorithmVersion() {
public MasterKeyV2(final String fullName) { return MasterKey.Version.V2;
super( fullName );
} }
@Override @Override
public Version getAlgorithmVersion() { public byte[] siteKey(final byte[] masterKey, final String siteName, UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose,
return Version.V2;
}
@Override
protected byte[] siteKey(final String siteName, UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose,
@Nullable final String keyContext) { @Nullable final String keyContext) {
Preconditions.checkArgument( !siteName.isEmpty() ); Preconditions.checkArgument( !siteName.isEmpty() );
@ -80,7 +74,6 @@ public class MasterKeyV2 extends MasterKeyV1 {
sitePasswordInfo = Bytes.concat( sitePasswordInfo, keyContextLengthBytes, keyContextBytes ); sitePasswordInfo = Bytes.concat( sitePasswordInfo, keyContextLengthBytes, keyContextBytes );
logger.trc( " => siteSalt.id: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) ); logger.trc( " => siteSalt.id: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) );
byte[] masterKey = getKey();
logger.trc( "siteKey: hmac-sha256( masterKey.id=%s, siteSalt )", (Object) idForBytes( masterKey ) ); logger.trc( "siteKey: hmac-sha256( masterKey.id=%s, siteSalt )", (Object) idForBytes( masterKey ) );
byte[] sitePasswordSeedBytes = MasterKeyV0.mpw_digest.of( masterKey, sitePasswordInfo ); byte[] sitePasswordSeedBytes = MasterKeyV0.mpw_digest.of( masterKey, sitePasswordInfo );
logger.trc( " => siteKey.id: %s", (Object) idForBytes( sitePasswordSeedBytes ) ); logger.trc( " => siteKey.id: %s", (Object) idForBytes( sitePasswordSeedBytes ) );

View File

@ -18,14 +18,14 @@
package com.lyndir.masterpassword; package com.lyndir.masterpassword;
import static com.lyndir.masterpassword.MPUtils.idForBytes;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.primitives.Bytes; import com.google.common.primitives.Bytes;
import com.lyndir.lhunath.opal.system.CodeUtils; import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.CharBuffer; import java.nio.CharBuffer;
import java.util.Arrays; import java.util.Arrays;
import javax.annotation.Nullable;
/** /**
@ -36,27 +36,18 @@ import javax.annotation.Nullable;
*/ */
public class MasterKeyV3 extends MasterKeyV2 { public class MasterKeyV3 extends MasterKeyV2 {
@SuppressWarnings("UnusedDeclaration") @Override
private static final Logger logger = Logger.get( MasterKeyV3.class ); public MasterKey.Version getAlgorithmVersion() {
public MasterKeyV3(final String fullName) { return MasterKey.Version.V3;
super( fullName );
} }
@Override @Override
public Version getAlgorithmVersion() { public byte[] deriveKey(final String fullName, final char[] masterPassword) {
return Version.V3;
}
@Nullable
@Override
protected byte[] deriveKey(final char[] masterPassword) {
Preconditions.checkArgument( masterPassword.length > 0 ); Preconditions.checkArgument( masterPassword.length > 0 );
String fullName = getFullName();
byte[] fullNameBytes = fullName.getBytes( MasterKeyV0.mpw_charset ); byte[] fullNameBytes = fullName.getBytes( MasterKeyV0.mpw_charset );
byte[] fullNameLengthBytes = bytesForInt( fullNameBytes.length ); byte[] fullNameLengthBytes = MPUtils.bytesForInt( fullNameBytes.length );
ByteBuffer mpBytesBuf = MasterKeyV0.mpw_charset.encode( CharBuffer.wrap( masterPassword ) ); ByteBuffer mpBytesBuf = MasterKeyV0.mpw_charset.encode( CharBuffer.wrap( masterPassword ) );
logger.trc( "-- mpw_masterKey (algorithm: %u)", getAlgorithmVersion().toInt() ); logger.trc( "-- mpw_masterKey (algorithm: %u)", getAlgorithmVersion().toInt() );
@ -81,7 +72,7 @@ public class MasterKeyV3 extends MasterKeyV2 {
byte[] masterKey = scrypt( masterKeySalt, mpBytes ); // TODO: Why not mpBytesBuf.array()? byte[] masterKey = scrypt( masterKeySalt, mpBytes ); // TODO: Why not mpBytesBuf.array()?
Arrays.fill( masterKeySalt, (byte) 0 ); Arrays.fill( masterKeySalt, (byte) 0 );
Arrays.fill( mpBytes, (byte) 0 ); Arrays.fill( mpBytes, (byte) 0 );
logger.trc( " => masterKey.id: %s", (masterKey == null)? null: (Object) idForBytes( masterKey ) ); logger.trc( " => masterKey.id: %s", (Object) idForBytes( masterKey ) );
return masterKey; return masterKey;
} }

View File

@ -0,0 +1,80 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.model;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import com.lyndir.masterpassword.MPConstant;
import com.lyndir.masterpassword.MasterKey;
import org.joda.time.Instant;
/**
* @author lhunath, 2017-09-20
*/
public class MPFlatMarshaller implements MPMarshaller {
private static final int FORMAT = 1;
@Override
public String marshall(final MPUser user, final MasterKey masterKey, final ContentMode contentMode) {
StringBuilder content = new StringBuilder();
content.append( "# Master Password site export\n" );
content.append( "# " ).append( contentMode.description() ).append( '\n' );
content.append( "# \n" );
content.append( "##\n" );
content.append( "# Format: " ).append( FORMAT ).append( '\n' );
content.append( "# Date: " ).append( MPConstant.dateTimeFormatter.print( new Instant() ) ).append( '\n' );
content.append( "# User Name: " ).append( user.getFullName() ).append( '\n' );
content.append( "# Full Name: " ).append( user.getFullName() ).append( '\n' );
content.append( "# Avatar: " ).append( user.getAvatar() ).append( '\n' );
content.append( "# Key ID: " ).append( user.exportKeyID() ).append( '\n' );
content.append( "# Algorithm: " ).append( MasterKey.Version.CURRENT.toInt() ).append( '\n' );
content.append( "# Default Type: " ).append( user.getDefaultType().getType() ).append( '\n' );
content.append( "# Passwords: " ).append( contentMode.name() ).append( '\n' );
content.append( "##\n" );
content.append( "#\n" );
content.append( "# Last Times Password Login\t Site\tSite\n" );
content.append( "# used used type name\t name\tpassword\n" );
for (final MPSite site : user.getSites()) {
String loginName = site.getLoginContent();
String password = site.getSiteContent();
if (!contentMode.isRedacted()) {
loginName = site.loginFor( masterKey );
password = site.resultFor( masterKey );
}
content.append( strf( "%s %8d %8s %25s\t%25s\t%s\n", //
MPConstant.dateTimeFormatter.print( site.getLastUsed() ), // lastUsed
site.getUses(), // uses
strf( "%d:%d:%d", //
site.getResultType().getType(), // type
site.getAlgorithmVersion().toInt(), // algorithm
site.getSiteCounter().intValue() ), // counter
ifNotNullElse( loginName, "" ), // loginName
site.getSiteName(), // siteName
ifNotNullElse( password, "" ) // password
) );
}
return content.toString();
}
}

View File

@ -0,0 +1,139 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.model;
import com.google.common.base.*;
import com.google.common.io.CharStreams;
import com.google.common.primitives.UnsignedInteger;
import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
import com.lyndir.masterpassword.*;
import java.io.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nonnull;
import org.joda.time.DateTime;
/**
* @author lhunath, 14-12-07
*/
public class MPFlatUnmarshaller implements MPUnmarshaller {
private static final Pattern[] unmarshallFormats = {
Pattern.compile( "^([^ ]+) +(\\d+) +(\\d+)(:\\d+)? +([^\t]+)\t(.*)" ),
Pattern.compile( "^([^ ]+) +(\\d+) +(\\d+)(:\\d+)?(:\\d+)? +([^\t]*)\t *([^\t]+)\t(.*)" ) };
private static final Pattern headerFormat = Pattern.compile( "^#\\s*([^:]+): (.*)" );
private static final Pattern colon = Pattern.compile( ":" );
@Nonnull
@Override
public MPUser unmarshall(@Nonnull final File file)
throws IOException {
try (Reader reader = new InputStreamReader( new FileInputStream( file ), Charsets.UTF_8 )) {
return unmarshall( CharStreams.toString( reader ) );
}
}
@Nonnull
@Override
public MPUser unmarshall(@Nonnull final String content) {
MPUser user = null;
byte[] keyID = null;
String fullName = null;
int mpVersion = 0, importFormat = 0, avatar = 0;
boolean clearContent = false, headerStarted = false;
MPResultType defaultType = MPResultType.DEFAULT;
//noinspection HardcodedLineSeparator
for (final String line : Splitter.on( CharMatcher.anyOf( "\r\n" ) ).omitEmptyStrings().split( content ))
// Header delimitor.
if (line.startsWith( "##" ))
if (!headerStarted)
// Starts the header.
headerStarted = true;
else
// Ends the header.
user = new MPUser( fullName, keyID, MasterKey.Version.fromInt( mpVersion ), avatar, defaultType, new DateTime( 0 ) );
// Comment.
else if (line.startsWith( "#" )) {
if (headerStarted && (user == null)) {
// In header.
Matcher headerMatcher = headerFormat.matcher( line );
if (headerMatcher.matches()) {
String name = headerMatcher.group( 1 ), value = headerMatcher.group( 2 );
if ("Full Name".equalsIgnoreCase( name ) || "User Name".equalsIgnoreCase( name ))
fullName = value;
else if ("Key ID".equalsIgnoreCase( name ))
keyID = CodeUtils.decodeHex( value );
else if ("Algorithm".equalsIgnoreCase( name ))
mpVersion = ConversionUtils.toIntegerNN( value );
else if ("Format".equalsIgnoreCase( name ))
importFormat = ConversionUtils.toIntegerNN( value );
else if ("Avatar".equalsIgnoreCase( name ))
avatar = ConversionUtils.toIntegerNN( value );
else if ("Passwords".equalsIgnoreCase( name ))
clearContent = "visible".equalsIgnoreCase( value );
else if ("Default Type".equalsIgnoreCase( name ))
defaultType = MPResultType.forType( ConversionUtils.toIntegerNN( value ) );
}
}
}
// No comment.
else if (user != null) {
Matcher siteMatcher = unmarshallFormats[importFormat].matcher( line );
if (!siteMatcher.matches())
return null;
MPSite site;
switch (importFormat) {
case 0:
site = new MPSite( user, //
siteMatcher.group( 5 ), siteMatcher.group( 6 ), MPSite.DEFAULT_COUNTER,
MPResultType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),
MasterKey.Version.fromInt( ConversionUtils.toIntegerNN(
colon.matcher( siteMatcher.group( 4 ) ).replaceAll( "" ) ) ),
null, null, null, ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ),
MPConstant.dateTimeFormatter.parseDateTime( siteMatcher.group( 1 ) ).toInstant() );
break;
case 1:
site = new MPSite( user, //
siteMatcher.group( 7 ), siteMatcher.group( 8 ),
UnsignedInteger.valueOf( colon.matcher( siteMatcher.group( 5 ) ).replaceAll( "" ) ),
MPResultType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),
MasterKey.Version.fromInt( ConversionUtils.toIntegerNN(
colon.matcher( siteMatcher.group( 4 ) ).replaceAll( "" ) ) ),
siteMatcher.group( 6 ), MPResultType.GeneratedName, null,
ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ),
MPConstant.dateTimeFormatter.parseDateTime( siteMatcher.group( 1 ) ).toInstant() );
break;
default:
throw new UnsupportedOperationException( "Unexpected format: " + importFormat );
}
user.addSite( site );
}
return Preconditions.checkNotNull( user, "No full header found in import file." );
}
}

View File

@ -0,0 +1,34 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.model;
import com.lyndir.masterpassword.MasterKey;
/**
* @author lhunath, 2017-09-20
*/
public class MPJSONMarshaller implements MPMarshaller {
@Override
public String marshall(final MPUser user, final MasterKey masterKey, final ContentMode contentMode) {
// TODO
return null;
}
}

View File

@ -0,0 +1,45 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.model;
import java.io.File;
import java.io.IOException;
import javax.annotation.Nonnull;
/**
* @author lhunath, 2017-09-20
*/
public class MPJSONUnmarshaller implements MPUnmarshaller {
@Nonnull
@Override
public MPUser unmarshall(@Nonnull final File file)
throws IOException {
// TODO
return null;
}
@Nonnull
@Override
public MPUser unmarshall(@Nonnull final String content) {
// TODO
return null;
}
}

View File

@ -25,11 +25,36 @@ public enum MPMarshalFormat {
/** /**
* Marshal using the line-based plain-text format. * Marshal using the line-based plain-text format.
*/ */
Flat, Flat {
@Override
public MPMarshaller marshaller() {
return new MPFlatMarshaller();
}
@Override
public MPUnmarshaller unmarshaller() {
return new MPFlatUnmarshaller();
}
},
/** /**
* Marshal using the JSON structured format. * Marshal using the JSON structured format.
*/ */
JSON; JSON {
@Override
public MPMarshaller marshaller() {
return new MPJSONMarshaller();
}
public static MPMarshalFormat DEFAULT = JSON; @Override
public MPUnmarshaller unmarshaller() {
return new MPJSONUnmarshaller();
}
};
public static final MPMarshalFormat DEFAULT = JSON;
public abstract MPMarshaller marshaller();
public abstract MPUnmarshaller unmarshaller();
} }

View File

@ -0,0 +1,50 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.model;
import com.lyndir.masterpassword.MasterKey;
/**
* @author lhunath, 14-12-07
*/
public interface MPMarshaller {
String marshall(MPUser user, MasterKey masterKey, ContentMode contentMode);
enum ContentMode {
PROTECTED( "Export of site names and stored passwords (unless device-private) encrypted with the master key." ),
VISIBLE( "Export of site names and passwords in clear-text." );
private final String description;
private boolean redacted;
ContentMode(final String description) {
this.description = description;
}
public String description() {
return description;
}
public boolean isRedacted() {
return redacted;
}
}
}

View File

@ -32,16 +32,25 @@ import org.joda.time.Instant;
*/ */
public class MPSite { public class MPSite {
public static final UnsignedInteger DEFAULT_COUNTER = UnsignedInteger.valueOf( 1 ); public static final UnsignedInteger DEFAULT_COUNTER = UnsignedInteger.ONE;
private final MPUser user; private final MPUser user;
private MasterKey.Version algorithmVersion;
private Instant lastUsed;
private String siteName; private String siteName;
private MPResultType resultType; @Nullable
private String siteContent;
private UnsignedInteger siteCounter; private UnsignedInteger siteCounter;
private int uses; private MPResultType resultType;
private String loginName; private MasterKey.Version algorithmVersion;
@Nullable
private String loginContent;
@Nullable
private MPResultType loginType;
@Nullable
private String url;
private int uses;
private Instant lastUsed;
public MPSite(final MPUser user, final String siteName) { public MPSite(final MPUser user, final String siteName) {
this( user, siteName, DEFAULT_COUNTER, MPResultType.DEFAULT ); this( user, siteName, DEFAULT_COUNTER, MPResultType.DEFAULT );
@ -49,24 +58,28 @@ public class MPSite {
public MPSite(final MPUser user, final String siteName, final UnsignedInteger siteCounter, final MPResultType resultType) { public MPSite(final MPUser user, final String siteName, final UnsignedInteger siteCounter, final MPResultType resultType) {
this.user = user; this.user = user;
this.siteName = siteName;
this.siteCounter = siteCounter;
this.resultType = resultType;
this.algorithmVersion = MasterKey.Version.CURRENT; this.algorithmVersion = MasterKey.Version.CURRENT;
this.lastUsed = new Instant(); this.lastUsed = new Instant();
this.siteName = siteName;
this.resultType = resultType;
this.siteCounter = siteCounter;
} }
protected MPSite(final MPUser user, final MasterKey.Version algorithmVersion, final Instant lastUsed, final String siteName, protected MPSite(final MPUser user, final String siteName, @Nullable final String siteContent, final UnsignedInteger siteCounter,
final MPResultType resultType, final UnsignedInteger siteCounter, final int uses, @Nullable final String loginName, final MPResultType resultType, final MasterKey.Version algorithmVersion,
@Nullable final String importContent) { @Nullable final String loginContent, @Nullable final MPResultType loginType,
@Nullable final String url, final int uses, final Instant lastUsed) {
this.user = user; this.user = user;
this.algorithmVersion = algorithmVersion;
this.lastUsed = lastUsed;
this.siteName = siteName; this.siteName = siteName;
this.resultType = resultType; this.siteContent = siteContent;
this.siteCounter = siteCounter; this.siteCounter = siteCounter;
this.resultType = resultType;
this.algorithmVersion = algorithmVersion;
this.loginContent = loginContent;
this.loginType = loginType;
this.url = url;
this.uses = uses; this.uses = uses;
this.loginName = loginName; this.lastUsed = lastUsed;
} }
public String resultFor(final MasterKey masterKey) { public String resultFor(final MasterKey masterKey) {
@ -74,7 +87,15 @@ public class MPSite {
} }
public String resultFor(final MasterKey masterKey, final MPKeyPurpose purpose, @Nullable final String context) { public String resultFor(final MasterKey masterKey, final MPKeyPurpose purpose, @Nullable final String context) {
return masterKey.siteResult( siteName, siteCounter, purpose, context, resultType, null ); return masterKey.siteResult( siteName, siteCounter, purpose, context, resultType, siteContent, algorithmVersion );
}
public String loginFor(final MasterKey masterKey) {
if (loginType == null)
loginType = MPResultType.GeneratedName;
return masterKey.siteResult( siteName, DEFAULT_COUNTER, MPKeyPurpose.Identification, null, loginType, loginContent,
algorithmVersion );
} }
public MPUser getUser() { public MPUser getUser() {
@ -111,6 +132,11 @@ public class MPSite {
this.siteName = siteName; this.siteName = siteName;
} }
@Nullable
public String getSiteContent() {
return siteContent;
}
public MPResultType getResultType() { public MPResultType getResultType() {
return resultType; return resultType;
} }
@ -135,26 +161,47 @@ public class MPSite {
this.uses = uses; this.uses = uses;
} }
public String getLoginName() { @Nullable
return loginName; public MPResultType getLoginType() {
return loginType;
} }
public void setLoginName(final String loginName) { @Nullable
this.loginName = loginName; public String getLoginContent() {
return loginContent;
}
public void setLoginName(final MasterKey masterKey, @Nullable final MPResultType loginType, @Nullable final String result) {
this.loginType = loginType;
if (this.loginType != null)
if (result == null)
this.loginContent = null;
else
this.loginContent = masterKey.siteState(
siteName, DEFAULT_COUNTER, MPKeyPurpose.Identification, null, this.loginType, result, algorithmVersion );
}
@Nullable
public String getUrl() {
return url;
}
public void setUrl(@Nullable final String url) {
this.url = url;
} }
@Override @Override
public boolean equals(final Object obj) { public boolean equals(final Object obj) {
return (this == obj) || ((obj instanceof MPSite) && Objects.equals( siteName, ((MPSite) obj).siteName )); return (this == obj) || ((obj instanceof MPSite) && Objects.equals( getSiteName(), ((MPSite) obj).getSiteName() ));
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hashCode( siteName ); return Objects.hashCode( getSiteName() );
} }
@Override @Override
public String toString() { public String toString() {
return strf( "{MPSite: %s}", siteName ); return strf( "{MPSite: %s}", getSiteName() );
} }
} }

View File

@ -1,148 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.model;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import com.google.common.base.Preconditions;
import com.lyndir.masterpassword.MasterKey;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.joda.time.Instant;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
/**
* @author lhunath, 14-12-07
*/
public class MPSiteMarshaller {
protected static final DateTimeFormatter rfc3339 = ISODateTimeFormat.dateTimeNoMillis();
private final StringBuilder export = new StringBuilder();
private final ContentMode contentMode = ContentMode.PROTECTED;
private final MasterKey masterKey;
public static MPSiteMarshaller marshallSafe(final MPUser user) {
MPSiteMarshaller marshaller = new MPSiteMarshaller();
marshaller.marshallHeaderForSafeContent( user );
for (final MPSite site : user.getSites())
marshaller.marshallSite( site );
return marshaller;
}
public static MPSiteMarshaller marshallVisible(final MPUser user, final MasterKey masterKey) {
MPSiteMarshaller marshaller = new MPSiteMarshaller();
marshaller.marshallHeaderForVisibleContentWithKey( user, masterKey );
for (final MPSite site : user.getSites())
marshaller.marshallSite( site );
return marshaller;
}
private String marshallHeaderForSafeContent(final MPUser user) {
return marshallHeader( ContentMode.PROTECTED, user, null );
}
private String marshallHeaderForVisibleContentWithKey(final MPUser user, final MasterKey masterKey) {
return marshallHeader( ContentMode.VISIBLE, user, masterKey );
}
private String marshallHeader(final ContentMode contentMode, final MPUser user, @Nullable final MasterKey masterKey) {
this.contentMode = contentMode;
this.masterKey = masterKey;
StringBuilder header = new StringBuilder();
header.append( "# Master Password site export\n" );
header.append( "# " ).append( this.contentMode.description() ).append( '\n' );
header.append( "# \n" );
header.append( "##\n" );
header.append( "# Format: 1\n" );
header.append( "# Date: " ).append( rfc3339.print( new Instant() ) ).append( '\n' );
header.append( "# User 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( "# Key ID: " ).append( user.exportKeyID() ).append( '\n' );
header.append( "# Algorithm: " ).append( MasterKey.Version.CURRENT.toInt() ).append( '\n' );
header.append( "# Default Type: " ).append( user.getDefaultType().getType() ).append( '\n' );
header.append( "# Passwords: " ).append( this.contentMode.name() ).append( '\n' );
header.append( "##\n" );
header.append( "#\n" );
header.append( "# Last Times Password Login\t Site\tSite\n" );
header.append( "# used used type name\t name\tpassword\n" );
export.append( header );
return header.toString();
}
public String marshallSite(final MPSite site) {
String exportLine = strf( "%s %8d %8s %25s\t%25s\t%s", //
rfc3339.print( site.getLastUsed() ), // lastUsed
site.getUses(), // uses
strf( "%d:%d:%d", //
site.getResultType().getType(), // type
site.getAlgorithmVersion().toInt(), // algorithm
site.getSiteCounter().intValue() ), // counter
ifNotNullElse( site.getLoginName(), "" ), // loginName
site.getSiteName(), // siteName
ifNotNullElse( contentMode.contentForSite( site, masterKey ), "" ) // password
);
export.append( exportLine ).append( '\n' );
return exportLine;
}
public String getExport() {
return export.toString();
}
public ContentMode getContentMode() {
return contentMode;
}
public enum ContentMode {
PROTECTED( "Export of site names and stored passwords (unless device-private) encrypted with the master key." ) {
@Override
public String contentForSite(final MPSite site, @Nullable final MasterKey masterKey) {
return site.exportContent();
}
},
VISIBLE( "Export of site names and passwords in clear-text." ) {
@Override
public String contentForSite(final MPSite site, @Nonnull final MasterKey masterKey) {
return site.resultFor( Preconditions.checkNotNull( masterKey, "Master key is required when content mode is VISIBLE." ) );
}
};
private final String description;
ContentMode(final String description) {
this.description = description;
}
public String description() {
return description;
}
public abstract String contentForSite(MPSite site, MasterKey masterKey);
}
}

View File

@ -1,181 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.model;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.io.CharStreams;
import com.google.common.primitives.UnsignedInteger;
import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
import com.lyndir.lhunath.opal.system.util.NNOperation;
import com.lyndir.masterpassword.MPResultType;
import com.lyndir.masterpassword.MasterKey;
import java.io.*;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
/**
* @author lhunath, 14-12-07
*/
public class MPSiteUnmarshaller {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MPSite.class );
private static final DateTimeFormatter rfc3339 = ISODateTimeFormat.dateTimeNoMillis();
private static final Pattern[] unmarshallFormats = {
Pattern.compile( "^([^ ]+) +(\\d+) +(\\d+)(:\\d+)? +([^\t]+)\t(.*)" ),
Pattern.compile( "^([^ ]+) +(\\d+) +(\\d+)(:\\d+)?(:\\d+)? +([^\t]*)\t *([^\t]+)\t(.*)" ) };
private static final Pattern headerFormat = Pattern.compile( "^#\\s*([^:]+): (.*)" );
private final int importFormat;
@SuppressWarnings({ "FieldCanBeLocal", "unused" })
private final int mpVersion;
@SuppressWarnings({ "FieldCanBeLocal", "unused" })
private final boolean clearContent;
private final MPUser user;
@Nonnull
public static MPSiteUnmarshaller unmarshall(@Nonnull final File file)
throws IOException {
try (Reader reader = new InputStreamReader( new FileInputStream( file ), Charsets.UTF_8 )) {
return unmarshall( CharStreams.readLines( reader ) );
}
}
@Nonnull
public static MPSiteUnmarshaller unmarshall(@Nonnull final List<String> lines) {
byte[] keyID = null;
String fullName = null;
int mpVersion = 0, importFormat = 0, avatar = 0;
boolean clearContent = false, headerStarted = false;
MPResultType defaultType = MPResultType.DEFAULT;
MPSiteUnmarshaller marshaller = null;
final ImmutableList.Builder<MPSite> sites = ImmutableList.builder();
for (final String line : lines)
// Header delimitor.
if (line.startsWith( "##" ))
if (!headerStarted)
// Starts the header.
headerStarted = true;
else
// Ends the header.
marshaller = new MPSiteUnmarshaller( importFormat, mpVersion, fullName, keyID, avatar, defaultType, clearContent );
// Comment.
else if (line.startsWith( "#" )) {
if (headerStarted && (marshaller == null)) {
// In header.
Matcher headerMatcher = headerFormat.matcher( line );
if (headerMatcher.matches()) {
String name = headerMatcher.group( 1 ), value = headerMatcher.group( 2 );
if ("Full Name".equalsIgnoreCase( name ) || "User Name".equalsIgnoreCase( name ))
fullName = value;
else if ("Key ID".equalsIgnoreCase( name ))
keyID = CodeUtils.decodeHex( value );
else if ("Algorithm".equalsIgnoreCase( name ))
mpVersion = ConversionUtils.toIntegerNN( value );
else if ("Format".equalsIgnoreCase( name ))
importFormat = ConversionUtils.toIntegerNN( value );
else if ("Avatar".equalsIgnoreCase( name ))
avatar = ConversionUtils.toIntegerNN( value );
else if ("Passwords".equalsIgnoreCase( name ))
clearContent = "visible".equalsIgnoreCase( value );
else if ("Default Type".equalsIgnoreCase( name ))
defaultType = MPResultType.forType( ConversionUtils.toIntegerNN( value ) );
}
}
}
// No comment.
else if (marshaller != null)
ifNotNull( marshaller.unmarshallSite( line ), new NNOperation<MPSite>() {
@Override
public void apply(@Nonnull final MPSite site) {
sites.add( site );
}
} );
return Preconditions.checkNotNull( marshaller, "No full header found in import file." );
}
protected MPSiteUnmarshaller(final int importFormat, final int mpVersion, final String fullName, final byte[] keyID, final int avatar,
final MPResultType defaultType, final boolean clearContent) {
this.importFormat = importFormat;
this.mpVersion = mpVersion;
this.clearContent = clearContent;
user = new MPUser( fullName, keyID, MasterKey.Version.fromInt( mpVersion ), avatar, defaultType, new DateTime( 0 ) );
}
@Nullable
public MPSite unmarshallSite(@Nonnull final String siteLine) {
Matcher siteMatcher = unmarshallFormats[importFormat].matcher( siteLine );
if (!siteMatcher.matches())
return null;
MPSite site;
switch (importFormat) {
case 0:
site = new MPSite( user, //
MasterKey.Version.fromInt( ConversionUtils.toIntegerNN( siteMatcher.group( 4 ).replace( ":", "" ) ) ), //
rfc3339.parseDateTime( siteMatcher.group( 1 ) ).toInstant(), //
siteMatcher.group( 5 ), //
MPResultType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ), MPSite.DEFAULT_COUNTER, //
ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ), //
null, //
siteMatcher.group( 6 ) );
break;
case 1:
site = new MPSite( user, //
MasterKey.Version.fromInt( ConversionUtils.toIntegerNN( siteMatcher.group( 4 ).replace( ":", "" ) ) ), //
rfc3339.parseDateTime( siteMatcher.group( 1 ) ).toInstant(), //
siteMatcher.group( 7 ), //
MPResultType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),
UnsignedInteger.valueOf( siteMatcher.group( 5 ).replace( ":", "" ) ), //
ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ), //
siteMatcher.group( 6 ), //
siteMatcher.group( 8 ) );
break;
default:
throw logger.bug( "Unexpected format: %d", importFormat );
}
user.addSite( site );
return site;
}
public MPUser getUser() {
return user;
}
}

View File

@ -0,0 +1,36 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.model;
import java.io.*;
import javax.annotation.Nonnull;
/**
* @author lhunath, 14-12-07
*/
public interface MPUnmarshaller {
@Nonnull
MPUser unmarshall(@Nonnull File file)
throws IOException;
@Nonnull
MPUser unmarshall(@Nonnull String content);
}

View File

@ -40,18 +40,19 @@ public class MPUser implements Comparable<MPUser> {
private final Collection<MPSite> sites = Sets.newHashSet(); private final Collection<MPSite> sites = Sets.newHashSet();
@Nullable @Nullable
private byte[] keyID; private byte[] keyID;
private final MasterKey.Version algorithmVersion; private MasterKey.Version algorithmVersion;
private int avatar;
private MPResultType defaultType; private int avatar;
private ReadableInstant lastUsed; private MPResultType defaultType;
private ReadableInstant lastUsed;
public MPUser(final String fullName) { public MPUser(final String fullName) {
this( fullName, null ); this( fullName, null, MasterKey.Version.CURRENT );
} }
public MPUser(final String fullName, @Nullable final byte[] keyID) { public MPUser(final String fullName, @Nullable final byte[] keyID, final MasterKey.Version algorithmVersion) {
this( fullName, keyID, MasterKey.Version.CURRENT, 0, MPResultType.DEFAULT, new DateTime() ); this( fullName, keyID, algorithmVersion, 0, MPResultType.DEFAULT, new Instant() );
} }
public MPUser(final String fullName, @Nullable final byte[] keyID, final MasterKey.Version algorithmVersion, final int avatar, public MPUser(final String fullName, @Nullable final byte[] keyID, final MasterKey.Version algorithmVersion, final int avatar,
@ -108,10 +109,10 @@ public class MPUser implements Comparable<MPUser> {
@SuppressWarnings("MethodCanBeVariableArityMethod") @SuppressWarnings("MethodCanBeVariableArityMethod")
public MasterKey authenticate(final char[] masterPassword) public MasterKey authenticate(final char[] masterPassword)
throws IncorrectMasterPasswordException { throws IncorrectMasterPasswordException {
MasterKey masterKey = MasterKey.create( algorithmVersion, getFullName(), masterPassword ); MasterKey masterKey = new MasterKey( getFullName(), masterPassword );
if ((keyID == null) || (keyID.length == 0)) if ((keyID == null) || (keyID.length == 0))
keyID = masterKey.getKeyID(); keyID = masterKey.getKeyID( algorithmVersion );
else if (!Arrays.equals( masterKey.getKeyID(), keyID )) else if (!Arrays.equals( masterKey.getKeyID( algorithmVersion ), keyID ))
throw new IncorrectMasterPasswordException( this ); throw new IncorrectMasterPasswordException( this );
return masterKey; return masterKey;

View File

@ -75,7 +75,7 @@ public class MPUserFileManager extends MPUserManager {
@Override @Override
public MPUser apply(@Nullable final File file) { public MPUser apply(@Nullable final File file) {
try { try {
return MPSiteUnmarshaller.unmarshall( Preconditions.checkNotNull( file ) ).getUser(); return new MPFlatUnmarshaller().unmarshall( Preconditions.checkNotNull( file ) );
} }
catch (final IOException e) { catch (final IOException e) {
logger.err( e, "Couldn't read user from: %s", file ); logger.err( e, "Couldn't read user from: %s", file );
@ -120,7 +120,7 @@ public class MPUserFileManager extends MPUserManager {
File mpsitesFile = new File( userFilesDirectory, user.getFullName() + ".mpsites" ); File mpsitesFile = new File( userFilesDirectory, user.getFullName() + ".mpsites" );
return new OutputStreamWriter( new FileOutputStream( mpsitesFile ), Charsets.UTF_8 ); return new OutputStreamWriter( new FileOutputStream( mpsitesFile ), Charsets.UTF_8 );
} }
}.write( MPSiteMarshaller.marshallSafe( user ).getExport() ); }.write( new MPFlatMarshaller().marshall( user, null/*TODO: masterKey*/, MPMarshaller.ContentMode.PROTECTED ) );
} }
catch (final IOException e) { catch (final IOException e) {
logger.err( e, "Unable to save sites for user: %s", user ); logger.err( e, "Unable to save sites for user: %s", user );

View File

@ -172,10 +172,10 @@ public class MPTestSuite implements Callable<Boolean> {
@Nonnull @Nonnull
@Override @Override
public Boolean apply(@Nonnull final MPTests.Case testCase) { public Boolean apply(@Nonnull final MPTests.Case testCase) {
MasterKey masterKey = MasterKey.create( testCase.getAlgorithm(), testCase.getFullName(), testCase.getMasterPassword() ); MasterKey masterKey = new MasterKey( testCase.getFullName(), testCase.getMasterPassword() );
String sitePassword = masterKey.siteResult( testCase.getSiteName(), testCase.getSiteCounter(), testCase.getKeyPurpose(), String sitePassword = masterKey.siteResult( testCase.getSiteName(), testCase.getSiteCounter(), testCase.getKeyPurpose(),
testCase.getKeyContext(), testCase.getResultType(), testCase.getKeyContext(), testCase.getResultType(),
null ); null, testCase.getAlgorithm() );
return testCase.getResult().equals( sitePassword ); return testCase.getResult().equals( sitePassword );
} }

View File

@ -52,12 +52,12 @@ public class MasterKeyTest {
@Nonnull @Nonnull
@Override @Override
public Boolean apply(@Nonnull final MPTests.Case testCase) { public Boolean apply(@Nonnull final MPTests.Case testCase) {
MasterKey masterKey = MasterKey.create( testCase.getAlgorithm(), testCase.getFullName(), testCase.getMasterPassword() ); MasterKey masterKey = new MasterKey( testCase.getFullName(), testCase.getMasterPassword() );
assertEquals( assertEquals(
masterKey.siteResult( testCase.getSiteName(), testCase.getSiteCounter(), testCase.getKeyPurpose(), masterKey.siteResult( testCase.getSiteName(), testCase.getSiteCounter(), testCase.getKeyPurpose(),
testCase.getKeyContext(), testCase.getResultType(), testCase.getKeyContext(), testCase.getResultType(),
null ), null, testCase.getAlgorithm() ),
testCase.getResult(), "[testEncode] Failed test case: " + testCase ); testCase.getResult(), "[testEncode] Failed test case: " + testCase );
return true; return true;
@ -71,7 +71,7 @@ public class MasterKeyTest {
MPTests.Case defaultCase = testSuite.getTests().getDefaultCase(); MPTests.Case defaultCase = testSuite.getTests().getDefaultCase();
assertEquals( MasterKey.create( defaultCase.getFullName(), defaultCase.getMasterPassword() ).getFullName(), assertEquals( new MasterKey( defaultCase.getFullName(), defaultCase.getMasterPassword() ).getFullName(),
defaultCase.getFullName(), "[testGetUserName] Failed test case: " + defaultCase ); defaultCase.getFullName(), "[testGetUserName] Failed test case: " + defaultCase );
} }
@ -83,32 +83,13 @@ public class MasterKeyTest {
@Nonnull @Nonnull
@Override @Override
public Boolean apply(@Nonnull final MPTests.Case testCase) { public Boolean apply(@Nonnull final MPTests.Case testCase) {
MasterKey masterKey = MasterKey.create( testCase.getFullName(), testCase.getMasterPassword() ); MasterKey masterKey = new MasterKey( testCase.getFullName(), testCase.getMasterPassword() );
assertEquals( CodeUtils.encodeHex( masterKey.getKeyID() ), assertEquals( CodeUtils.encodeHex( masterKey.getKeyID( testCase.getAlgorithm() ) ),
testCase.getKeyID(), "[testGetKeyID] Failed test case: " + testCase ); testCase.getKeyID(), "[testGetKeyID] Failed test case: " + testCase );
return true; return true;
} }
} ); } );
} }
@Test
public void testInvalidate()
throws Exception {
try {
MPTests.Case defaultCase = testSuite.getTests().getDefaultCase();
MasterKey masterKey = MasterKey.create( defaultCase.getFullName(), defaultCase.getMasterPassword() );
masterKey.invalidate();
masterKey.siteResult( defaultCase.getSiteName(), defaultCase.getSiteCounter(), defaultCase.getKeyPurpose(),
defaultCase.getKeyContext(), defaultCase.getResultType(),
null );
fail( "[testInvalidate] Master key should have been invalidated, but was still usable." );
}
catch (final IllegalStateException ignored) {
}
}
} }

View File

@ -33,7 +33,6 @@ import android.view.WindowManager;
import android.widget.*; import android.widget.*;
import butterknife.BindView; import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.primitives.UnsignedInteger; import com.google.common.primitives.UnsignedInteger;
import com.google.common.util.concurrent.*; import com.google.common.util.concurrent.*;
@ -57,7 +56,7 @@ public class EmergencyActivity extends Activity {
private final ImmutableList<MPResultType> allResultTypes = ImmutableList.copyOf( MPResultType.forClass( MPResultTypeClass.Template ) ); private final ImmutableList<MPResultType> allResultTypes = ImmutableList.copyOf( MPResultType.forClass( MPResultTypeClass.Template ) );
private final ImmutableList<MasterKey.Version> allVersions = ImmutableList.copyOf( MasterKey.Version.values() ); private final ImmutableList<MasterKey.Version> allVersions = ImmutableList.copyOf( MasterKey.Version.values() );
private ListenableFuture<MasterKey> masterKeyFuture; private MasterKey masterKey;
@BindView(R.id.progressView) @BindView(R.id.progressView)
ProgressBar progressView; ProgressBar progressView;
@ -97,7 +96,6 @@ public class EmergencyActivity extends Activity {
private int id_userName; private int id_userName;
private int id_masterPassword; private int id_masterPassword;
private int id_version;
private String sitePassword; private String sitePassword;
public static void start(final Context context) { public static void start(final Context context) {
@ -213,7 +211,7 @@ public class EmergencyActivity extends Activity {
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
MasterKey.setAllowNativeByDefault( preferences.isAllowNativeKDF() ); // FIXME: MasterKey.setAllowNativeByDefault( preferences.isAllowNativeKDF() );
fullNameField.setText( preferences.getFullName() ); fullNameField.setText( preferences.getFullName() );
rememberFullNameField.setChecked( preferences.isRememberFullName() ); rememberFullNameField.setChecked( preferences.isRememberFullName() );
@ -241,10 +239,8 @@ public class EmergencyActivity extends Activity {
if (preferences.isForgetPassword()) { if (preferences.isForgetPassword()) {
synchronized (this) { synchronized (this) {
id_userName = id_masterPassword = 0; id_userName = id_masterPassword = 0;
if (masterKeyFuture != null) { if (masterKey != null)
masterKeyFuture.cancel( true ); masterKey = null;
masterKeyFuture = null;
}
masterPasswordField.setText( "" ); masterPasswordField.setText( "" );
} }
@ -260,23 +256,17 @@ public class EmergencyActivity extends Activity {
private synchronized void updateMasterKey() { private synchronized void updateMasterKey() {
final String fullName = fullNameField.getText().toString(); final String fullName = fullNameField.getText().toString();
final char[] masterPassword = masterPasswordField.getText().toString().toCharArray(); final char[] masterPassword = masterPasswordField.getText().toString().toCharArray();
final MasterKey.Version version = (MasterKey.Version) siteVersionButton.getTag();
if ((id_userName == fullName.hashCode()) if ((id_userName == fullName.hashCode())
&& (id_masterPassword == Arrays.hashCode( masterPassword )) && (id_masterPassword == Arrays.hashCode( masterPassword )))
&& (id_version == version.ordinal())) if (masterKey != null)
if ((masterKeyFuture != null) && !masterKeyFuture.isCancelled())
return; return;
id_userName = fullName.hashCode(); id_userName = fullName.hashCode();
id_masterPassword = Arrays.hashCode( masterPassword ); id_masterPassword = Arrays.hashCode( masterPassword );
id_version = version.ordinal();
if (preferences.isRememberFullName()) if (preferences.isRememberFullName())
preferences.setFullName( fullName ); preferences.setFullName( fullName );
if (masterKeyFuture != null)
masterKeyFuture.cancel( true );
if (fullName.isEmpty() || (masterPassword.length == 0)) { if (fullName.isEmpty() || (masterPassword.length == 0)) {
sitePasswordField.setText( "" ); sitePasswordField.setText( "" );
progressView.setVisibility( View.INVISIBLE ); progressView.setVisibility( View.INVISIBLE );
@ -285,43 +275,21 @@ public class EmergencyActivity extends Activity {
sitePasswordField.setText( "" ); sitePasswordField.setText( "" );
progressView.setVisibility( View.VISIBLE ); progressView.setVisibility( View.VISIBLE );
(masterKeyFuture = executor.submit( new Callable<MasterKey>() { masterKey = new MasterKey( fullName, masterPassword );
@Override updateSitePassword();
public MasterKey call()
throws Exception {
try {
return MasterKey.create( version, fullName, masterPassword );
}
catch (final Exception e) {
sitePasswordField.setText( "" );
progressView.setVisibility( View.INVISIBLE );
logger.err( e, "While generating master key." );
throw e;
}
}
} )).addListener( new Runnable() {
@Override
public void run() {
runOnUiThread( new Runnable() {
@Override
public void run() {
updateSitePassword();
}
} );
}
}, executor );
} }
private void updateSitePassword() { private void updateSitePassword() {
final String siteName = siteNameField.getText().toString(); final String siteName = siteNameField.getText().toString();
final MPResultType type = (MPResultType) resultTypeButton.getTag(); final MPResultType type = (MPResultType) resultTypeButton.getTag();
final UnsignedInteger counter = UnsignedInteger.valueOf( siteCounterButton.getText().toString() ); final UnsignedInteger counter = UnsignedInteger.valueOf( siteCounterButton.getText().toString() );
final MasterKey.Version version = (MasterKey.Version) siteVersionButton.getTag();
if ((masterKeyFuture == null) || siteName.isEmpty() || (type == null)) { if ((masterKey == null) || siteName.isEmpty() || (type == null)) {
sitePasswordField.setText( "" ); sitePasswordField.setText( "" );
progressView.setVisibility( View.INVISIBLE ); progressView.setVisibility( View.INVISIBLE );
if (masterKeyFuture == null) if (masterKey == null)
updateMasterKey(); updateMasterKey();
return; return;
} }
@ -332,7 +300,7 @@ public class EmergencyActivity extends Activity {
@Override @Override
public void run() { public void run() {
try { try {
sitePassword = masterKeyFuture.get().siteResult( siteName, counter, MPKeyPurpose.Authentication, null, type, null ); sitePassword = masterKey.siteResult( siteName, counter, MPKeyPurpose.Authentication, null, type, null, version );
runOnUiThread( new Runnable() { runOnUiThread( new Runnable() {
@Override @Override
@ -342,16 +310,6 @@ public class EmergencyActivity extends Activity {
} }
} ); } );
} }
catch (final InterruptedException ignored) {
sitePasswordField.setText( "" );
progressView.setVisibility( View.INVISIBLE );
}
catch (final ExecutionException e) {
sitePasswordField.setText( "" );
progressView.setVisibility( View.INVISIBLE );
logger.err( e, "While generating site password." );
throw Throwables.propagate( e );
}
catch (final RuntimeException e) { catch (final RuntimeException e) {
sitePasswordField.setText( "" ); sitePasswordField.setText( "" );
progressView.setVisibility( View.INVISIBLE ); progressView.setVisibility( View.INVISIBLE );
@ -363,10 +321,9 @@ public class EmergencyActivity extends Activity {
} }
public void integrityTests(final View view) { public void integrityTests(final View view) {
if (masterKeyFuture != null) { if (masterKey != null)
masterKeyFuture.cancel( true ); masterKey = null;
masterKeyFuture = null;
}
TestActivity.startNoSkip( this ); TestActivity.startNoSkip( this );
} }

View File

@ -74,7 +74,7 @@ public final class Preferences {
} }
public boolean isAllowNativeKDF() { public boolean isAllowNativeKDF() {
return prefs().getBoolean( PREF_NATIVE_KDF, MasterKey.isAllowNativeByDefault() ); return prefs().getBoolean( PREF_NATIVE_KDF, true );
} }
public boolean setTestsPassed(final Set<String> value) { public boolean setTestsPassed(final Set<String> value) {

View File

@ -80,7 +80,7 @@ public class TestActivity extends Activity implements MPTestSuite.Listener {
@Override @Override
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) { public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
preferences.setNativeKDFEnabled( isChecked ); preferences.setNativeKDFEnabled( isChecked );
MasterKey.setAllowNativeByDefault( isChecked ); // TODO: MasterKey.setAllowNativeByDefault( isChecked );
} }
} ); } );
@ -122,7 +122,7 @@ public class TestActivity extends Activity implements MPTestSuite.Listener {
if (testFuture != null) if (testFuture != null)
testFuture.cancel( true ); testFuture.cancel( true );
MasterKey.setAllowNativeByDefault( preferences.isAllowNativeKDF() ); // TODO: MasterKey.setAllowNativeByDefault( preferences.isAllowNativeKDF() );
setStatus( R.string.tests_testing, R.string.tests_btn_testing, null ); setStatus( R.string.tests_testing, R.string.tests_btn_testing, null );
Futures.addCallback( testFuture = backgroundExecutor.submit( testSuite ), new FutureCallback<Boolean>() { Futures.addCallback( testFuture = backgroundExecutor.submit( testSuite ), new FutureCallback<Boolean>() {

View File

@ -19,6 +19,7 @@
package com.lyndir.masterpassword.gui.model; package com.lyndir.masterpassword.gui.model;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.lyndir.masterpassword.MasterKey;
import com.lyndir.masterpassword.model.IncorrectMasterPasswordException; import com.lyndir.masterpassword.model.IncorrectMasterPasswordException;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -29,8 +30,6 @@ import javax.annotation.Nullable;
public class IncognitoUser extends User { public class IncognitoUser extends User {
private final String fullName; private final String fullName;
@Nullable
private char[] masterPassword;
public IncognitoUser(final String fullName) { public IncognitoUser(final String fullName) {
this.fullName = fullName; this.fullName = fullName;
@ -41,16 +40,10 @@ public class IncognitoUser extends User {
return fullName; return fullName;
} }
@Nullable
@Override
protected char[] getMasterPassword() {
return masterPassword;
}
@Override @Override
public void authenticate(final char[] masterPassword) public void authenticate(final char[] masterPassword)
throws IncorrectMasterPasswordException { throws IncorrectMasterPasswordException {
this.masterPassword = masterPassword.clone(); this.key = new MasterKey( getFullName(), masterPassword );
} }
@Override @Override

View File

@ -34,9 +34,6 @@ public class ModelUser extends User {
private final MPUser model; private final MPUser model;
@Nullable
private char[] masterPassword;
public ModelUser(final MPUser model) { public ModelUser(final MPUser model) {
this.model = model; this.model = model;
} }
@ -50,12 +47,6 @@ public class ModelUser extends User {
return model.getFullName(); return model.getFullName();
} }
@Nullable
@Override
protected char[] getMasterPassword() {
return masterPassword;
}
@Override @Override
public int getAvatar() { public int getAvatar() {
return model.getAvatar(); return model.getAvatar();
@ -69,21 +60,10 @@ public class ModelUser extends User {
@Override @Override
public void authenticate(final char[] masterPassword) public void authenticate(final char[] masterPassword)
throws IncorrectMasterPasswordException { throws IncorrectMasterPasswordException {
putKey( model.authenticate( masterPassword ) ); key = model.authenticate( masterPassword );
this.masterPassword = masterPassword.clone();
MPUserFileManager.get().save(); MPUserFileManager.get().save();
} }
@Override
public void reset() {
super.reset();
if (masterPassword != null) {
Arrays.fill( masterPassword, (char) 0 );
masterPassword = null;
}
}
@Override @Override
public Iterable<Site> findSitesByName(final String siteName) { public Iterable<Site> findSitesByName(final String siteName) {
return FluentIterable.from( model.findSitesByName( siteName ) ).transform( new Function<MPSiteResult, Site>() { return FluentIterable.from( model.findSitesByName( siteName ) ).transform( new Function<MPSiteResult, Site>() {

View File

@ -19,7 +19,6 @@
package com.lyndir.masterpassword.gui.model; package com.lyndir.masterpassword.gui.model;
import com.google.common.base.Preconditions; 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.IncorrectMasterPasswordException; import com.lyndir.masterpassword.model.IncorrectMasterPasswordException;
import java.util.*; import java.util.*;
@ -32,14 +31,11 @@ import javax.annotation.Nullable;
*/ */
public abstract class User { public abstract class User {
@Nonnull @Nullable
private final Map<MasterKey.Version, MasterKey> keyByVersion = Maps.newEnumMap( MasterKey.Version.class ); protected MasterKey key;
public abstract String getFullName(); public abstract String getFullName();
@Nullable
protected abstract char[] getMasterPassword();
@SuppressWarnings("MethodCanBeVariableArityMethod") @SuppressWarnings("MethodCanBeVariableArityMethod")
public abstract void authenticate(char[] masterPassword) public abstract void authenticate(char[] masterPassword)
throws IncorrectMasterPasswordException; throws IncorrectMasterPasswordException;
@ -49,31 +45,12 @@ public abstract class User {
} }
public boolean isKeyAvailable() { public boolean isKeyAvailable() {
return getMasterPassword() != null; return key != null;
} }
@Nonnull @Nonnull
public MasterKey getKey(final MasterKey.Version algorithmVersion) { public MasterKey getKey() {
char[] masterPassword = Preconditions.checkNotNull( getMasterPassword(), "User is not authenticated: " + getFullName() ); return Preconditions.checkNotNull( key, "User is not authenticated: " + getFullName() );
MasterKey key = keyByVersion.get( algorithmVersion );
if (key == null)
putKey( key = MasterKey.create( algorithmVersion, getFullName(), masterPassword ) );
if (!key.isValid())
key.revalidate( masterPassword );
return key;
}
protected void putKey(final MasterKey masterKey) {
MasterKey oldKey = keyByVersion.put( masterKey.getAlgorithmVersion(), masterKey );
if (oldKey != null)
oldKey.invalidate();
}
public void reset() {
for (final MasterKey key : keyByVersion.values())
key.invalidate();
} }
public abstract Iterable<Site> findSitesByName(String siteName); public abstract Iterable<Site> findSitesByName(String siteName);

View File

@ -257,8 +257,8 @@ public class PasswordFrame extends JFrame implements DocumentListener {
@Override @Override
public String call() public String call()
throws Exception { throws Exception {
return user.getKey( site.getAlgorithmVersion() ) return user.getKey()
.siteResult( site.getSiteName(), site.getSiteCounter(), MPKeyPurpose.Authentication, null, site.getResultType(), null ); .siteResult( site.getSiteName(), site.getSiteCounter(), MPKeyPurpose.Authentication, null, site.getResultType(), null, site.getAlgorithmVersion() );
} }
} ); } );
Futures.addCallback( passwordFuture, new FutureCallback<String>() { Futures.addCallback( passwordFuture, new FutureCallback<String>() {