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