diff --git a/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPConstant.java b/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPConstant.java
index c96ab251..b4d3cc9b 100644
--- a/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPConstant.java
+++ b/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPConstant.java
@@ -18,6 +18,9 @@
package com.lyndir.masterpassword;
+import org.joda.time.format.DateTimeFormatter;
+import org.joda.time.format.ISODateTimeFormat;
+
/**
* @author lhunath, 2016-10-29
@@ -38,4 +41,6 @@ public final class MPConstant {
/* Algorithm */
public static final int MS_PER_S = 1000;
+
+ public static final DateTimeFormatter dateTimeFormatter = ISODateTimeFormat.dateTimeNoMillis();
}
diff --git a/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPUtils.java b/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPUtils.java
new file mode 100644
index 00000000..b81b6486
--- /dev/null
+++ b/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPUtils.java
@@ -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 .
+//==============================================================================
+
+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 );
+ }
+}
diff --git a/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MasterKey.java b/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MasterKey.java
index b149861c..94f048cc 100644
--- a/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MasterKey.java
+++ b/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MasterKey.java
@@ -18,13 +18,11 @@
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.primitives.UnsignedInteger;
-import com.lyndir.lhunath.opal.system.*;
import com.lyndir.lhunath.opal.system.logging.Logger;
-import java.util.Arrays;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -32,86 +30,25 @@ import javax.annotation.Nullable;
/**
* @author lhunath, 2014-08-30
*/
-public abstract class MasterKey {
+public class MasterKey {
@SuppressWarnings("UnusedDeclaration")
- private static final Logger logger = Logger.get( MasterKey.class );
- private static boolean allowNativeByDefault = true;
+ private static final Logger logger = Logger.get( MasterKey.class );
- @Nonnull
private final String fullName;
- private boolean allowNative = allowNativeByDefault;
+ private final char[] masterPassword;
- @Nullable
- private byte[] masterKey;
-
- @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.
- *
- * 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() );
+ @SuppressWarnings("AssignmentToCollectionOrArrayFieldFromParameter")
+ public MasterKey(final String fullName, final char[] masterPassword) {
this.fullName = fullName;
- logger.trc( "fullName: %s", fullName );
+ this.masterPassword = masterPassword;
}
- /**
- * Derive the master key for a user based on their name and master password.
- *
- * @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);
+ private byte[] getKey(final Version algorithmVersion) {
+ // TODO: Cache keys.
+ return algorithmVersion.getAlgorithm().deriveKey( fullName, masterPassword );
+ }
/**
* Generate a site result token.
@@ -122,16 +59,14 @@ public abstract class MasterKey {
* @param keyContext A site-scoped result modifier.
* @param resultType The type of result to generate.
* @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,
- @Nullable String keyContext, MPResultType resultType, @Nullable String resultParam);
-
- protected abstract String sitePasswordFromTemplate(byte[] siteKey, MPResultType resultType, @Nullable String resultParam);
-
- protected abstract String sitePasswordFromCrypt(byte[] siteKey, MPResultType resultType, @Nullable String resultParam);
-
- protected abstract String sitePasswordFromDerive(byte[] siteKey, MPResultType resultType, @Nullable String resultParam);
+ public String siteResult(final String siteName, final UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose,
+ @Nullable final String keyContext, final MPResultType resultType, @Nullable final String resultParam,
+ final Version algorithmVersion) {
+ return algorithmVersion.getAlgorithm().siteResult(
+ getKey( algorithmVersion ), siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam );
+ }
/**
* Encrypt a stateful site token for persistence.
@@ -142,12 +77,14 @@ public abstract class MasterKey {
* @param keyContext A site-scoped key modifier.
* @param resultType The type of result token to encrypt.
* @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,
- @Nullable String keyContext, MPResultType resultType, @Nullable String resultParam);
-
- public abstract Version getAlgorithmVersion();
+ public String siteState(final String siteName, final UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose,
+ @Nullable final String keyContext, final MPResultType resultType, @Nullable final String resultParam,
+ final Version algorithmVersion) {
+ return algorithmVersion.getAlgorithm().siteState(
+ getKey( algorithmVersion ), siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam );
+ }
@Nonnull
public String getFullName() {
@@ -155,63 +92,11 @@ public abstract class MasterKey {
return fullName;
}
- public boolean isAllowNative() {
- return allowNative;
+ public byte[] getKeyID(final Version algorithmVersion) {
+
+ 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 {
/**
* 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 full names.
*/
- V0,
+ V0( new MasterKeyV0() ),
/**
* bugs:
* - miscounted the byte-length for multi-byte site names.
* - miscounted the byte-length for multi-byte full names.
*/
- V1,
+ V1( new MasterKeyV1() ),
/**
* bugs:
* - miscounted the byte-length for multi-byte full names.
*/
- V2,
+ V2( new MasterKeyV2() ),
/**
* bugs:
* - no known issues.
*/
- V3;
+ V3( new MasterKeyV3() );
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) {
return values()[algorithmVersion];
diff --git a/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MasterKeyAlgorithm.java b/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MasterKeyAlgorithm.java
new file mode 100644
index 00000000..9bbc4763
--- /dev/null
+++ b/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MasterKeyAlgorithm.java
@@ -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 .
+//==============================================================================
+
+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);
+}
diff --git a/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MasterKeyV0.java b/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MasterKeyV0.java
index 1a2cc1ac..505d73a8 100644
--- a/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MasterKeyV0.java
+++ b/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MasterKeyV0.java
@@ -18,6 +18,8 @@
package com.lyndir.masterpassword;
+import static com.lyndir.masterpassword.MPUtils.*;
+
import com.google.common.base.*;
import com.google.common.primitives.Bytes;
import com.google.common.primitives.UnsignedInteger;
@@ -36,14 +38,11 @@ import javax.crypto.IllegalBlockSizeException;
/**
- * bugs:
- * - 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.
+ * @see MasterKey.Version#V0
*
* @author lhunath, 2014-08-30
*/
-public class MasterKeyV0 extends MasterKey {
+public class MasterKeyV0 implements MasterKeyAlgorithm {
/**
* mpw: validity for the time-based rolling counter.
@@ -82,25 +81,18 @@ public class MasterKeyV0 extends MasterKey {
*/
protected static final int scrypt_N = 32768;
- @SuppressWarnings("UnusedDeclaration")
- private static final Logger logger = Logger.get( MasterKeyV0.class );
+ protected final Logger logger = Logger.get( getClass() );
- public MasterKeyV0(final String fullName) {
- super( fullName );
+ @Override
+ public MasterKey.Version getAlgorithmVersion() {
+
+ return MasterKey.Version.V0;
}
@Override
- public Version getAlgorithmVersion() {
-
- return Version.V0;
- }
-
- @Nullable
- @Override
- protected byte[] deriveKey(final char[] masterPassword) {
+ public byte[] deriveKey(final String fullName, final char[] masterPassword) {
Preconditions.checkArgument( masterPassword.length > 0 );
- String fullName = getFullName();
byte[] fullNameBytes = fullName.getBytes( mpw_charset );
byte[] fullNameLengthBytes = bytesForInt( fullName.length() );
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()?
Arrays.fill( masterKeySalt, (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;
}
- @Nullable
protected byte[] scrypt(final byte[] masterKeySalt, final byte[] mpBytes) {
try {
- if (isAllowNative())
+// if (isAllowNative())
return SCrypt.scrypt( mpBytes, masterKeySalt, scrypt_N, scrypt_r, scrypt_p, mpw_dkLen );
- else
- return SCrypt.scryptJ( mpBytes, masterKeySalt, scrypt_N, scrypt_r, scrypt_p, mpw_dkLen );
+// else
+// return SCrypt.scryptJ( mpBytes, masterKeySalt, scrypt_N, scrypt_r, scrypt_p, mpw_dkLen );
}
catch (final GeneralSecurityException e) {
- logger.bug( e );
- return null;
+ throw logger.bug( e );
}
}
@Override
- protected byte[] siteKey(final String siteName, UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose,
- @Nullable final String keyContext) {
+ public byte[] siteKey(final byte[] masterKey, final String siteName, UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose,
+ @Nullable final String keyContext) {
Preconditions.checkArgument( !siteName.isEmpty() );
logger.trc( "-- mpw_siteKey (algorithm: %u)", getAlgorithmVersion().toInt() );
@@ -179,7 +169,6 @@ public class MasterKeyV0 extends MasterKey {
sitePasswordInfo = Bytes.concat( sitePasswordInfo, keyContextLengthBytes, keyContextBytes );
logger.trc( " => siteSalt.id: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) );
- byte[] masterKey = getKey();
logger.trc( "siteKey: hmac-sha256( masterKey.id=%s, siteSalt )", (Object) idForBytes( masterKey ) );
byte[] sitePasswordSeedBytes = mpw_digest.of( masterKey, sitePasswordInfo );
logger.trc( " => siteKey.id: %s", (Object) idForBytes( sitePasswordSeedBytes ) );
@@ -188,10 +177,10 @@ public class MasterKeyV0 extends MasterKey {
}
@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) {
- 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( "resultType: %d (%s)", resultType.toInt(), resultType.getShortName() );
@@ -199,18 +188,18 @@ public class MasterKeyV0 extends MasterKey {
switch (resultType.getTypeClass()) {
case Template:
- return sitePasswordFromTemplate( siteKey, resultType, resultParam );
+ return sitePasswordFromTemplate( masterKey, siteKey, resultType, resultParam );
case Stateful:
- return sitePasswordFromCrypt( siteKey, resultType, resultParam );
+ return sitePasswordFromCrypt( masterKey, siteKey, resultType, resultParam );
case Derive:
- return sitePasswordFromDerive( siteKey, resultType, resultParam );
+ return sitePasswordFromDerive( masterKey, siteKey, resultType, resultParam );
}
throw logger.bug( "Unsupported result type class: %s", resultType.getTypeClass() );
}
@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];
for (int i = 0; i < siteKey.length; ++i) {
@@ -244,7 +233,7 @@ public class MasterKeyV0 extends MasterKey {
}
@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.checkArgument( !resultParam.isEmpty() );
@@ -255,7 +244,7 @@ public class MasterKeyV0 extends MasterKey {
logger.trc( "b64 decoded: %zu bytes = %s", cipherBuf.length, CodeUtils.encodeHex( cipherBuf ) );
// Decrypt
- byte[] plainBuf = CryptUtils.decrypt( cipherBuf, getKey(), true );
+ byte[] plainBuf = CryptUtils.decrypt( cipherBuf, masterKey, true );
String plainText = mpw_charset.decode( ByteBuffer.wrap( plainBuf ) ).toString();
logger.trc( "decrypted -> plainText: %s", plainText );
@@ -267,7 +256,7 @@ public class MasterKeyV0 extends MasterKey {
}
@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) {
Preconditions.checkNotNull( resultParam );
@@ -296,7 +285,7 @@ public class MasterKeyV0 extends MasterKey {
}
@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) {
Preconditions.checkNotNull( resultParam );
@@ -305,7 +294,7 @@ public class MasterKeyV0 extends MasterKey {
try {
// Encrypt
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 ) );
// Base64-encode
@@ -318,19 +307,4 @@ public class MasterKeyV0 extends MasterKey {
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 );
- }
}
diff --git a/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MasterKeyV1.java b/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MasterKeyV1.java
index 60faca07..94c29963 100644
--- a/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MasterKeyV1.java
+++ b/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MasterKeyV1.java
@@ -19,8 +19,6 @@
package com.lyndir.masterpassword;
import com.google.common.base.Preconditions;
-import com.google.common.primitives.UnsignedInteger;
-import com.lyndir.lhunath.opal.system.logging.Logger;
import javax.annotation.Nullable;
@@ -33,21 +31,14 @@ import javax.annotation.Nullable;
*/
public class MasterKeyV1 extends MasterKeyV0 {
- @SuppressWarnings("UnusedDeclaration")
- private static final Logger logger = Logger.get( MasterKeyV1.class );
+ @Override
+ public MasterKey.Version getAlgorithmVersion() {
- public MasterKeyV1(final String fullName) {
- super( fullName );
+ return MasterKey.Version.V1;
}
@Override
- public Version getAlgorithmVersion() {
-
- return Version.V1;
- }
-
- @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) {
logger.trc( "-- mpw_siteResult (algorithm: %u)", getAlgorithmVersion().toInt() );
logger.trc( "resultType: %d (%s)", resultType.toInt(), resultType.getShortName() );
diff --git a/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MasterKeyV2.java b/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MasterKeyV2.java
index 9ad93303..08989fa0 100644
--- a/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MasterKeyV2.java
+++ b/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MasterKeyV2.java
@@ -18,11 +18,12 @@
package com.lyndir.masterpassword;
+import static com.lyndir.masterpassword.MPUtils.*;
+
import com.google.common.base.Preconditions;
import com.google.common.primitives.Bytes;
import com.google.common.primitives.UnsignedInteger;
import com.lyndir.lhunath.opal.system.CodeUtils;
-import com.lyndir.lhunath.opal.system.logging.Logger;
import javax.annotation.Nullable;
@@ -34,21 +35,14 @@ import javax.annotation.Nullable;
*/
public class MasterKeyV2 extends MasterKeyV1 {
- @SuppressWarnings("UnusedDeclaration")
- private static final Logger logger = Logger.get( MasterKeyV2.class );
+ @Override
+ public MasterKey.Version getAlgorithmVersion() {
- public MasterKeyV2(final String fullName) {
- super( fullName );
+ return MasterKey.Version.V2;
}
@Override
- public Version getAlgorithmVersion() {
-
- return Version.V2;
- }
-
- @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) {
Preconditions.checkArgument( !siteName.isEmpty() );
@@ -80,7 +74,6 @@ public class MasterKeyV2 extends MasterKeyV1 {
sitePasswordInfo = Bytes.concat( sitePasswordInfo, keyContextLengthBytes, keyContextBytes );
logger.trc( " => siteSalt.id: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) );
- byte[] masterKey = getKey();
logger.trc( "siteKey: hmac-sha256( masterKey.id=%s, siteSalt )", (Object) idForBytes( masterKey ) );
byte[] sitePasswordSeedBytes = MasterKeyV0.mpw_digest.of( masterKey, sitePasswordInfo );
logger.trc( " => siteKey.id: %s", (Object) idForBytes( sitePasswordSeedBytes ) );
diff --git a/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MasterKeyV3.java b/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MasterKeyV3.java
index 7df1fb93..e4362d25 100644
--- a/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MasterKeyV3.java
+++ b/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MasterKeyV3.java
@@ -18,14 +18,14 @@
package com.lyndir.masterpassword;
+import static com.lyndir.masterpassword.MPUtils.idForBytes;
+
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 java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.util.Arrays;
-import javax.annotation.Nullable;
/**
@@ -36,27 +36,18 @@ import javax.annotation.Nullable;
*/
public class MasterKeyV3 extends MasterKeyV2 {
- @SuppressWarnings("UnusedDeclaration")
- private static final Logger logger = Logger.get( MasterKeyV3.class );
+ @Override
+ public MasterKey.Version getAlgorithmVersion() {
- public MasterKeyV3(final String fullName) {
- super( fullName );
+ return MasterKey.Version.V3;
}
@Override
- public Version getAlgorithmVersion() {
-
- return Version.V3;
- }
-
- @Nullable
- @Override
- protected byte[] deriveKey(final char[] masterPassword) {
+ public byte[] deriveKey(final String fullName, final char[] masterPassword) {
Preconditions.checkArgument( masterPassword.length > 0 );
- String fullName = getFullName();
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 ) );
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()?
Arrays.fill( masterKeySalt, (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;
}
diff --git a/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPFlatMarshaller.java b/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPFlatMarshaller.java
new file mode 100644
index 00000000..6344a8ef
--- /dev/null
+++ b/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPFlatMarshaller.java
@@ -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 .
+//==============================================================================
+
+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();
+ }
+}
diff --git a/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPFlatUnmarshaller.java b/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPFlatUnmarshaller.java
new file mode 100644
index 00000000..bc97ab14
--- /dev/null
+++ b/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPFlatUnmarshaller.java
@@ -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 .
+//==============================================================================
+
+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." );
+ }
+}
diff --git a/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPJSONMarshaller.java b/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPJSONMarshaller.java
new file mode 100644
index 00000000..537edffc
--- /dev/null
+++ b/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPJSONMarshaller.java
@@ -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 .
+//==============================================================================
+
+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;
+ }
+}
diff --git a/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPJSONUnmarshaller.java b/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPJSONUnmarshaller.java
new file mode 100644
index 00000000..376700d9
--- /dev/null
+++ b/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPJSONUnmarshaller.java
@@ -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 .
+//==============================================================================
+
+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;
+ }
+}
diff --git a/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPMarshalFormat.java b/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPMarshalFormat.java
index 77111257..0788ff87 100644
--- a/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPMarshalFormat.java
+++ b/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPMarshalFormat.java
@@ -25,11 +25,36 @@ public enum MPMarshalFormat {
/**
* 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.
*/
- 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();
}
diff --git a/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPMarshaller.java b/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPMarshaller.java
new file mode 100644
index 00000000..f4195529
--- /dev/null
+++ b/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPMarshaller.java
@@ -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 .
+//==============================================================================
+
+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;
+ }
+ }
+}
diff --git a/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPSite.java b/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPSite.java
index 8b8a6b6c..ca9e56c3 100644
--- a/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPSite.java
+++ b/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPSite.java
@@ -32,16 +32,25 @@ import org.joda.time.Instant;
*/
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 MasterKey.Version algorithmVersion;
- private Instant lastUsed;
private String siteName;
- private MPResultType resultType;
+ @Nullable
+ private String siteContent;
private UnsignedInteger siteCounter;
- private int uses;
- private String loginName;
+ private MPResultType resultType;
+ 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) {
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) {
this.user = user;
+ this.siteName = siteName;
+ this.siteCounter = siteCounter;
+ this.resultType = resultType;
this.algorithmVersion = MasterKey.Version.CURRENT;
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,
- final MPResultType resultType, final UnsignedInteger siteCounter, final int uses, @Nullable final String loginName,
- @Nullable final String importContent) {
+ protected MPSite(final MPUser user, final String siteName, @Nullable final String siteContent, final UnsignedInteger siteCounter,
+ final MPResultType resultType, final MasterKey.Version algorithmVersion,
+ @Nullable final String loginContent, @Nullable final MPResultType loginType,
+ @Nullable final String url, final int uses, final Instant lastUsed) {
this.user = user;
- this.algorithmVersion = algorithmVersion;
- this.lastUsed = lastUsed;
this.siteName = siteName;
- this.resultType = resultType;
+ this.siteContent = siteContent;
this.siteCounter = siteCounter;
+ this.resultType = resultType;
+ this.algorithmVersion = algorithmVersion;
+ this.loginContent = loginContent;
+ this.loginType = loginType;
+ this.url = url;
this.uses = uses;
- this.loginName = loginName;
+ this.lastUsed = lastUsed;
}
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) {
- 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() {
@@ -111,6 +132,11 @@ public class MPSite {
this.siteName = siteName;
}
+ @Nullable
+ public String getSiteContent() {
+ return siteContent;
+ }
+
public MPResultType getResultType() {
return resultType;
}
@@ -135,26 +161,47 @@ public class MPSite {
this.uses = uses;
}
- public String getLoginName() {
- return loginName;
+ @Nullable
+ public MPResultType getLoginType() {
+ return loginType;
}
- public void setLoginName(final String loginName) {
- this.loginName = loginName;
+ @Nullable
+ 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
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
public int hashCode() {
- return Objects.hashCode( siteName );
+ return Objects.hashCode( getSiteName() );
}
@Override
public String toString() {
- return strf( "{MPSite: %s}", siteName );
+ return strf( "{MPSite: %s}", getSiteName() );
}
}
diff --git a/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPSiteMarshaller.java b/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPSiteMarshaller.java
deleted file mode 100644
index c5adc20f..00000000
--- a/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPSiteMarshaller.java
+++ /dev/null
@@ -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 .
-//==============================================================================
-
-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);
- }
-}
diff --git a/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPSiteUnmarshaller.java b/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPSiteUnmarshaller.java
deleted file mode 100644
index 9c23c479..00000000
--- a/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPSiteUnmarshaller.java
+++ /dev/null
@@ -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 .
-//==============================================================================
-
-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 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 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() {
- @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;
- }
-}
diff --git a/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPUnmarshaller.java b/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPUnmarshaller.java
new file mode 100644
index 00000000..3b0dec65
--- /dev/null
+++ b/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPUnmarshaller.java
@@ -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 .
+//==============================================================================
+
+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);
+}
diff --git a/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPUser.java b/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPUser.java
index 7cd4ea26..00e7a76a 100755
--- a/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPUser.java
+++ b/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPUser.java
@@ -40,18 +40,19 @@ public class MPUser implements Comparable {
private final Collection sites = Sets.newHashSet();
@Nullable
- private byte[] keyID;
- private final MasterKey.Version algorithmVersion;
- private int avatar;
- private MPResultType defaultType;
- private ReadableInstant lastUsed;
+ private byte[] keyID;
+ private MasterKey.Version algorithmVersion;
+
+ private int avatar;
+ private MPResultType defaultType;
+ private ReadableInstant lastUsed;
public MPUser(final String fullName) {
- this( fullName, null );
+ this( fullName, null, MasterKey.Version.CURRENT );
}
- public MPUser(final String fullName, @Nullable final byte[] keyID) {
- this( fullName, keyID, MasterKey.Version.CURRENT, 0, MPResultType.DEFAULT, new DateTime() );
+ public MPUser(final String fullName, @Nullable final byte[] keyID, final MasterKey.Version algorithmVersion) {
+ 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,
@@ -108,10 +109,10 @@ public class MPUser implements Comparable {
@SuppressWarnings("MethodCanBeVariableArityMethod")
public MasterKey authenticate(final char[] masterPassword)
throws IncorrectMasterPasswordException {
- MasterKey masterKey = MasterKey.create( algorithmVersion, getFullName(), masterPassword );
+ MasterKey masterKey = new MasterKey( getFullName(), masterPassword );
if ((keyID == null) || (keyID.length == 0))
- keyID = masterKey.getKeyID();
- else if (!Arrays.equals( masterKey.getKeyID(), keyID ))
+ keyID = masterKey.getKeyID( algorithmVersion );
+ else if (!Arrays.equals( masterKey.getKeyID( algorithmVersion ), keyID ))
throw new IncorrectMasterPasswordException( this );
return masterKey;
diff --git a/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPUserFileManager.java b/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPUserFileManager.java
index 0844eeaa..0ebbe1af 100644
--- a/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPUserFileManager.java
+++ b/core/java/model/src/main/java/com/lyndir/masterpassword/model/MPUserFileManager.java
@@ -75,7 +75,7 @@ public class MPUserFileManager extends MPUserManager {
@Override
public MPUser apply(@Nullable final File file) {
try {
- return MPSiteUnmarshaller.unmarshall( Preconditions.checkNotNull( file ) ).getUser();
+ return new MPFlatUnmarshaller().unmarshall( Preconditions.checkNotNull( file ) );
}
catch (final IOException e) {
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" );
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) {
logger.err( e, "Unable to save sites for user: %s", user );
diff --git a/core/java/tests/src/main/java/com/lyndir/masterpassword/MPTestSuite.java b/core/java/tests/src/main/java/com/lyndir/masterpassword/MPTestSuite.java
index 4af802d0..7036b75b 100644
--- a/core/java/tests/src/main/java/com/lyndir/masterpassword/MPTestSuite.java
+++ b/core/java/tests/src/main/java/com/lyndir/masterpassword/MPTestSuite.java
@@ -172,10 +172,10 @@ public class MPTestSuite implements Callable {
@Nonnull
@Override
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(),
testCase.getKeyContext(), testCase.getResultType(),
- null );
+ null, testCase.getAlgorithm() );
return testCase.getResult().equals( sitePassword );
}
diff --git a/core/java/tests/src/test/java/com/lyndir/masterpassword/MasterKeyTest.java b/core/java/tests/src/test/java/com/lyndir/masterpassword/MasterKeyTest.java
index 82144f3c..1063d116 100644
--- a/core/java/tests/src/test/java/com/lyndir/masterpassword/MasterKeyTest.java
+++ b/core/java/tests/src/test/java/com/lyndir/masterpassword/MasterKeyTest.java
@@ -52,12 +52,12 @@ public class MasterKeyTest {
@Nonnull
@Override
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(
masterKey.siteResult( testCase.getSiteName(), testCase.getSiteCounter(), testCase.getKeyPurpose(),
testCase.getKeyContext(), testCase.getResultType(),
- null ),
+ null, testCase.getAlgorithm() ),
testCase.getResult(), "[testEncode] Failed test case: " + testCase );
return true;
@@ -71,7 +71,7 @@ public class MasterKeyTest {
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 );
}
@@ -83,32 +83,13 @@ public class MasterKeyTest {
@Nonnull
@Override
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 );
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) {
- }
- }
}
diff --git a/platform-android/src/main/java/com/lyndir/masterpassword/EmergencyActivity.java b/platform-android/src/main/java/com/lyndir/masterpassword/EmergencyActivity.java
index e6725126..4ad71dba 100644
--- a/platform-android/src/main/java/com/lyndir/masterpassword/EmergencyActivity.java
+++ b/platform-android/src/main/java/com/lyndir/masterpassword/EmergencyActivity.java
@@ -33,7 +33,6 @@ import android.view.WindowManager;
import android.widget.*;
import butterknife.BindView;
import butterknife.ButterKnife;
-import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.primitives.UnsignedInteger;
import com.google.common.util.concurrent.*;
@@ -57,7 +56,7 @@ public class EmergencyActivity extends Activity {
private final ImmutableList allResultTypes = ImmutableList.copyOf( MPResultType.forClass( MPResultTypeClass.Template ) );
private final ImmutableList allVersions = ImmutableList.copyOf( MasterKey.Version.values() );
- private ListenableFuture masterKeyFuture;
+ private MasterKey masterKey;
@BindView(R.id.progressView)
ProgressBar progressView;
@@ -97,7 +96,6 @@ public class EmergencyActivity extends Activity {
private int id_userName;
private int id_masterPassword;
- private int id_version;
private String sitePassword;
public static void start(final Context context) {
@@ -213,7 +211,7 @@ public class EmergencyActivity extends Activity {
protected void onResume() {
super.onResume();
- MasterKey.setAllowNativeByDefault( preferences.isAllowNativeKDF() );
+// FIXME: MasterKey.setAllowNativeByDefault( preferences.isAllowNativeKDF() );
fullNameField.setText( preferences.getFullName() );
rememberFullNameField.setChecked( preferences.isRememberFullName() );
@@ -241,10 +239,8 @@ public class EmergencyActivity extends Activity {
if (preferences.isForgetPassword()) {
synchronized (this) {
id_userName = id_masterPassword = 0;
- if (masterKeyFuture != null) {
- masterKeyFuture.cancel( true );
- masterKeyFuture = null;
- }
+ if (masterKey != null)
+ masterKey = null;
masterPasswordField.setText( "" );
}
@@ -260,23 +256,17 @@ public class EmergencyActivity extends Activity {
private synchronized void updateMasterKey() {
final String fullName = fullNameField.getText().toString();
final char[] masterPassword = masterPasswordField.getText().toString().toCharArray();
- final MasterKey.Version version = (MasterKey.Version) siteVersionButton.getTag();
if ((id_userName == fullName.hashCode())
- && (id_masterPassword == Arrays.hashCode( masterPassword ))
- && (id_version == version.ordinal()))
- if ((masterKeyFuture != null) && !masterKeyFuture.isCancelled())
+ && (id_masterPassword == Arrays.hashCode( masterPassword )))
+ if (masterKey != null)
return;
id_userName = fullName.hashCode();
id_masterPassword = Arrays.hashCode( masterPassword );
- id_version = version.ordinal();
if (preferences.isRememberFullName())
preferences.setFullName( fullName );
- if (masterKeyFuture != null)
- masterKeyFuture.cancel( true );
-
if (fullName.isEmpty() || (masterPassword.length == 0)) {
sitePasswordField.setText( "" );
progressView.setVisibility( View.INVISIBLE );
@@ -285,43 +275,21 @@ public class EmergencyActivity extends Activity {
sitePasswordField.setText( "" );
progressView.setVisibility( View.VISIBLE );
- (masterKeyFuture = executor.submit( new Callable() {
- @Override
- 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 );
+ masterKey = new MasterKey( fullName, masterPassword );
+ updateSitePassword();
}
private void updateSitePassword() {
final String siteName = siteNameField.getText().toString();
final MPResultType type = (MPResultType) resultTypeButton.getTag();
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( "" );
progressView.setVisibility( View.INVISIBLE );
- if (masterKeyFuture == null)
+ if (masterKey == null)
updateMasterKey();
return;
}
@@ -332,7 +300,7 @@ public class EmergencyActivity extends Activity {
@Override
public void run() {
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() {
@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) {
sitePasswordField.setText( "" );
progressView.setVisibility( View.INVISIBLE );
@@ -363,10 +321,9 @@ public class EmergencyActivity extends Activity {
}
public void integrityTests(final View view) {
- if (masterKeyFuture != null) {
- masterKeyFuture.cancel( true );
- masterKeyFuture = null;
- }
+ if (masterKey != null)
+ masterKey = null;
+
TestActivity.startNoSkip( this );
}
diff --git a/platform-android/src/main/java/com/lyndir/masterpassword/Preferences.java b/platform-android/src/main/java/com/lyndir/masterpassword/Preferences.java
index f3a99b37..7e8801d6 100644
--- a/platform-android/src/main/java/com/lyndir/masterpassword/Preferences.java
+++ b/platform-android/src/main/java/com/lyndir/masterpassword/Preferences.java
@@ -74,7 +74,7 @@ public final class Preferences {
}
public boolean isAllowNativeKDF() {
- return prefs().getBoolean( PREF_NATIVE_KDF, MasterKey.isAllowNativeByDefault() );
+ return prefs().getBoolean( PREF_NATIVE_KDF, true );
}
public boolean setTestsPassed(final Set value) {
diff --git a/platform-android/src/main/java/com/lyndir/masterpassword/TestActivity.java b/platform-android/src/main/java/com/lyndir/masterpassword/TestActivity.java
index d388cb30..9c3f22b5 100644
--- a/platform-android/src/main/java/com/lyndir/masterpassword/TestActivity.java
+++ b/platform-android/src/main/java/com/lyndir/masterpassword/TestActivity.java
@@ -80,7 +80,7 @@ public class TestActivity extends Activity implements MPTestSuite.Listener {
@Override
public void onCheckedChanged(final CompoundButton buttonView, final boolean 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)
testFuture.cancel( true );
- MasterKey.setAllowNativeByDefault( preferences.isAllowNativeKDF() );
+ // TODO: MasterKey.setAllowNativeByDefault( preferences.isAllowNativeKDF() );
setStatus( R.string.tests_testing, R.string.tests_btn_testing, null );
Futures.addCallback( testFuture = backgroundExecutor.submit( testSuite ), new FutureCallback() {
diff --git a/platform-independent/gui-java/src/main/java/com/lyndir/masterpassword/gui/model/IncognitoUser.java b/platform-independent/gui-java/src/main/java/com/lyndir/masterpassword/gui/model/IncognitoUser.java
index ca5d3f99..0feae51f 100755
--- a/platform-independent/gui-java/src/main/java/com/lyndir/masterpassword/gui/model/IncognitoUser.java
+++ b/platform-independent/gui-java/src/main/java/com/lyndir/masterpassword/gui/model/IncognitoUser.java
@@ -19,6 +19,7 @@
package com.lyndir.masterpassword.gui.model;
import com.google.common.collect.ImmutableList;
+import com.lyndir.masterpassword.MasterKey;
import com.lyndir.masterpassword.model.IncorrectMasterPasswordException;
import javax.annotation.Nullable;
@@ -29,8 +30,6 @@ import javax.annotation.Nullable;
public class IncognitoUser extends User {
private final String fullName;
- @Nullable
- private char[] masterPassword;
public IncognitoUser(final String fullName) {
this.fullName = fullName;
@@ -41,16 +40,10 @@ public class IncognitoUser extends User {
return fullName;
}
- @Nullable
- @Override
- protected char[] getMasterPassword() {
- return masterPassword;
- }
-
@Override
public void authenticate(final char[] masterPassword)
throws IncorrectMasterPasswordException {
- this.masterPassword = masterPassword.clone();
+ this.key = new MasterKey( getFullName(), masterPassword );
}
@Override
diff --git a/platform-independent/gui-java/src/main/java/com/lyndir/masterpassword/gui/model/ModelUser.java b/platform-independent/gui-java/src/main/java/com/lyndir/masterpassword/gui/model/ModelUser.java
index 5c763261..094830d5 100755
--- a/platform-independent/gui-java/src/main/java/com/lyndir/masterpassword/gui/model/ModelUser.java
+++ b/platform-independent/gui-java/src/main/java/com/lyndir/masterpassword/gui/model/ModelUser.java
@@ -34,9 +34,6 @@ public class ModelUser extends User {
private final MPUser model;
- @Nullable
- private char[] masterPassword;
-
public ModelUser(final MPUser model) {
this.model = model;
}
@@ -50,12 +47,6 @@ public class ModelUser extends User {
return model.getFullName();
}
- @Nullable
- @Override
- protected char[] getMasterPassword() {
- return masterPassword;
- }
-
@Override
public int getAvatar() {
return model.getAvatar();
@@ -69,21 +60,10 @@ public class ModelUser extends User {
@Override
public void authenticate(final char[] masterPassword)
throws IncorrectMasterPasswordException {
- putKey( model.authenticate( masterPassword ) );
- this.masterPassword = masterPassword.clone();
+ key = model.authenticate( masterPassword );
MPUserFileManager.get().save();
}
- @Override
- public void reset() {
- super.reset();
-
- if (masterPassword != null) {
- Arrays.fill( masterPassword, (char) 0 );
- masterPassword = null;
- }
- }
-
@Override
public Iterable findSitesByName(final String siteName) {
return FluentIterable.from( model.findSitesByName( siteName ) ).transform( new Function() {
diff --git a/platform-independent/gui-java/src/main/java/com/lyndir/masterpassword/gui/model/User.java b/platform-independent/gui-java/src/main/java/com/lyndir/masterpassword/gui/model/User.java
index 31edfd28..4cc1011a 100755
--- a/platform-independent/gui-java/src/main/java/com/lyndir/masterpassword/gui/model/User.java
+++ b/platform-independent/gui-java/src/main/java/com/lyndir/masterpassword/gui/model/User.java
@@ -19,7 +19,6 @@
package com.lyndir.masterpassword.gui.model;
import com.google.common.base.Preconditions;
-import com.google.common.collect.Maps;
import com.lyndir.masterpassword.MasterKey;
import com.lyndir.masterpassword.model.IncorrectMasterPasswordException;
import java.util.*;
@@ -32,14 +31,11 @@ import javax.annotation.Nullable;
*/
public abstract class User {
- @Nonnull
- private final Map keyByVersion = Maps.newEnumMap( MasterKey.Version.class );
+ @Nullable
+ protected MasterKey key;
public abstract String getFullName();
- @Nullable
- protected abstract char[] getMasterPassword();
-
@SuppressWarnings("MethodCanBeVariableArityMethod")
public abstract void authenticate(char[] masterPassword)
throws IncorrectMasterPasswordException;
@@ -49,31 +45,12 @@ public abstract class User {
}
public boolean isKeyAvailable() {
- return getMasterPassword() != null;
+ return key != null;
}
@Nonnull
- public MasterKey getKey(final MasterKey.Version algorithmVersion) {
- char[] masterPassword = Preconditions.checkNotNull( getMasterPassword(), "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 MasterKey getKey() {
+ return Preconditions.checkNotNull( key, "User is not authenticated: " + getFullName() );
}
public abstract Iterable findSitesByName(String siteName);
diff --git a/platform-independent/gui-java/src/main/java/com/lyndir/masterpassword/gui/view/PasswordFrame.java b/platform-independent/gui-java/src/main/java/com/lyndir/masterpassword/gui/view/PasswordFrame.java
index d0f4391f..2c68390e 100755
--- a/platform-independent/gui-java/src/main/java/com/lyndir/masterpassword/gui/view/PasswordFrame.java
+++ b/platform-independent/gui-java/src/main/java/com/lyndir/masterpassword/gui/view/PasswordFrame.java
@@ -257,8 +257,8 @@ public class PasswordFrame extends JFrame implements DocumentListener {
@Override
public String call()
throws Exception {
- return user.getKey( site.getAlgorithmVersion() )
- .siteResult( site.getSiteName(), site.getSiteCounter(), MPKeyPurpose.Authentication, null, site.getResultType(), null );
+ return user.getKey()
+ .siteResult( site.getSiteName(), site.getSiteCounter(), MPKeyPurpose.Authentication, null, site.getResultType(), null, site.getAlgorithmVersion() );
}
} );
Futures.addCallback( passwordFuture, new FutureCallback() {