Marshal refactoring to prepare for new format.
This commit is contained in:
		@@ -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();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,41 @@
 | 
			
		||||
//==============================================================================
 | 
			
		||||
// This file is part of Master Password.
 | 
			
		||||
// Copyright (c) 2011-2017, Maarten Billemont.
 | 
			
		||||
//
 | 
			
		||||
// Master Password is free software: you can redistribute it and/or modify
 | 
			
		||||
// it under the terms of the GNU General Public License as published by
 | 
			
		||||
// the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
// (at your option) any later version.
 | 
			
		||||
//
 | 
			
		||||
// Master Password is distributed in the hope that it will be useful,
 | 
			
		||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
// GNU General Public License for more details.
 | 
			
		||||
//
 | 
			
		||||
// You can find a copy of the GNU General Public License in the
 | 
			
		||||
// LICENSE file.  Alternatively, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
package com.lyndir.masterpassword;
 | 
			
		||||
 | 
			
		||||
import com.google.common.primitives.UnsignedInteger;
 | 
			
		||||
import java.nio.ByteBuffer;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author lhunath, 2017-09-20
 | 
			
		||||
 */
 | 
			
		||||
public final class MPUtils {
 | 
			
		||||
 | 
			
		||||
    public static byte[] bytesForInt(final int number) {
 | 
			
		||||
        return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( MasterKeyV0.mpw_byteOrder ).putInt( number ).array();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static byte[] bytesForInt(final UnsignedInteger number) {
 | 
			
		||||
        return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( MasterKeyV0.mpw_byteOrder ).putInt( number.intValue() ).array();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static byte[] idForBytes(final byte[] bytes) {
 | 
			
		||||
        return MasterKeyV0.mpw_hash.of( bytes );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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;
 | 
			
		||||
 | 
			
		||||
    @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.
 | 
			
		||||
     * <p/>
 | 
			
		||||
     * This setter affects the default setting for any newly created {@link MasterKey}s.
 | 
			
		||||
     *
 | 
			
		||||
     * @param allowNative false to disallow the use of native libraries.
 | 
			
		||||
     */
 | 
			
		||||
    public static void setAllowNativeByDefault(final boolean allowNative) {
 | 
			
		||||
        allowNativeByDefault = allowNative;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected MasterKey(final String fullName) {
 | 
			
		||||
        Preconditions.checkArgument( !fullName.isEmpty() );
 | 
			
		||||
    @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];
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,49 @@
 | 
			
		||||
//==============================================================================
 | 
			
		||||
// This file is part of Master Password.
 | 
			
		||||
// Copyright (c) 2011-2017, Maarten Billemont.
 | 
			
		||||
//
 | 
			
		||||
// Master Password is free software: you can redistribute it and/or modify
 | 
			
		||||
// it under the terms of the GNU General Public License as published by
 | 
			
		||||
// the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
// (at your option) any later version.
 | 
			
		||||
//
 | 
			
		||||
// Master Password is distributed in the hope that it will be useful,
 | 
			
		||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
// GNU General Public License for more details.
 | 
			
		||||
//
 | 
			
		||||
// You can find a copy of the GNU General Public License in the
 | 
			
		||||
// LICENSE file.  Alternatively, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
package com.lyndir.masterpassword;
 | 
			
		||||
 | 
			
		||||
import com.google.common.primitives.UnsignedInteger;
 | 
			
		||||
import java.io.Serializable;
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @see MasterKey.Version
 | 
			
		||||
 */
 | 
			
		||||
public interface MasterKeyAlgorithm extends Serializable {
 | 
			
		||||
 | 
			
		||||
    MasterKey.Version getAlgorithmVersion();
 | 
			
		||||
 | 
			
		||||
    byte[] deriveKey(String fullName, char[] masterPassword);
 | 
			
		||||
 | 
			
		||||
    byte[] siteKey(byte[] masterKey, String siteName, UnsignedInteger siteCounter, MPKeyPurpose keyPurpose,
 | 
			
		||||
                   @Nullable String keyContext);
 | 
			
		||||
 | 
			
		||||
    String siteResult(byte[] masterKey, String siteName, UnsignedInteger siteCounter, MPKeyPurpose keyPurpose,
 | 
			
		||||
                      @Nullable String keyContext, MPResultType resultType, @Nullable String resultParam);
 | 
			
		||||
 | 
			
		||||
    String sitePasswordFromTemplate(byte[] masterKey, byte[] siteKey, MPResultType resultType, @Nullable String resultParam);
 | 
			
		||||
 | 
			
		||||
    String sitePasswordFromCrypt(byte[] masterKey, byte[] siteKey, MPResultType resultType, @Nullable String resultParam);
 | 
			
		||||
 | 
			
		||||
    String sitePasswordFromDerive(byte[] masterKey, byte[] siteKey, MPResultType resultType, @Nullable String resultParam);
 | 
			
		||||
 | 
			
		||||
    String siteState(byte[] masterKey, String siteName, UnsignedInteger siteCounter, MPKeyPurpose keyPurpose,
 | 
			
		||||
                     @Nullable String keyContext, MPResultType resultType, @Nullable String resultParam);
 | 
			
		||||
}
 | 
			
		||||
@@ -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,27 +119,25 @@ 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,
 | 
			
		||||
    public byte[] siteKey(final byte[] masterKey, final String siteName, UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose,
 | 
			
		||||
                          @Nullable final String keyContext) {
 | 
			
		||||
        Preconditions.checkArgument( !siteName.isEmpty() );
 | 
			
		||||
 | 
			
		||||
@@ -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 );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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() );
 | 
			
		||||
 
 | 
			
		||||
@@ -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 ) );
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,80 @@
 | 
			
		||||
//==============================================================================
 | 
			
		||||
// This file is part of Master Password.
 | 
			
		||||
// Copyright (c) 2011-2017, Maarten Billemont.
 | 
			
		||||
//
 | 
			
		||||
// Master Password is free software: you can redistribute it and/or modify
 | 
			
		||||
// it under the terms of the GNU General Public License as published by
 | 
			
		||||
// the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
// (at your option) any later version.
 | 
			
		||||
//
 | 
			
		||||
// Master Password is distributed in the hope that it will be useful,
 | 
			
		||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
// GNU General Public License for more details.
 | 
			
		||||
//
 | 
			
		||||
// You can find a copy of the GNU General Public License in the
 | 
			
		||||
// LICENSE file.  Alternatively, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
package com.lyndir.masterpassword.model;
 | 
			
		||||
 | 
			
		||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse;
 | 
			
		||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
 | 
			
		||||
 | 
			
		||||
import com.lyndir.masterpassword.MPConstant;
 | 
			
		||||
import com.lyndir.masterpassword.MasterKey;
 | 
			
		||||
import org.joda.time.Instant;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author lhunath, 2017-09-20
 | 
			
		||||
 */
 | 
			
		||||
public class MPFlatMarshaller implements MPMarshaller {
 | 
			
		||||
 | 
			
		||||
    private static final int FORMAT = 1;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String marshall(final MPUser user, final MasterKey masterKey, final ContentMode contentMode) {
 | 
			
		||||
        StringBuilder content = new StringBuilder();
 | 
			
		||||
        content.append( "# Master Password site export\n" );
 | 
			
		||||
        content.append( "#     " ).append( contentMode.description() ).append( '\n' );
 | 
			
		||||
        content.append( "# \n" );
 | 
			
		||||
        content.append( "##\n" );
 | 
			
		||||
        content.append( "# Format: " ).append( FORMAT ).append( '\n' );
 | 
			
		||||
        content.append( "# Date: " ).append( MPConstant.dateTimeFormatter.print( new Instant() ) ).append( '\n' );
 | 
			
		||||
        content.append( "# User Name: " ).append( user.getFullName() ).append( '\n' );
 | 
			
		||||
        content.append( "# Full Name: " ).append( user.getFullName() ).append( '\n' );
 | 
			
		||||
        content.append( "# Avatar: " ).append( user.getAvatar() ).append( '\n' );
 | 
			
		||||
        content.append( "# Key ID: " ).append( user.exportKeyID() ).append( '\n' );
 | 
			
		||||
        content.append( "# Algorithm: " ).append( MasterKey.Version.CURRENT.toInt() ).append( '\n' );
 | 
			
		||||
        content.append( "# Default Type: " ).append( user.getDefaultType().getType() ).append( '\n' );
 | 
			
		||||
        content.append( "# Passwords: " ).append( contentMode.name() ).append( '\n' );
 | 
			
		||||
        content.append( "##\n" );
 | 
			
		||||
        content.append( "#\n" );
 | 
			
		||||
        content.append( "#               Last     Times  Password                      Login\t                     Site\tSite\n" );
 | 
			
		||||
        content.append( "#               used      used      type                       name\t                     name\tpassword\n" );
 | 
			
		||||
 | 
			
		||||
        for (final MPSite site : user.getSites()) {
 | 
			
		||||
            String loginName = site.getLoginContent();
 | 
			
		||||
            String password = site.getSiteContent();
 | 
			
		||||
            if (!contentMode.isRedacted()) {
 | 
			
		||||
                loginName = site.loginFor( masterKey );
 | 
			
		||||
                password = site.resultFor( masterKey );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            content.append( strf( "%s  %8d  %8s  %25s\t%25s\t%s\n", //
 | 
			
		||||
                                  MPConstant.dateTimeFormatter.print( site.getLastUsed() ), // lastUsed
 | 
			
		||||
                                  site.getUses(), // uses
 | 
			
		||||
                                  strf( "%d:%d:%d", //
 | 
			
		||||
                                        site.getResultType().getType(), // type
 | 
			
		||||
                                        site.getAlgorithmVersion().toInt(), // algorithm
 | 
			
		||||
                                        site.getSiteCounter().intValue() ), // counter
 | 
			
		||||
                                  ifNotNullElse( loginName, "" ), // loginName
 | 
			
		||||
                                  site.getSiteName(), // siteName
 | 
			
		||||
                                  ifNotNullElse( password, "" ) // password
 | 
			
		||||
            ) );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return content.toString();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,139 @@
 | 
			
		||||
//==============================================================================
 | 
			
		||||
// This file is part of Master Password.
 | 
			
		||||
// Copyright (c) 2011-2017, Maarten Billemont.
 | 
			
		||||
//
 | 
			
		||||
// Master Password is free software: you can redistribute it and/or modify
 | 
			
		||||
// it under the terms of the GNU General Public License as published by
 | 
			
		||||
// the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
// (at your option) any later version.
 | 
			
		||||
//
 | 
			
		||||
// Master Password is distributed in the hope that it will be useful,
 | 
			
		||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
// GNU General Public License for more details.
 | 
			
		||||
//
 | 
			
		||||
// You can find a copy of the GNU General Public License in the
 | 
			
		||||
// LICENSE file.  Alternatively, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
package com.lyndir.masterpassword.model;
 | 
			
		||||
 | 
			
		||||
import com.google.common.base.*;
 | 
			
		||||
import com.google.common.io.CharStreams;
 | 
			
		||||
import com.google.common.primitives.UnsignedInteger;
 | 
			
		||||
import com.lyndir.lhunath.opal.system.CodeUtils;
 | 
			
		||||
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
 | 
			
		||||
import com.lyndir.masterpassword.*;
 | 
			
		||||
import java.io.*;
 | 
			
		||||
import java.util.regex.Matcher;
 | 
			
		||||
import java.util.regex.Pattern;
 | 
			
		||||
import javax.annotation.Nonnull;
 | 
			
		||||
import org.joda.time.DateTime;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author lhunath, 14-12-07
 | 
			
		||||
 */
 | 
			
		||||
public class MPFlatUnmarshaller implements MPUnmarshaller {
 | 
			
		||||
 | 
			
		||||
    private static final Pattern[] unmarshallFormats = {
 | 
			
		||||
            Pattern.compile( "^([^ ]+) +(\\d+) +(\\d+)(:\\d+)? +([^\t]+)\t(.*)" ),
 | 
			
		||||
            Pattern.compile( "^([^ ]+) +(\\d+) +(\\d+)(:\\d+)?(:\\d+)? +([^\t]*)\t *([^\t]+)\t(.*)" ) };
 | 
			
		||||
    private static final Pattern   headerFormat      = Pattern.compile( "^#\\s*([^:]+): (.*)" );
 | 
			
		||||
    private static final Pattern   colon             = Pattern.compile( ":" );
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    @Override
 | 
			
		||||
    public MPUser unmarshall(@Nonnull final File file)
 | 
			
		||||
            throws IOException {
 | 
			
		||||
        try (Reader reader = new InputStreamReader( new FileInputStream( file ), Charsets.UTF_8 )) {
 | 
			
		||||
            return unmarshall( CharStreams.toString( reader ) );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    @Override
 | 
			
		||||
    public MPUser unmarshall(@Nonnull final String content) {
 | 
			
		||||
        MPUser       user         = null;
 | 
			
		||||
        byte[]       keyID        = null;
 | 
			
		||||
        String       fullName     = null;
 | 
			
		||||
        int          mpVersion    = 0, importFormat = 0, avatar = 0;
 | 
			
		||||
        boolean      clearContent = false, headerStarted = false;
 | 
			
		||||
        MPResultType defaultType  = MPResultType.DEFAULT;
 | 
			
		||||
 | 
			
		||||
        //noinspection HardcodedLineSeparator
 | 
			
		||||
        for (final String line : Splitter.on( CharMatcher.anyOf( "\r\n" ) ).omitEmptyStrings().split( content ))
 | 
			
		||||
            // Header delimitor.
 | 
			
		||||
            if (line.startsWith( "##" ))
 | 
			
		||||
                if (!headerStarted)
 | 
			
		||||
                    // Starts the header.
 | 
			
		||||
                    headerStarted = true;
 | 
			
		||||
                else
 | 
			
		||||
                    // Ends the header.
 | 
			
		||||
                    user = new MPUser( fullName, keyID, MasterKey.Version.fromInt( mpVersion ), avatar, defaultType, new DateTime( 0 ) );
 | 
			
		||||
 | 
			
		||||
                // Comment.
 | 
			
		||||
            else if (line.startsWith( "#" )) {
 | 
			
		||||
                if (headerStarted && (user == null)) {
 | 
			
		||||
                    // In header.
 | 
			
		||||
                    Matcher headerMatcher = headerFormat.matcher( line );
 | 
			
		||||
                    if (headerMatcher.matches()) {
 | 
			
		||||
                        String name = headerMatcher.group( 1 ), value = headerMatcher.group( 2 );
 | 
			
		||||
                        if ("Full Name".equalsIgnoreCase( name ) || "User Name".equalsIgnoreCase( name ))
 | 
			
		||||
                            fullName = value;
 | 
			
		||||
                        else if ("Key ID".equalsIgnoreCase( name ))
 | 
			
		||||
                            keyID = CodeUtils.decodeHex( value );
 | 
			
		||||
                        else if ("Algorithm".equalsIgnoreCase( name ))
 | 
			
		||||
                            mpVersion = ConversionUtils.toIntegerNN( value );
 | 
			
		||||
                        else if ("Format".equalsIgnoreCase( name ))
 | 
			
		||||
                            importFormat = ConversionUtils.toIntegerNN( value );
 | 
			
		||||
                        else if ("Avatar".equalsIgnoreCase( name ))
 | 
			
		||||
                            avatar = ConversionUtils.toIntegerNN( value );
 | 
			
		||||
                        else if ("Passwords".equalsIgnoreCase( name ))
 | 
			
		||||
                            clearContent = "visible".equalsIgnoreCase( value );
 | 
			
		||||
                        else if ("Default Type".equalsIgnoreCase( name ))
 | 
			
		||||
                            defaultType = MPResultType.forType( ConversionUtils.toIntegerNN( value ) );
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // No comment.
 | 
			
		||||
            else if (user != null) {
 | 
			
		||||
                Matcher siteMatcher = unmarshallFormats[importFormat].matcher( line );
 | 
			
		||||
                if (!siteMatcher.matches())
 | 
			
		||||
                    return null;
 | 
			
		||||
 | 
			
		||||
                MPSite site;
 | 
			
		||||
                switch (importFormat) {
 | 
			
		||||
                    case 0:
 | 
			
		||||
                        site = new MPSite( user, //
 | 
			
		||||
                                           siteMatcher.group( 5 ), siteMatcher.group( 6 ), MPSite.DEFAULT_COUNTER,
 | 
			
		||||
                                           MPResultType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),
 | 
			
		||||
                                           MasterKey.Version.fromInt( ConversionUtils.toIntegerNN(
 | 
			
		||||
                                                   colon.matcher( siteMatcher.group( 4 ) ).replaceAll( "" ) ) ),
 | 
			
		||||
                                           null, null, null, ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ),
 | 
			
		||||
                                           MPConstant.dateTimeFormatter.parseDateTime( siteMatcher.group( 1 ) ).toInstant() );
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    case 1:
 | 
			
		||||
                        site = new MPSite( user, //
 | 
			
		||||
                                           siteMatcher.group( 7 ), siteMatcher.group( 8 ),
 | 
			
		||||
                                           UnsignedInteger.valueOf( colon.matcher( siteMatcher.group( 5 ) ).replaceAll( "" ) ),
 | 
			
		||||
                                           MPResultType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),
 | 
			
		||||
                                           MasterKey.Version.fromInt( ConversionUtils.toIntegerNN(
 | 
			
		||||
                                                   colon.matcher( siteMatcher.group( 4 ) ).replaceAll( "" ) ) ),
 | 
			
		||||
                                           siteMatcher.group( 6 ), MPResultType.GeneratedName, null,
 | 
			
		||||
                                           ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ),
 | 
			
		||||
                                           MPConstant.dateTimeFormatter.parseDateTime( siteMatcher.group( 1 ) ).toInstant() );
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    default:
 | 
			
		||||
                        throw new UnsupportedOperationException( "Unexpected format: " + importFormat );
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                user.addSite( site );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        return Preconditions.checkNotNull( user, "No full header found in import file." );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,34 @@
 | 
			
		||||
//==============================================================================
 | 
			
		||||
// This file is part of Master Password.
 | 
			
		||||
// Copyright (c) 2011-2017, Maarten Billemont.
 | 
			
		||||
//
 | 
			
		||||
// Master Password is free software: you can redistribute it and/or modify
 | 
			
		||||
// it under the terms of the GNU General Public License as published by
 | 
			
		||||
// the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
// (at your option) any later version.
 | 
			
		||||
//
 | 
			
		||||
// Master Password is distributed in the hope that it will be useful,
 | 
			
		||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
// GNU General Public License for more details.
 | 
			
		||||
//
 | 
			
		||||
// You can find a copy of the GNU General Public License in the
 | 
			
		||||
// LICENSE file.  Alternatively, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
package com.lyndir.masterpassword.model;
 | 
			
		||||
 | 
			
		||||
import com.lyndir.masterpassword.MasterKey;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author lhunath, 2017-09-20
 | 
			
		||||
 */
 | 
			
		||||
public class MPJSONMarshaller implements MPMarshaller {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String marshall(final MPUser user, final MasterKey masterKey, final ContentMode contentMode) {
 | 
			
		||||
        // TODO
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,45 @@
 | 
			
		||||
//==============================================================================
 | 
			
		||||
// This file is part of Master Password.
 | 
			
		||||
// Copyright (c) 2011-2017, Maarten Billemont.
 | 
			
		||||
//
 | 
			
		||||
// Master Password is free software: you can redistribute it and/or modify
 | 
			
		||||
// it under the terms of the GNU General Public License as published by
 | 
			
		||||
// the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
// (at your option) any later version.
 | 
			
		||||
//
 | 
			
		||||
// Master Password is distributed in the hope that it will be useful,
 | 
			
		||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
// GNU General Public License for more details.
 | 
			
		||||
//
 | 
			
		||||
// You can find a copy of the GNU General Public License in the
 | 
			
		||||
// LICENSE file.  Alternatively, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
package com.lyndir.masterpassword.model;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import javax.annotation.Nonnull;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author lhunath, 2017-09-20
 | 
			
		||||
 */
 | 
			
		||||
public class MPJSONUnmarshaller implements MPUnmarshaller {
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    @Override
 | 
			
		||||
    public MPUser unmarshall(@Nonnull final File file)
 | 
			
		||||
            throws IOException {
 | 
			
		||||
        // TODO
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    @Override
 | 
			
		||||
    public MPUser unmarshall(@Nonnull final String content) {
 | 
			
		||||
        // TODO
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,50 @@
 | 
			
		||||
//==============================================================================
 | 
			
		||||
// This file is part of Master Password.
 | 
			
		||||
// Copyright (c) 2011-2017, Maarten Billemont.
 | 
			
		||||
//
 | 
			
		||||
// Master Password is free software: you can redistribute it and/or modify
 | 
			
		||||
// it under the terms of the GNU General Public License as published by
 | 
			
		||||
// the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
// (at your option) any later version.
 | 
			
		||||
//
 | 
			
		||||
// Master Password is distributed in the hope that it will be useful,
 | 
			
		||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
// GNU General Public License for more details.
 | 
			
		||||
//
 | 
			
		||||
// You can find a copy of the GNU General Public License in the
 | 
			
		||||
// LICENSE file.  Alternatively, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
package com.lyndir.masterpassword.model;
 | 
			
		||||
 | 
			
		||||
import com.lyndir.masterpassword.MasterKey;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author lhunath, 14-12-07
 | 
			
		||||
 */
 | 
			
		||||
public interface MPMarshaller {
 | 
			
		||||
 | 
			
		||||
    String marshall(MPUser user, MasterKey masterKey, ContentMode contentMode);
 | 
			
		||||
 | 
			
		||||
    enum ContentMode {
 | 
			
		||||
        PROTECTED( "Export of site names and stored passwords (unless device-private) encrypted with the master key." ),
 | 
			
		||||
        VISIBLE( "Export of site names and passwords in clear-text." );
 | 
			
		||||
 | 
			
		||||
        private final String description;
 | 
			
		||||
        private boolean redacted;
 | 
			
		||||
 | 
			
		||||
        ContentMode(final String description) {
 | 
			
		||||
            this.description = description;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public String description() {
 | 
			
		||||
            return description;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public boolean isRedacted() {
 | 
			
		||||
            return redacted;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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       MPResultType      resultType;
 | 
			
		||||
    private       MasterKey.Version algorithmVersion;
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
    private String       loginContent;
 | 
			
		||||
    @Nullable
 | 
			
		||||
    private MPResultType loginType;
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
    private String  url;
 | 
			
		||||
    private int     uses;
 | 
			
		||||
    private       String            loginName;
 | 
			
		||||
    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() );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,148 +0,0 @@
 | 
			
		||||
//==============================================================================
 | 
			
		||||
// This file is part of Master Password.
 | 
			
		||||
// Copyright (c) 2011-2017, Maarten Billemont.
 | 
			
		||||
//
 | 
			
		||||
// Master Password is free software: you can redistribute it and/or modify
 | 
			
		||||
// it under the terms of the GNU General Public License as published by
 | 
			
		||||
// the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
// (at your option) any later version.
 | 
			
		||||
//
 | 
			
		||||
// Master Password is distributed in the hope that it will be useful,
 | 
			
		||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
// GNU General Public License for more details.
 | 
			
		||||
//
 | 
			
		||||
// You can find a copy of the GNU General Public License in the
 | 
			
		||||
// LICENSE file.  Alternatively, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
package com.lyndir.masterpassword.model;
 | 
			
		||||
 | 
			
		||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse;
 | 
			
		||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
 | 
			
		||||
 | 
			
		||||
import com.google.common.base.Preconditions;
 | 
			
		||||
import com.lyndir.masterpassword.MasterKey;
 | 
			
		||||
import javax.annotation.Nonnull;
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import org.joda.time.Instant;
 | 
			
		||||
import org.joda.time.format.DateTimeFormatter;
 | 
			
		||||
import org.joda.time.format.ISODateTimeFormat;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author lhunath, 14-12-07
 | 
			
		||||
 */
 | 
			
		||||
public class MPSiteMarshaller {
 | 
			
		||||
 | 
			
		||||
    protected static final DateTimeFormatter rfc3339 = ISODateTimeFormat.dateTimeNoMillis();
 | 
			
		||||
 | 
			
		||||
    private final StringBuilder export      = new StringBuilder();
 | 
			
		||||
    private   final    ContentMode   contentMode = ContentMode.PROTECTED;
 | 
			
		||||
    private final MasterKey masterKey;
 | 
			
		||||
 | 
			
		||||
    public static MPSiteMarshaller marshallSafe(final MPUser user) {
 | 
			
		||||
        MPSiteMarshaller marshaller = new MPSiteMarshaller();
 | 
			
		||||
        marshaller.marshallHeaderForSafeContent( user );
 | 
			
		||||
        for (final MPSite site : user.getSites())
 | 
			
		||||
            marshaller.marshallSite( site );
 | 
			
		||||
 | 
			
		||||
        return marshaller;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static MPSiteMarshaller marshallVisible(final MPUser user, final MasterKey masterKey) {
 | 
			
		||||
        MPSiteMarshaller marshaller = new MPSiteMarshaller();
 | 
			
		||||
        marshaller.marshallHeaderForVisibleContentWithKey( user, masterKey );
 | 
			
		||||
        for (final MPSite site : user.getSites())
 | 
			
		||||
            marshaller.marshallSite( site );
 | 
			
		||||
 | 
			
		||||
        return marshaller;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private String marshallHeaderForSafeContent(final MPUser user) {
 | 
			
		||||
        return marshallHeader( ContentMode.PROTECTED, user, null );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private String marshallHeaderForVisibleContentWithKey(final MPUser user, final MasterKey masterKey) {
 | 
			
		||||
        return marshallHeader( ContentMode.VISIBLE, user, masterKey );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private String marshallHeader(final ContentMode contentMode, final MPUser user, @Nullable final MasterKey masterKey) {
 | 
			
		||||
        this.contentMode = contentMode;
 | 
			
		||||
        this.masterKey = masterKey;
 | 
			
		||||
 | 
			
		||||
        StringBuilder header = new StringBuilder();
 | 
			
		||||
        header.append( "# Master Password site export\n" );
 | 
			
		||||
        header.append( "#     " ).append( this.contentMode.description() ).append( '\n' );
 | 
			
		||||
        header.append( "# \n" );
 | 
			
		||||
        header.append( "##\n" );
 | 
			
		||||
        header.append( "# Format: 1\n" );
 | 
			
		||||
        header.append( "# Date: " ).append( rfc3339.print( new Instant() ) ).append( '\n' );
 | 
			
		||||
        header.append( "# User Name: " ).append( user.getFullName() ).append( '\n' );
 | 
			
		||||
        header.append( "# Full Name: " ).append( user.getFullName() ).append( '\n' );
 | 
			
		||||
        header.append( "# Avatar: " ).append( user.getAvatar() ).append( '\n' );
 | 
			
		||||
        header.append( "# Key ID: " ).append( user.exportKeyID() ).append( '\n' );
 | 
			
		||||
        header.append( "# Algorithm: " ).append( MasterKey.Version.CURRENT.toInt() ).append( '\n' );
 | 
			
		||||
        header.append( "# Default Type: " ).append( user.getDefaultType().getType() ).append( '\n' );
 | 
			
		||||
        header.append( "# Passwords: " ).append( this.contentMode.name() ).append( '\n' );
 | 
			
		||||
        header.append( "##\n" );
 | 
			
		||||
        header.append( "#\n" );
 | 
			
		||||
        header.append( "#               Last     Times  Password                      Login\t                     Site\tSite\n" );
 | 
			
		||||
        header.append( "#               used      used      type                       name\t                     name\tpassword\n" );
 | 
			
		||||
 | 
			
		||||
        export.append( header );
 | 
			
		||||
        return header.toString();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String marshallSite(final MPSite site) {
 | 
			
		||||
        String exportLine = strf( "%s  %8d  %8s  %25s\t%25s\t%s", //
 | 
			
		||||
                                  rfc3339.print( site.getLastUsed() ), // lastUsed
 | 
			
		||||
                                  site.getUses(), // uses
 | 
			
		||||
                                  strf( "%d:%d:%d", //
 | 
			
		||||
                                        site.getResultType().getType(), // type
 | 
			
		||||
                                        site.getAlgorithmVersion().toInt(), // algorithm
 | 
			
		||||
                                        site.getSiteCounter().intValue() ), // counter
 | 
			
		||||
                                  ifNotNullElse( site.getLoginName(), "" ), // loginName
 | 
			
		||||
                                  site.getSiteName(), // siteName
 | 
			
		||||
                                  ifNotNullElse( contentMode.contentForSite( site, masterKey ), "" ) // password
 | 
			
		||||
        );
 | 
			
		||||
        export.append( exportLine ).append( '\n' );
 | 
			
		||||
 | 
			
		||||
        return exportLine;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getExport() {
 | 
			
		||||
        return export.toString();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ContentMode getContentMode() {
 | 
			
		||||
        return contentMode;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public enum ContentMode {
 | 
			
		||||
        PROTECTED( "Export of site names and stored passwords (unless device-private) encrypted with the master key." ) {
 | 
			
		||||
            @Override
 | 
			
		||||
            public String contentForSite(final MPSite site, @Nullable final MasterKey masterKey) {
 | 
			
		||||
                return site.exportContent();
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        VISIBLE( "Export of site names and passwords in clear-text." ) {
 | 
			
		||||
            @Override
 | 
			
		||||
            public String contentForSite(final MPSite site, @Nonnull final MasterKey masterKey) {
 | 
			
		||||
                return site.resultFor( Preconditions.checkNotNull( masterKey, "Master key is required when content mode is VISIBLE." ) );
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        private final String description;
 | 
			
		||||
 | 
			
		||||
        ContentMode(final String description) {
 | 
			
		||||
            this.description = description;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public String description() {
 | 
			
		||||
            return description;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public abstract String contentForSite(MPSite site, MasterKey masterKey);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,181 +0,0 @@
 | 
			
		||||
//==============================================================================
 | 
			
		||||
// This file is part of Master Password.
 | 
			
		||||
// Copyright (c) 2011-2017, Maarten Billemont.
 | 
			
		||||
//
 | 
			
		||||
// Master Password is free software: you can redistribute it and/or modify
 | 
			
		||||
// it under the terms of the GNU General Public License as published by
 | 
			
		||||
// the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
// (at your option) any later version.
 | 
			
		||||
//
 | 
			
		||||
// Master Password is distributed in the hope that it will be useful,
 | 
			
		||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
// GNU General Public License for more details.
 | 
			
		||||
//
 | 
			
		||||
// You can find a copy of the GNU General Public License in the
 | 
			
		||||
// LICENSE file.  Alternatively, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
package com.lyndir.masterpassword.model;
 | 
			
		||||
 | 
			
		||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
 | 
			
		||||
 | 
			
		||||
import com.google.common.base.Charsets;
 | 
			
		||||
import com.google.common.base.Preconditions;
 | 
			
		||||
import com.google.common.collect.ImmutableList;
 | 
			
		||||
import com.google.common.io.CharStreams;
 | 
			
		||||
import com.google.common.primitives.UnsignedInteger;
 | 
			
		||||
import com.lyndir.lhunath.opal.system.CodeUtils;
 | 
			
		||||
import com.lyndir.lhunath.opal.system.logging.Logger;
 | 
			
		||||
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
 | 
			
		||||
import com.lyndir.lhunath.opal.system.util.NNOperation;
 | 
			
		||||
import com.lyndir.masterpassword.MPResultType;
 | 
			
		||||
import com.lyndir.masterpassword.MasterKey;
 | 
			
		||||
import java.io.*;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.regex.Matcher;
 | 
			
		||||
import java.util.regex.Pattern;
 | 
			
		||||
import javax.annotation.Nonnull;
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import org.joda.time.DateTime;
 | 
			
		||||
import org.joda.time.format.DateTimeFormatter;
 | 
			
		||||
import org.joda.time.format.ISODateTimeFormat;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author lhunath, 14-12-07
 | 
			
		||||
 */
 | 
			
		||||
public class MPSiteUnmarshaller {
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("UnusedDeclaration")
 | 
			
		||||
    private static final Logger            logger            = Logger.get( MPSite.class );
 | 
			
		||||
    private static final DateTimeFormatter rfc3339           = ISODateTimeFormat.dateTimeNoMillis();
 | 
			
		||||
    private static final Pattern[]         unmarshallFormats = {
 | 
			
		||||
            Pattern.compile( "^([^ ]+) +(\\d+) +(\\d+)(:\\d+)? +([^\t]+)\t(.*)" ),
 | 
			
		||||
            Pattern.compile( "^([^ ]+) +(\\d+) +(\\d+)(:\\d+)?(:\\d+)? +([^\t]*)\t *([^\t]+)\t(.*)" ) };
 | 
			
		||||
    private static final Pattern           headerFormat      = Pattern.compile( "^#\\s*([^:]+): (.*)" );
 | 
			
		||||
 | 
			
		||||
    private final int     importFormat;
 | 
			
		||||
    @SuppressWarnings({ "FieldCanBeLocal", "unused" })
 | 
			
		||||
    private final int     mpVersion;
 | 
			
		||||
    @SuppressWarnings({ "FieldCanBeLocal", "unused" })
 | 
			
		||||
    private final boolean clearContent;
 | 
			
		||||
    private final MPUser  user;
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    public static MPSiteUnmarshaller unmarshall(@Nonnull final File file)
 | 
			
		||||
            throws IOException {
 | 
			
		||||
        try (Reader reader = new InputStreamReader( new FileInputStream( file ), Charsets.UTF_8 )) {
 | 
			
		||||
            return unmarshall( CharStreams.readLines( reader ) );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    public static MPSiteUnmarshaller unmarshall(@Nonnull final List<String> lines) {
 | 
			
		||||
        byte[]                              keyID        = null;
 | 
			
		||||
        String                              fullName     = null;
 | 
			
		||||
        int                                 mpVersion    = 0, importFormat = 0, avatar = 0;
 | 
			
		||||
        boolean                             clearContent = false, headerStarted = false;
 | 
			
		||||
        MPResultType                        defaultType  = MPResultType.DEFAULT;
 | 
			
		||||
        MPSiteUnmarshaller                  marshaller   = null;
 | 
			
		||||
        final ImmutableList.Builder<MPSite> sites        = ImmutableList.builder();
 | 
			
		||||
 | 
			
		||||
        for (final String line : lines)
 | 
			
		||||
            // Header delimitor.
 | 
			
		||||
            if (line.startsWith( "##" ))
 | 
			
		||||
                if (!headerStarted)
 | 
			
		||||
                    // Starts the header.
 | 
			
		||||
                    headerStarted = true;
 | 
			
		||||
                else
 | 
			
		||||
                    // Ends the header.
 | 
			
		||||
                    marshaller = new MPSiteUnmarshaller( importFormat, mpVersion, fullName, keyID, avatar, defaultType, clearContent );
 | 
			
		||||
 | 
			
		||||
                // Comment.
 | 
			
		||||
            else if (line.startsWith( "#" )) {
 | 
			
		||||
                if (headerStarted && (marshaller == null)) {
 | 
			
		||||
                    // In header.
 | 
			
		||||
                    Matcher headerMatcher = headerFormat.matcher( line );
 | 
			
		||||
                    if (headerMatcher.matches()) {
 | 
			
		||||
                        String name = headerMatcher.group( 1 ), value = headerMatcher.group( 2 );
 | 
			
		||||
                        if ("Full Name".equalsIgnoreCase( name ) || "User Name".equalsIgnoreCase( name ))
 | 
			
		||||
                            fullName = value;
 | 
			
		||||
                        else if ("Key ID".equalsIgnoreCase( name ))
 | 
			
		||||
                            keyID = CodeUtils.decodeHex( value );
 | 
			
		||||
                        else if ("Algorithm".equalsIgnoreCase( name ))
 | 
			
		||||
                            mpVersion = ConversionUtils.toIntegerNN( value );
 | 
			
		||||
                        else if ("Format".equalsIgnoreCase( name ))
 | 
			
		||||
                            importFormat = ConversionUtils.toIntegerNN( value );
 | 
			
		||||
                        else if ("Avatar".equalsIgnoreCase( name ))
 | 
			
		||||
                            avatar = ConversionUtils.toIntegerNN( value );
 | 
			
		||||
                        else if ("Passwords".equalsIgnoreCase( name ))
 | 
			
		||||
                            clearContent = "visible".equalsIgnoreCase( value );
 | 
			
		||||
                        else if ("Default Type".equalsIgnoreCase( name ))
 | 
			
		||||
                            defaultType = MPResultType.forType( ConversionUtils.toIntegerNN( value ) );
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // No comment.
 | 
			
		||||
            else if (marshaller != null)
 | 
			
		||||
                ifNotNull( marshaller.unmarshallSite( line ), new NNOperation<MPSite>() {
 | 
			
		||||
                    @Override
 | 
			
		||||
                    public void apply(@Nonnull final MPSite site) {
 | 
			
		||||
                        sites.add( site );
 | 
			
		||||
                    }
 | 
			
		||||
                } );
 | 
			
		||||
 | 
			
		||||
        return Preconditions.checkNotNull( marshaller, "No full header found in import file." );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected MPSiteUnmarshaller(final int importFormat, final int mpVersion, final String fullName, final byte[] keyID, final int avatar,
 | 
			
		||||
                                 final MPResultType defaultType, final boolean clearContent) {
 | 
			
		||||
        this.importFormat = importFormat;
 | 
			
		||||
        this.mpVersion = mpVersion;
 | 
			
		||||
        this.clearContent = clearContent;
 | 
			
		||||
 | 
			
		||||
        user = new MPUser( fullName, keyID, MasterKey.Version.fromInt( mpVersion ), avatar, defaultType, new DateTime( 0 ) );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public MPSite unmarshallSite(@Nonnull final String siteLine) {
 | 
			
		||||
        Matcher siteMatcher = unmarshallFormats[importFormat].matcher( siteLine );
 | 
			
		||||
        if (!siteMatcher.matches())
 | 
			
		||||
            return null;
 | 
			
		||||
 | 
			
		||||
        MPSite site;
 | 
			
		||||
        switch (importFormat) {
 | 
			
		||||
            case 0:
 | 
			
		||||
                site = new MPSite( user, //
 | 
			
		||||
                                   MasterKey.Version.fromInt( ConversionUtils.toIntegerNN( siteMatcher.group( 4 ).replace( ":", "" ) ) ), //
 | 
			
		||||
                                   rfc3339.parseDateTime( siteMatcher.group( 1 ) ).toInstant(), //
 | 
			
		||||
                                   siteMatcher.group( 5 ), //
 | 
			
		||||
                                   MPResultType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ), MPSite.DEFAULT_COUNTER, //
 | 
			
		||||
                                   ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ), //
 | 
			
		||||
                                   null, //
 | 
			
		||||
                                   siteMatcher.group( 6 ) );
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            case 1:
 | 
			
		||||
                site = new MPSite( user, //
 | 
			
		||||
                                   MasterKey.Version.fromInt( ConversionUtils.toIntegerNN( siteMatcher.group( 4 ).replace( ":", "" ) ) ), //
 | 
			
		||||
                                   rfc3339.parseDateTime( siteMatcher.group( 1 ) ).toInstant(), //
 | 
			
		||||
                                   siteMatcher.group( 7 ), //
 | 
			
		||||
                                   MPResultType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),
 | 
			
		||||
                                   UnsignedInteger.valueOf( siteMatcher.group( 5 ).replace( ":", "" ) ), //
 | 
			
		||||
                                   ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ), //
 | 
			
		||||
                                   siteMatcher.group( 6 ), //
 | 
			
		||||
                                   siteMatcher.group( 8 ) );
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            default:
 | 
			
		||||
                throw logger.bug( "Unexpected format: %d", importFormat );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        user.addSite( site );
 | 
			
		||||
        return site;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public MPUser getUser() {
 | 
			
		||||
        return user;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,36 @@
 | 
			
		||||
//==============================================================================
 | 
			
		||||
// This file is part of Master Password.
 | 
			
		||||
// Copyright (c) 2011-2017, Maarten Billemont.
 | 
			
		||||
//
 | 
			
		||||
// Master Password is free software: you can redistribute it and/or modify
 | 
			
		||||
// it under the terms of the GNU General Public License as published by
 | 
			
		||||
// the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
// (at your option) any later version.
 | 
			
		||||
//
 | 
			
		||||
// Master Password is distributed in the hope that it will be useful,
 | 
			
		||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
// GNU General Public License for more details.
 | 
			
		||||
//
 | 
			
		||||
// You can find a copy of the GNU General Public License in the
 | 
			
		||||
// LICENSE file.  Alternatively, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
package com.lyndir.masterpassword.model;
 | 
			
		||||
 | 
			
		||||
import java.io.*;
 | 
			
		||||
import javax.annotation.Nonnull;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author lhunath, 14-12-07
 | 
			
		||||
 */
 | 
			
		||||
public interface MPUnmarshaller {
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    MPUser unmarshall(@Nonnull File file)
 | 
			
		||||
            throws IOException;
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    MPUser unmarshall(@Nonnull String content);
 | 
			
		||||
}
 | 
			
		||||
@@ -41,17 +41,18 @@ public class MPUser implements Comparable<MPUser> {
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
    private byte[]            keyID;
 | 
			
		||||
    private final MasterKey.Version algorithmVersion;
 | 
			
		||||
    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<MPUser> {
 | 
			
		||||
    @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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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 );
 | 
			
		||||
 
 | 
			
		||||
@@ -172,10 +172,10 @@ public class MPTestSuite implements Callable<Boolean> {
 | 
			
		||||
            @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 );
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -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) {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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<MPResultType>      allResultTypes = ImmutableList.copyOf( MPResultType.forClass( MPResultTypeClass.Template ) );
 | 
			
		||||
    private final ImmutableList<MasterKey.Version> allVersions    = ImmutableList.copyOf( MasterKey.Version.values() );
 | 
			
		||||
 | 
			
		||||
    private ListenableFuture<MasterKey> 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<MasterKey>() {
 | 
			
		||||
            @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() {
 | 
			
		||||
        masterKey = new MasterKey( fullName, masterPassword );
 | 
			
		||||
        updateSitePassword();
 | 
			
		||||
    }
 | 
			
		||||
                } );
 | 
			
		||||
            }
 | 
			
		||||
        }, executor );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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 );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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<String> value) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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<Boolean>() {
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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<Site> findSitesByName(final String siteName) {
 | 
			
		||||
        return FluentIterable.from( model.findSitesByName( siteName ) ).transform( new Function<MPSiteResult, Site>() {
 | 
			
		||||
 
 | 
			
		||||
@@ -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<MasterKey.Version, MasterKey> 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<Site> findSitesByName(String siteName);
 | 
			
		||||
 
 | 
			
		||||
@@ -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<String>() {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user