Reorganize core source and add Docker support to CLI.
This commit is contained in:
39
platform-independent/java/core/algorithm/build.gradle
Normal file
39
platform-independent/java/core/algorithm/build.gradle
Normal file
@@ -0,0 +1,39 @@
|
||||
plugins {
|
||||
id 'java-library'
|
||||
}
|
||||
|
||||
description = 'Master Password Algorithm Implementation'
|
||||
|
||||
configurations {
|
||||
lib
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation group: 'com.lyndir.lhunath.opal', name: 'opal-system', version: '1.7-p1'
|
||||
|
||||
api group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.9.5'
|
||||
api group: 'org.jetbrains', name: 'annotations', version: '13.0'
|
||||
api group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2'
|
||||
api group: 'com.google.code.findbugs', name: 'findbugs-annotations', version: '3.0.1'
|
||||
|
||||
lib project( path: ':masterpassword-core', configuration: 'default' )
|
||||
}
|
||||
|
||||
processResources {
|
||||
dependsOn task( type: Sync, 'copyResources', {
|
||||
dependsOn configurations.lib {
|
||||
files.each { libFile ->
|
||||
from zipTree( libFile )
|
||||
into new File( processResources.outputs.files.singleFile, "lib" )
|
||||
}
|
||||
}
|
||||
} )
|
||||
}
|
||||
|
||||
compileJava {
|
||||
doLast {
|
||||
ant.javah( class: 'com.lyndir.masterpassword.impl.MPAlgorithmV0',
|
||||
outputFile: '../../c/src/mpw-jni.h',
|
||||
classpath: files( sourceSets.main.compileClasspath, sourceSets.main.output ).asPath )
|
||||
}
|
||||
}
|
@@ -0,0 +1,240 @@
|
||||
//==============================================================================
|
||||
// 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 static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.lhunath.opal.system.MessageAuthenticationDigests;
|
||||
import com.lyndir.lhunath.opal.system.MessageDigests;
|
||||
import com.lyndir.masterpassword.impl.*;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.Charset;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @see Version
|
||||
*/
|
||||
@SuppressWarnings({ "FieldMayBeStatic", "NewMethodNamingConvention", "MethodReturnAlwaysConstant" })
|
||||
public abstract class MPAlgorithm {
|
||||
|
||||
/**
|
||||
* Derive a master key that describes a user's identity.
|
||||
*
|
||||
* @param fullName The name of the user whose identity is described by the key.
|
||||
* @param masterPassword The user's secret that authenticates his access to the identity.
|
||||
*/
|
||||
@Nullable
|
||||
public abstract byte[] masterKey(String fullName, char[] masterPassword);
|
||||
|
||||
/**
|
||||
* Derive a site key that describes a user's access to a specific entity.
|
||||
*
|
||||
* @param masterKey The identity of the user trying to access the entity.
|
||||
* @param siteName The name of the entity to access.
|
||||
* @param siteCounter The site key's generation.
|
||||
* @param keyPurpose The action that the user aims to undertake with this key.
|
||||
* @param keyContext An action-specific context within which to scope the key.
|
||||
*/
|
||||
@Nullable
|
||||
public abstract byte[] siteKey(byte[] masterKey, String siteName, UnsignedInteger siteCounter,
|
||||
MPKeyPurpose keyPurpose, @Nullable String keyContext);
|
||||
|
||||
/**
|
||||
* Encode a templated result for a site key.
|
||||
*
|
||||
* @param resultType The template to base the site key's encoding on.
|
||||
* @param resultParam A parameter that provides contextual data specific to the type template.
|
||||
*/
|
||||
@Nullable
|
||||
public abstract String siteResult(byte[] masterKey, byte[] siteKey, String siteName, UnsignedInteger siteCounter,
|
||||
MPKeyPurpose keyPurpose, @Nullable String keyContext,
|
||||
MPResultType resultType, @Nullable String resultParam);
|
||||
|
||||
/**
|
||||
* For {@link MPResultTypeClass#Stateful} {@code resultType}s, generate the {@code resultParam} to use with the
|
||||
* {@link #siteResult(byte[], byte[], String, UnsignedInteger, MPKeyPurpose, String, MPResultType, String)} call
|
||||
* in order to reconstruct this call's original {@code resultParam}.
|
||||
*
|
||||
* @param resultType The template to base the site key's encoding on.
|
||||
* @param resultParam A parameter that provides contextual data specific to the type template.
|
||||
*/
|
||||
@Nullable
|
||||
public abstract String siteState(byte[] masterKey, byte[] siteKey, String siteName, UnsignedInteger siteCounter,
|
||||
MPKeyPurpose keyPurpose, @Nullable String keyContext,
|
||||
MPResultType resultType, String resultParam);
|
||||
|
||||
// Configuration
|
||||
|
||||
/**
|
||||
* The linear version identifier of this algorithm's implementation.
|
||||
*/
|
||||
public abstract Version version();
|
||||
|
||||
/**
|
||||
* mpw: defaults: initial counter value.
|
||||
*/
|
||||
public abstract UnsignedInteger mpw_default_counter();
|
||||
|
||||
/**
|
||||
* mpw: defaults: password result type.
|
||||
*/
|
||||
public abstract MPResultType mpw_default_result_type();
|
||||
|
||||
/**
|
||||
* mpw: defaults: login result type.
|
||||
*/
|
||||
public abstract MPResultType mpw_default_login_type();
|
||||
|
||||
/**
|
||||
* mpw: defaults: answer result type.
|
||||
*/
|
||||
public abstract MPResultType mpw_default_answer_type();
|
||||
|
||||
/**
|
||||
* mpw: Input character encoding.
|
||||
*/
|
||||
public abstract Charset mpw_charset();
|
||||
|
||||
/**
|
||||
* mpw: Platform-agnostic byte order.
|
||||
*/
|
||||
public abstract ByteOrder mpw_byteOrder();
|
||||
|
||||
/**
|
||||
* mpw: Key ID hash.
|
||||
*/
|
||||
public abstract MessageDigests mpw_hash();
|
||||
|
||||
/**
|
||||
* mpw: Site digest.
|
||||
*/
|
||||
public abstract MessageAuthenticationDigests mpw_digest();
|
||||
|
||||
/**
|
||||
* mpw: Master key size (byte).
|
||||
*/
|
||||
public abstract int mpw_dkLen();
|
||||
|
||||
/**
|
||||
* mpw: Minimum size for derived keys (bit).
|
||||
*/
|
||||
public abstract int mpw_keySize_min();
|
||||
|
||||
/**
|
||||
* mpw: Maximum size for derived keys (bit).
|
||||
*/
|
||||
public abstract int mpw_keySize_max();
|
||||
|
||||
/**
|
||||
* mpw: validity for the time-based rolling counter (s).
|
||||
*/
|
||||
public abstract long mpw_otp_window();
|
||||
|
||||
/**
|
||||
* scrypt: CPU cost parameter.
|
||||
*/
|
||||
public abstract int scrypt_N();
|
||||
|
||||
/**
|
||||
* scrypt: Memory cost parameter.
|
||||
*/
|
||||
public abstract int scrypt_r();
|
||||
|
||||
/**
|
||||
* scrypt: Parallelization parameter.
|
||||
*/
|
||||
public abstract int scrypt_p();
|
||||
|
||||
// Utilities
|
||||
|
||||
protected abstract byte[] toBytes(int number);
|
||||
|
||||
protected abstract byte[] toBytes(UnsignedInteger number);
|
||||
|
||||
protected abstract byte[] toBytes(char[] characters);
|
||||
|
||||
protected abstract byte[] toID(byte[] bytes);
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
return strf( "%d, %s", version().toInt(), getClass().getSimpleName() );
|
||||
}
|
||||
|
||||
/**
|
||||
* The algorithm iterations.
|
||||
*/
|
||||
public enum Version {
|
||||
|
||||
/**
|
||||
* bugs:
|
||||
* - does math with chars whose signedness was platform-dependent.
|
||||
* - miscounted the byte-length for multi-byte site names.
|
||||
* - miscounted the byte-length for multi-byte user names.
|
||||
*/
|
||||
V0( new MPAlgorithmV0() ),
|
||||
|
||||
/**
|
||||
* bugs:
|
||||
* - miscounted the byte-length for multi-byte site names.
|
||||
* - miscounted the byte-length for multi-byte user names.
|
||||
*/
|
||||
V1( new MPAlgorithmV1() ),
|
||||
|
||||
/**
|
||||
* bugs:
|
||||
* - miscounted the byte-length for multi-byte user names.
|
||||
*/
|
||||
V2( new MPAlgorithmV2() ),
|
||||
|
||||
/**
|
||||
* bugs:
|
||||
* - no known issues.
|
||||
*/
|
||||
V3( new MPAlgorithmV3() );
|
||||
|
||||
public static final Version CURRENT = V3;
|
||||
|
||||
private final MPAlgorithm algorithm;
|
||||
|
||||
Version(final MPAlgorithm algorithm) {
|
||||
this.algorithm = algorithm;
|
||||
}
|
||||
|
||||
public MPAlgorithm getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
@JsonCreator
|
||||
public static Version fromInt(final int algorithmVersion) {
|
||||
|
||||
return values()[algorithmVersion];
|
||||
}
|
||||
|
||||
@JsonValue
|
||||
public int toInt() {
|
||||
|
||||
return ordinal();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
//==============================================================================
|
||||
// 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;
|
||||
|
||||
/**
|
||||
* @author lhunath, 2017-09-21
|
||||
*/
|
||||
public class MPAlgorithmException extends MPException {
|
||||
|
||||
public MPAlgorithmException(final String message) {
|
||||
super( message );
|
||||
}
|
||||
|
||||
public MPAlgorithmException(final String message, final Throwable cause) {
|
||||
super( message, cause );
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
//==============================================================================
|
||||
// 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;
|
||||
|
||||
/**
|
||||
* @author lhunath, 2018-06-03
|
||||
*/
|
||||
public class MPException extends Exception {
|
||||
|
||||
public MPException(final String message) {
|
||||
super( message );
|
||||
}
|
||||
|
||||
public MPException(final String message, final Throwable cause) {
|
||||
super( message, cause );
|
||||
}
|
||||
}
|
@@ -0,0 +1,99 @@
|
||||
//==============================================================================
|
||||
// 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 static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.primitives.UnsignedBytes;
|
||||
import com.lyndir.lhunath.opal.system.MessageAuthenticationDigests;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import java.nio.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 15-03-29
|
||||
*/
|
||||
public class MPIdenticon {
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( MPIdenticon.class );
|
||||
|
||||
private static final Charset charset = Charsets.UTF_8;
|
||||
private static final Color[] colors = {
|
||||
Color.RED, Color.GREEN, Color.YELLOW, Color.BLUE, Color.MAGENTA, Color.CYAN, Color.MONO };
|
||||
private static final char[] leftArm = { '╔', '╚', '╰', '═' };
|
||||
private static final char[] rightArm = { '╗', '╝', '╯', '═' };
|
||||
private static final char[] body = { '█', '░', '▒', '▓', '☺', '☻' };
|
||||
private static final char[] accessory = {
|
||||
'◈', '◎', '◐', '◑', '◒', '◓', '☀', '☁', '☂', '☃', '☄', '★', '☆', '☎', '☏', '⎈', '⌂', '☘', '☢', '☣', '☕', '⌚', '⌛', '⏰', '⚡',
|
||||
'⛄', '⛅', '☔', '♔', '♕', '♖', '♗', '♘', '♙', '♚', '♛', '♜', '♝', '♞', '♟', '♨', '♩', '♪', '♫', '⚐', '⚑', '⚔', '⚖', '⚙', '⚠',
|
||||
'⌘', '⏎', '✄', '✆', '✈', '✉', '✌' };
|
||||
|
||||
private final String fullName;
|
||||
private final Color color;
|
||||
private final String text;
|
||||
|
||||
public MPIdenticon(final String fullName, final String masterPassword) {
|
||||
this( fullName, masterPassword.toCharArray() );
|
||||
}
|
||||
|
||||
@SuppressWarnings("MethodCanBeVariableArityMethod")
|
||||
public MPIdenticon(final String fullName, final char[] masterPassword) {
|
||||
this.fullName = fullName;
|
||||
|
||||
byte[] masterPasswordBytes = charset.encode( CharBuffer.wrap( masterPassword ) ).array();
|
||||
ByteBuffer identiconSeedBytes = ByteBuffer.wrap(
|
||||
MessageAuthenticationDigests.HmacSHA256.of( masterPasswordBytes, fullName.getBytes( charset ) ) );
|
||||
Arrays.fill( masterPasswordBytes, (byte) 0 );
|
||||
|
||||
IntBuffer identiconSeedBuffer = IntBuffer.allocate( identiconSeedBytes.capacity() );
|
||||
while (identiconSeedBytes.hasRemaining())
|
||||
identiconSeedBuffer.put( UnsignedBytes.toInt( identiconSeedBytes.get() ) );
|
||||
int[] identiconSeed = identiconSeedBuffer.array();
|
||||
|
||||
color = colors[identiconSeed[4] % colors.length];
|
||||
text = strf( "%c%c%c%c", leftArm[identiconSeed[0] % leftArm.length], body[identiconSeed[1] % body.length],
|
||||
rightArm[identiconSeed[2] % rightArm.length], accessory[identiconSeed[3] % accessory.length] );
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public Color getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public enum Color {
|
||||
RED,
|
||||
GREEN,
|
||||
YELLOW,
|
||||
BLUE,
|
||||
MAGENTA,
|
||||
CYAN,
|
||||
MONO
|
||||
}
|
||||
}
|
@@ -0,0 +1,103 @@
|
||||
//==============================================================================
|
||||
// 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.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import java.util.Locale;
|
||||
import javax.annotation.Nullable;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 14-12-02
|
||||
*/
|
||||
public enum MPKeyPurpose {
|
||||
/**
|
||||
* Generate a key for authentication.
|
||||
*/
|
||||
Authentication( "authentication", "Generate a key for authentication.", "com.lyndir.masterpassword" ),
|
||||
|
||||
/**
|
||||
* Generate a name for identification.
|
||||
*/
|
||||
Identification( "identification", "Generate a name for identification.", "com.lyndir.masterpassword.login" ),
|
||||
|
||||
/**
|
||||
* Generate a recovery token.
|
||||
*/
|
||||
Recovery( "recovery", "Generate a recovery token.", "com.lyndir.masterpassword.answer" );
|
||||
|
||||
static final Logger logger = Logger.get( MPResultType.class );
|
||||
|
||||
private final String shortName;
|
||||
private final String description;
|
||||
private final String scope;
|
||||
|
||||
MPKeyPurpose(final String shortName, final String description, @NonNls final String scope) {
|
||||
this.shortName = shortName;
|
||||
this.description = description;
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
public String getShortName() {
|
||||
return shortName;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public String getScope() {
|
||||
return scope;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param shortNamePrefix The name for the purpose to look up. It is a case insensitive prefix of the purpose's short name.
|
||||
*
|
||||
* @return The purpose registered with the given name.
|
||||
*/
|
||||
@Nullable
|
||||
@Contract("!null -> !null")
|
||||
public static MPKeyPurpose forName(@Nullable final String shortNamePrefix) {
|
||||
|
||||
if (shortNamePrefix == null)
|
||||
return null;
|
||||
|
||||
for (final MPKeyPurpose type : values())
|
||||
if (type.getShortName().toLowerCase( Locale.ROOT ).startsWith( shortNamePrefix.toLowerCase( Locale.ROOT ) ))
|
||||
return type;
|
||||
|
||||
throw logger.bug( "No purpose for name: %s", shortNamePrefix );
|
||||
}
|
||||
|
||||
@JsonCreator
|
||||
public static MPKeyPurpose forInt(final int keyPurpose) {
|
||||
|
||||
return values()[keyPurpose];
|
||||
}
|
||||
|
||||
@JsonValue
|
||||
public int toInt() {
|
||||
|
||||
return ordinal();
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
//==============================================================================
|
||||
// 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;
|
||||
|
||||
/**
|
||||
* @author lhunath, 2017-09-21
|
||||
*/
|
||||
public class MPKeyUnavailableException extends MPException {
|
||||
|
||||
public MPKeyUnavailableException(final String message) {
|
||||
super( message );
|
||||
}
|
||||
|
||||
public MPKeyUnavailableException(final String message, final Throwable cause) {
|
||||
super( message, cause );
|
||||
}
|
||||
}
|
@@ -0,0 +1,196 @@
|
||||
//==============================================================================
|
||||
// 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.base.Preconditions;
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.lhunath.opal.system.CodeUtils;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumMap;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-08-30
|
||||
*/
|
||||
public class MPMasterKey {
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( MPMasterKey.class );
|
||||
|
||||
private final EnumMap<MPAlgorithm.Version, byte[]> keyByVersion = new EnumMap<>( MPAlgorithm.Version.class );
|
||||
private final String fullName;
|
||||
private final char[] masterPassword;
|
||||
|
||||
private boolean invalidated;
|
||||
|
||||
/**
|
||||
* @param masterPassword The characters of the user's master password.
|
||||
* <b>Note: this method destroys the contents of the array.</b>
|
||||
*/
|
||||
@SuppressWarnings("AssignmentToCollectionOrArrayFieldFromParameter")
|
||||
public MPMasterKey(final String fullName, final char[] masterPassword) {
|
||||
|
||||
this.fullName = fullName;
|
||||
this.masterPassword = masterPassword.clone();
|
||||
Arrays.fill( masterPassword, (char) 0 );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getFullName() {
|
||||
|
||||
return fullName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate an identifier for the master key.
|
||||
*
|
||||
* @throws MPKeyUnavailableException {@link #invalidate()} has been called on this object.
|
||||
*/
|
||||
public byte[] getKeyID(final MPAlgorithm algorithm)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
|
||||
return algorithm.toID( masterKey( algorithm ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Wipe this key's secrets from memory, making the object permanently unusable.
|
||||
*/
|
||||
public void invalidate() {
|
||||
|
||||
invalidated = true;
|
||||
for (final byte[] key : keyByVersion.values())
|
||||
Arrays.fill( key, (byte) 0 );
|
||||
Arrays.fill( masterPassword, (char) 0 );
|
||||
}
|
||||
|
||||
private byte[] masterKey(final MPAlgorithm algorithm)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
Preconditions.checkArgument( masterPassword.length > 0 );
|
||||
|
||||
if (invalidated)
|
||||
throw new MPKeyUnavailableException( "Master key was invalidated." );
|
||||
|
||||
byte[] masterKey = keyByVersion.get( algorithm.version() );
|
||||
if (masterKey == null) {
|
||||
logger.trc( "-- mpw_masterKey (algorithm: %s)", algorithm );
|
||||
logger.trc( "fullName: %s", fullName );
|
||||
logger.trc( "masterPassword.id: %s", CodeUtils.encodeHex(
|
||||
algorithm.toID( algorithm.toBytes( masterPassword ) ) ) );
|
||||
|
||||
keyByVersion.put( algorithm.version(), masterKey = algorithm.masterKey( fullName, masterPassword ) );
|
||||
}
|
||||
if (masterKey == null)
|
||||
throw new MPAlgorithmException( "Could not derive master key." );
|
||||
|
||||
return masterKey;
|
||||
}
|
||||
|
||||
private byte[] siteKey(final String siteName, final MPAlgorithm algorithm, final UnsignedInteger siteCounter,
|
||||
final MPKeyPurpose keyPurpose, @Nullable final String keyContext)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
Preconditions.checkArgument( !siteName.isEmpty() );
|
||||
|
||||
byte[] masterKey = masterKey( algorithm );
|
||||
|
||||
logger.trc( "-- mpw_siteKey (algorithm: %s)", algorithm );
|
||||
logger.trc( "siteName: %s", siteName );
|
||||
logger.trc( "siteCounter: %s", siteCounter );
|
||||
logger.trc( "keyPurpose: %d (%s)", keyPurpose.toInt(), keyPurpose.getShortName() );
|
||||
logger.trc( "keyContext: %s", keyContext );
|
||||
|
||||
byte[] siteKey = algorithm.siteKey( masterKey, siteName, siteCounter, keyPurpose, keyContext );
|
||||
if (siteKey == null)
|
||||
throw new MPAlgorithmException( "Could not derive site key." );
|
||||
|
||||
return siteKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a token for use with site.
|
||||
*
|
||||
* @param siteName The site's identifier.
|
||||
* @param siteCounter The result's generation.
|
||||
* @param keyPurpose The intended purpose for the site token.
|
||||
* @param keyContext The purpose-specific context for this token.
|
||||
* @param resultType The type of token we're deriving.
|
||||
* @param resultParam Type-specific contextual data for the derivation.
|
||||
* In the case of {@link MPResultTypeClass#Stateful} types, the result of
|
||||
* {@link #siteState(String, MPAlgorithm, UnsignedInteger, MPKeyPurpose, String, MPResultType, String)}.
|
||||
*
|
||||
* @throws MPKeyUnavailableException {@link #invalidate()} has been called on this object.
|
||||
*/
|
||||
public String siteResult(final String siteName, final MPAlgorithm algorithm, final UnsignedInteger siteCounter,
|
||||
final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
|
||||
final MPResultType resultType, @Nullable final String resultParam)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
|
||||
byte[] masterKey = masterKey( algorithm );
|
||||
byte[] siteKey = siteKey( siteName, algorithm, siteCounter, keyPurpose, keyContext );
|
||||
|
||||
logger.trc( "-- mpw_siteResult (algorithm: %s)", algorithm );
|
||||
logger.trc( "resultType: %d (%s)", resultType.getType(), resultType.getShortName() );
|
||||
logger.trc( "resultParam: %s", resultParam );
|
||||
|
||||
String siteResult = algorithm.siteResult(
|
||||
masterKey, siteKey, siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam );
|
||||
if (siteResult == null)
|
||||
throw new MPAlgorithmException( "Could not derive site result." );
|
||||
|
||||
return siteResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt a stateful site token for persistence.
|
||||
*
|
||||
* @param siteName The site's identifier.
|
||||
* @param siteCounter The result's generation.
|
||||
* @param keyPurpose The intended purpose for the site token.
|
||||
* @param keyContext The purpose-specific context for this token.
|
||||
* @param resultType The type of token we're deriving.
|
||||
* @param resultParam The original token that this method's state should reconstruct when passed into
|
||||
* {@link #siteResult(String, MPAlgorithm, UnsignedInteger, MPKeyPurpose, String, MPResultType, String)}.
|
||||
*
|
||||
* @throws MPKeyUnavailableException {@link #invalidate()} has been called on this object.
|
||||
*/
|
||||
public String siteState(final String siteName, final MPAlgorithm algorithm, final UnsignedInteger siteCounter,
|
||||
final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
|
||||
final MPResultType resultType, final String resultParam)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
|
||||
Preconditions.checkNotNull( resultParam );
|
||||
Preconditions.checkArgument( !resultParam.isEmpty() );
|
||||
|
||||
byte[] masterKey = masterKey( algorithm );
|
||||
byte[] siteKey = siteKey( siteName, algorithm, siteCounter, keyPurpose, keyContext );
|
||||
|
||||
logger.trc( "-- mpw_siteState (algorithm: %s)", algorithm );
|
||||
logger.trc( "resultType: %d (%s)", resultType.getType(), resultType.getShortName() );
|
||||
logger.trc( "resultParam: %d bytes = %s", resultParam.getBytes( algorithm.mpw_charset() ).length, resultParam );
|
||||
|
||||
String siteState = algorithm.siteState(
|
||||
masterKey, siteKey, siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam );
|
||||
if (siteState == null)
|
||||
throw new MPAlgorithmException( "Could not derive site state." );
|
||||
|
||||
return siteState;
|
||||
}
|
||||
}
|
@@ -0,0 +1,263 @@
|
||||
//==============================================================================
|
||||
// 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.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import javax.annotation.Nullable;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
|
||||
/**
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
@SuppressWarnings({ "RedundantTypeArguments", "SpellCheckingInspection" })
|
||||
public enum MPResultType {
|
||||
// bit 0-3 | MPResultTypeClass | MPSiteFeature
|
||||
|
||||
/**
|
||||
* 16: pg^VMAUBk5x3p%HP%i4=
|
||||
*/
|
||||
GeneratedMaximum( "maximum", "20 characters, contains symbols.", //
|
||||
ImmutableList.of( new MPTemplate( "anoxxxxxxxxxxxxxxxxx" ),
|
||||
new MPTemplate( "axxxxxxxxxxxxxxxxxno" ) ), //
|
||||
MPResultTypeClass.Template, 0x0 ),
|
||||
|
||||
/**
|
||||
* 17: BiroYena8:Kixa
|
||||
*/
|
||||
GeneratedLong( "long", "Copy-friendly, 14 characters, contains symbols.", //
|
||||
ImmutableList.of( new MPTemplate( "CvcvnoCvcvCvcv" ), new MPTemplate( "CvcvCvcvnoCvcv" ),
|
||||
new MPTemplate( "CvcvCvcvCvcvno" ), new MPTemplate( "CvccnoCvcvCvcv" ),
|
||||
new MPTemplate( "CvccCvcvnoCvcv" ), new MPTemplate( "CvccCvcvCvcvno" ),
|
||||
new MPTemplate( "CvcvnoCvccCvcv" ), new MPTemplate( "CvcvCvccnoCvcv" ),
|
||||
new MPTemplate( "CvcvCvccCvcvno" ), new MPTemplate( "CvcvnoCvcvCvcc" ),
|
||||
new MPTemplate( "CvcvCvcvnoCvcc" ), new MPTemplate( "CvcvCvcvCvccno" ),
|
||||
new MPTemplate( "CvccnoCvccCvcv" ), new MPTemplate( "CvccCvccnoCvcv" ),
|
||||
new MPTemplate( "CvccCvccCvcvno" ), new MPTemplate( "CvcvnoCvccCvcc" ),
|
||||
new MPTemplate( "CvcvCvccnoCvcc" ), new MPTemplate( "CvcvCvccCvccno" ),
|
||||
new MPTemplate( "CvccnoCvcvCvcc" ), new MPTemplate( "CvccCvcvnoCvcc" ),
|
||||
new MPTemplate( "CvccCvcvCvccno" ) ), //
|
||||
MPResultTypeClass.Template, 0x1 ),
|
||||
|
||||
/**
|
||||
* 18: BirSuj0-
|
||||
*/
|
||||
GeneratedMedium( "medium", "Copy-friendly, 8 characters, contains symbols.", //
|
||||
ImmutableList.of( new MPTemplate( "CvcnoCvc" ),
|
||||
new MPTemplate( "CvcCvcno" ) ), //
|
||||
MPResultTypeClass.Template, 0x2 ),
|
||||
|
||||
/**
|
||||
* 19: Bir8
|
||||
*/
|
||||
GeneratedShort( "short", "Copy-friendly, 4 characters, no symbols.", //
|
||||
ImmutableList.of( new MPTemplate( "Cvcn" ) ), //
|
||||
MPResultTypeClass.Template, 0x3 ),
|
||||
|
||||
/**
|
||||
* 20: pO98MoD0
|
||||
*/
|
||||
GeneratedBasic( "basic", "8 characters, no symbols.", //
|
||||
ImmutableList.of( new MPTemplate( "aaanaaan" ),
|
||||
new MPTemplate( "aannaaan" ),
|
||||
new MPTemplate( "aaannaaa" ) ), //
|
||||
MPResultTypeClass.Template, 0x4 ),
|
||||
|
||||
/**
|
||||
* 21: 2798
|
||||
*/
|
||||
GeneratedPIN( "pin", "4 numbers.", //
|
||||
ImmutableList.of( new MPTemplate( "nnnn" ) ), //
|
||||
MPResultTypeClass.Template, 0x5 ),
|
||||
|
||||
/**
|
||||
* 30: birsujano
|
||||
*/
|
||||
GeneratedName( "name", "9 letter name.", //
|
||||
ImmutableList.of( new MPTemplate( "cvccvcvcv" ) ), //
|
||||
MPResultTypeClass.Template, 0xE ),
|
||||
|
||||
/**
|
||||
* 31: bir yennoquce fefi
|
||||
*/
|
||||
GeneratedPhrase( "phrase", "20 character sentence.", //
|
||||
ImmutableList.of( new MPTemplate( "cvcc cvc cvccvcv cvc" ),
|
||||
new MPTemplate( "cvc cvccvcvcv cvcv" ),
|
||||
new MPTemplate( "cv cvccv cvc cvcvccv" ) ), //
|
||||
MPResultTypeClass.Template, 0xF ),
|
||||
|
||||
/**
|
||||
* 1056: Custom saved password.
|
||||
*/
|
||||
StoredPersonal( "personal", "AES-encrypted, exportable.", //
|
||||
ImmutableList.<MPTemplate>of(), //
|
||||
MPResultTypeClass.Stateful, 0x0, MPSiteFeature.ExportContent ),
|
||||
|
||||
/**
|
||||
* 2081: Custom saved password that should not be exported from the device.
|
||||
*/
|
||||
StoredDevicePrivate( "device", "AES-encrypted, not exported.", //
|
||||
ImmutableList.<MPTemplate>of(), //
|
||||
MPResultTypeClass.Stateful, 0x1, MPSiteFeature.DevicePrivate ),
|
||||
|
||||
/**
|
||||
* 4160: Derive a unique binary key.
|
||||
*/
|
||||
DeriveKey( "key", "Encryption key.", //
|
||||
ImmutableList.<MPTemplate>of(), //
|
||||
MPResultTypeClass.Derive, 0x0, MPSiteFeature.Alternative );
|
||||
|
||||
static final Logger logger = Logger.get( MPResultType.class );
|
||||
|
||||
private final String shortName;
|
||||
private final String description;
|
||||
private final List<MPTemplate> templates;
|
||||
private final MPResultTypeClass typeClass;
|
||||
private final int typeIndex;
|
||||
private final ImmutableSet<MPSiteFeature> typeFeatures;
|
||||
|
||||
MPResultType(final String shortName, final String description, final List<MPTemplate> templates,
|
||||
final MPResultTypeClass typeClass, final int typeIndex, final MPSiteFeature... typeFeatures) {
|
||||
|
||||
this.shortName = shortName;
|
||||
this.description = description;
|
||||
this.templates = templates;
|
||||
this.typeClass = typeClass;
|
||||
this.typeIndex = typeIndex;
|
||||
|
||||
ImmutableSet.Builder<MPSiteFeature> typeFeaturesBuilder = ImmutableSet.builder();
|
||||
for (final MPSiteFeature typeFeature : typeFeatures) {
|
||||
typeFeaturesBuilder.add( typeFeature );
|
||||
}
|
||||
this.typeFeatures = typeFeaturesBuilder.build();
|
||||
}
|
||||
|
||||
public String getShortName() {
|
||||
return shortName;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
|
||||
return description;
|
||||
}
|
||||
|
||||
public MPResultTypeClass getTypeClass() {
|
||||
|
||||
return typeClass;
|
||||
}
|
||||
|
||||
@SuppressWarnings("AssignmentOrReturnOfFieldWithMutableType" /* IDEA-191042 */)
|
||||
public ImmutableSet<MPSiteFeature> getTypeFeatures() {
|
||||
|
||||
return typeFeatures;
|
||||
}
|
||||
|
||||
public boolean supportsTypeFeature(final MPSiteFeature feature) {
|
||||
|
||||
return typeFeatures.contains( feature );
|
||||
}
|
||||
|
||||
@JsonValue
|
||||
public int getType() {
|
||||
int mask = typeIndex | typeClass.getMask();
|
||||
for (final MPSiteFeature typeFeature : typeFeatures)
|
||||
mask |= typeFeature.getMask();
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param shortNamePrefix The name for the type to look up. It is a case insensitive prefix of the type's short name.
|
||||
*
|
||||
* @return The type registered with the given name.
|
||||
*/
|
||||
@Nullable
|
||||
@Contract("!null -> !null")
|
||||
public static MPResultType forName(@Nullable final String shortNamePrefix) {
|
||||
|
||||
if (shortNamePrefix == null)
|
||||
return null;
|
||||
|
||||
for (final MPResultType type : values())
|
||||
if (type.getShortName().toLowerCase( Locale.ROOT ).startsWith( shortNamePrefix.toLowerCase( Locale.ROOT ) ))
|
||||
return type;
|
||||
|
||||
throw logger.bug( "No type for name: %s", shortNamePrefix );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param typeClass The class for which we look up types.
|
||||
*
|
||||
* @return All types that support the given class.
|
||||
*/
|
||||
public static ImmutableList<MPResultType> forClass(final MPResultTypeClass typeClass) {
|
||||
|
||||
ImmutableList.Builder<MPResultType> types = ImmutableList.builder();
|
||||
for (final MPResultType type : values())
|
||||
if (type.getTypeClass() == typeClass)
|
||||
types.add( type );
|
||||
|
||||
return types.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param type The type for which we look up types.
|
||||
*
|
||||
* @return The type registered with the given type.
|
||||
*/
|
||||
@JsonCreator
|
||||
public static MPResultType forType(final int type) {
|
||||
|
||||
for (final MPResultType resultType : values())
|
||||
if (resultType.getType() == type)
|
||||
return resultType;
|
||||
|
||||
throw logger.bug( "No type: %s", type );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mask The type mask for which we look up types.
|
||||
*
|
||||
* @return All types that support the given mask's class & features.
|
||||
*/
|
||||
@SuppressWarnings({ "MagicNumber", "UnnecessaryParentheses" /* IDEA-191040 */ })
|
||||
public static ImmutableList<MPResultType> forMask(final int mask) {
|
||||
|
||||
int typeMask = mask & ~0xF; // Ignore resultType bit 0-3
|
||||
|
||||
ImmutableList.Builder<MPResultType> types = ImmutableList.builder();
|
||||
for (final MPResultType resultType : values())
|
||||
if (((resultType.getType() & ~0xF) & typeMask) != 0)
|
||||
types.add( resultType );
|
||||
|
||||
return types.build();
|
||||
}
|
||||
|
||||
public MPTemplate getTemplateAtRollingIndex(final int templateIndex) {
|
||||
return templates.get( templateIndex % templates.size() );
|
||||
}
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
//==============================================================================
|
||||
// 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;
|
||||
|
||||
/**
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
public enum MPResultTypeClass {
|
||||
// bit 4 - 9
|
||||
|
||||
/**
|
||||
* Use the site key to generate a password from a template.
|
||||
*/
|
||||
Template( 1 << 4 ),
|
||||
/**
|
||||
* Use the site key to encrypt and decrypt a stateful entity.
|
||||
*/
|
||||
Stateful( 1 << 5 ),
|
||||
/**
|
||||
* Use the site key to derive a site-specific object.
|
||||
*/
|
||||
Derive( 1 << 6 );
|
||||
|
||||
private final int mask;
|
||||
|
||||
MPResultTypeClass(final int mask) {
|
||||
this.mask = mask;
|
||||
}
|
||||
|
||||
public int getMask() {
|
||||
return mask;
|
||||
}
|
||||
}
|
@@ -0,0 +1,53 @@
|
||||
//==============================================================================
|
||||
// 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;
|
||||
|
||||
/**
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
public enum MPSiteFeature {
|
||||
// bit 10 - 15
|
||||
|
||||
/**
|
||||
* Export the key-protected content data.
|
||||
*/
|
||||
ExportContent( 1 << 10 ),
|
||||
|
||||
/**
|
||||
* Never export content.
|
||||
*/
|
||||
DevicePrivate( 1 << 11 ),
|
||||
|
||||
/**
|
||||
* Don't use this as the primary authentication result type.
|
||||
*/
|
||||
Alternative( 1 << 12 );
|
||||
|
||||
MPSiteFeature(final int mask) {
|
||||
this.mask = mask;
|
||||
}
|
||||
|
||||
private final int mask;
|
||||
|
||||
public int getMask() {
|
||||
return mask;
|
||||
}
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
//==============================================================================
|
||||
// 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 static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.lyndir.lhunath.opal.system.util.MetaObject;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
|
||||
|
||||
/**
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
public class MPTemplate extends MetaObject implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final String templateString;
|
||||
private final List<MPTemplateCharacterClass> template;
|
||||
|
||||
MPTemplate(@NonNls final String templateString) {
|
||||
|
||||
ImmutableList.Builder<MPTemplateCharacterClass> builder = ImmutableList.builder();
|
||||
for (int i = 0; i < templateString.length(); ++i)
|
||||
builder.add( MPTemplateCharacterClass.forIdentifier( templateString.charAt( i ) ) );
|
||||
|
||||
this.templateString = templateString;
|
||||
template = builder.build();
|
||||
}
|
||||
|
||||
public String getTemplateString() {
|
||||
return templateString;
|
||||
}
|
||||
|
||||
public MPTemplateCharacterClass getCharacterClassAtIndex(final int index) {
|
||||
|
||||
return template.get( index );
|
||||
}
|
||||
|
||||
public int length() {
|
||||
|
||||
return template.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return strf( "{MPTemplate: %s}", templateString );
|
||||
}
|
||||
}
|
@@ -0,0 +1,73 @@
|
||||
//==============================================================================
|
||||
// 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.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
|
||||
|
||||
/**
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
@SuppressWarnings({ "HardcodedFileSeparator", "SpellCheckingInspection" })
|
||||
public enum MPTemplateCharacterClass {
|
||||
|
||||
UpperVowel( 'V', "AEIOU" ),
|
||||
UpperConsonant( 'C', "BCDFGHJKLMNPQRSTVWXYZ" ),
|
||||
LowerVowel( 'v', "aeiou" ),
|
||||
LowerConsonant( 'c', "bcdfghjklmnpqrstvwxyz" ),
|
||||
UpperAlphanumeric( 'A', "AEIOUBCDFGHJKLMNPQRSTVWXYZ" ),
|
||||
Alphanumeric( 'a', "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz" ),
|
||||
Numeric( 'n', "0123456789" ),
|
||||
Other( 'o', "@&%?,=[]_:-+*$#!'^~;()/." ),
|
||||
Any( 'x', "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz0123456789!@#$%^&*()" ),
|
||||
Space( ' ', " " );
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( MPTemplateCharacterClass.class );
|
||||
|
||||
private final char identifier;
|
||||
private final char[] characters;
|
||||
|
||||
MPTemplateCharacterClass(final char identifier, @NonNls final String characters) {
|
||||
|
||||
this.identifier = identifier;
|
||||
this.characters = characters.toCharArray();
|
||||
}
|
||||
|
||||
public char getIdentifier() {
|
||||
|
||||
return identifier;
|
||||
}
|
||||
|
||||
public char getCharacterAtRollingIndex(final int index) {
|
||||
|
||||
return characters[index % characters.length];
|
||||
}
|
||||
|
||||
public static MPTemplateCharacterClass forIdentifier(final char identifier) {
|
||||
for (final MPTemplateCharacterClass characterClass : values())
|
||||
if (characterClass.getIdentifier() == identifier)
|
||||
return characterClass;
|
||||
|
||||
throw logger.bug( "No character class defined for identifier: %s", identifier );
|
||||
}
|
||||
}
|
@@ -0,0 +1,241 @@
|
||||
//==============================================================================
|
||||
// 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.impl;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.lhunath.opal.system.MessageAuthenticationDigests;
|
||||
import com.lyndir.lhunath.opal.system.MessageDigests;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.masterpassword.*;
|
||||
import java.nio.*;
|
||||
import java.nio.charset.*;
|
||||
import java.util.Arrays;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-08-30
|
||||
* @see MPAlgorithm.Version#V0
|
||||
*/
|
||||
@SuppressWarnings("NewMethodNamingConvention")
|
||||
public class MPAlgorithmV0 extends MPAlgorithm {
|
||||
|
||||
@SuppressWarnings("HardcodedFileSeparator")
|
||||
protected static final String AES_TRANSFORMATION = "AES/CBC/PKCS5Padding";
|
||||
protected static final int AES_BLOCKSIZE = 128 /* bit */;
|
||||
|
||||
static {
|
||||
Native.load( MPAlgorithmV0.class, "masterpassword-core" );
|
||||
}
|
||||
|
||||
public final Version version = MPAlgorithm.Version.V0;
|
||||
|
||||
protected final Logger logger = Logger.get( getClass() );
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public byte[] masterKey(final String fullName, final char[] masterPassword) {
|
||||
|
||||
// Create a memory-safe NUL-terminated UTF-8 C-string byte array variant of masterPassword.
|
||||
CharsetEncoder encoder = mpw_charset().newEncoder();
|
||||
byte[] masterPasswordBytes = new byte[(int) (masterPassword.length * (double) encoder.maxBytesPerChar()) + 1];
|
||||
try {
|
||||
Arrays.fill( masterPasswordBytes, (byte) 0 );
|
||||
ByteBuffer masterPasswordBuffer = ByteBuffer.wrap( masterPasswordBytes );
|
||||
|
||||
CoderResult result = encoder.encode( CharBuffer.wrap( masterPassword ), masterPasswordBuffer, true );
|
||||
if (!result.isUnderflow())
|
||||
result.throwException();
|
||||
result = encoder.flush( masterPasswordBuffer );
|
||||
if (!result.isUnderflow())
|
||||
result.throwException();
|
||||
|
||||
return _masterKey( fullName, masterPasswordBytes, version().toInt() );
|
||||
}
|
||||
catch (final CharacterCodingException e) {
|
||||
throw new IllegalStateException( e );
|
||||
}
|
||||
finally {
|
||||
Arrays.fill( masterPasswordBytes, (byte) 0 );
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected native byte[] _masterKey(final String fullName, final byte[] masterPassword, final int algorithmVersion);
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public byte[] siteKey(final byte[] masterKey, final String siteName, final UnsignedInteger siteCounter,
|
||||
final MPKeyPurpose keyPurpose, @Nullable final String keyContext) {
|
||||
|
||||
return _siteKey( masterKey, siteName, siteCounter.longValue(), keyPurpose.toInt(), keyContext, version().toInt() );
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected native byte[] _siteKey(final byte[] masterKey, final String siteName, final long siteCounter,
|
||||
final int keyPurpose, @Nullable final String keyContext, final int version);
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String siteResult(final byte[] masterKey, final byte[] siteKey, final String siteName, final UnsignedInteger siteCounter,
|
||||
final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
|
||||
final MPResultType resultType, @Nullable final String resultParam) {
|
||||
|
||||
return _siteResult( masterKey, siteKey, siteName, siteCounter.longValue(),
|
||||
keyPurpose.toInt(), keyContext, resultType.getType(), resultParam, version().toInt() );
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected native String _siteResult(final byte[] masterKey, final byte[] siteKey, final String siteName, final long siteCounter,
|
||||
final int keyPurpose, @Nullable final String keyContext,
|
||||
final int resultType, @Nullable final String resultParam, final int algorithmVersion);
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String siteState(final byte[] masterKey, final byte[] siteKey, final String siteName, final UnsignedInteger siteCounter,
|
||||
final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
|
||||
final MPResultType resultType, final String resultParam) {
|
||||
|
||||
return _siteState( masterKey, siteKey, siteName, siteCounter.longValue(),
|
||||
keyPurpose.toInt(), keyContext, resultType.getType(), resultParam, version().toInt() );
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected native String _siteState(final byte[] masterKey, final byte[] siteKey, final String siteName, final long siteCounter,
|
||||
final int keyPurpose, @Nullable final String keyContext,
|
||||
final int resultType, final String resultParam, final int algorithmVersion);
|
||||
|
||||
// Configuration
|
||||
|
||||
@Override
|
||||
public Version version() {
|
||||
return MPAlgorithm.Version.V0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnsignedInteger mpw_default_counter() {
|
||||
return UnsignedInteger.ONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MPResultType mpw_default_result_type() {
|
||||
return MPResultType.GeneratedLong;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MPResultType mpw_default_login_type() {
|
||||
return MPResultType.GeneratedName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MPResultType mpw_default_answer_type() {
|
||||
return MPResultType.GeneratedPhrase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Charset mpw_charset() {
|
||||
return Charsets.UTF_8;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteOrder mpw_byteOrder() {
|
||||
return ByteOrder.BIG_ENDIAN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageDigests mpw_hash() {
|
||||
return MessageDigests.SHA256;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageAuthenticationDigests mpw_digest() {
|
||||
return MessageAuthenticationDigests.HmacSHA256;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("MagicNumber")
|
||||
public int mpw_dkLen() {
|
||||
return 64;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("MagicNumber")
|
||||
public int mpw_keySize_min() {
|
||||
return 128;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("MagicNumber")
|
||||
public int mpw_keySize_max() {
|
||||
return 512;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("MagicNumber")
|
||||
public long mpw_otp_window() {
|
||||
return 5 * 60 /* s */;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("MagicNumber")
|
||||
public int scrypt_N() {
|
||||
return 32768;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("MagicNumber")
|
||||
public int scrypt_r() {
|
||||
return 8;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("MagicNumber")
|
||||
public int scrypt_p() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Utilities
|
||||
|
||||
@Override
|
||||
public byte[] toBytes(final int number) {
|
||||
return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( mpw_byteOrder() ).putInt( number ).array();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toBytes(final UnsignedInteger number) {
|
||||
return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( mpw_byteOrder() ).putInt( number.intValue() ).array();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toBytes(final char[] characters) {
|
||||
ByteBuffer byteBuffer = mpw_charset().encode( CharBuffer.wrap( characters ) );
|
||||
|
||||
byte[] bytes = new byte[byteBuffer.remaining()];
|
||||
byteBuffer.get( bytes );
|
||||
|
||||
Arrays.fill( byteBuffer.array(), (byte) 0 );
|
||||
return bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toID(final byte[] bytes) {
|
||||
return mpw_hash().of( bytes );
|
||||
}
|
||||
}
|
@@ -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.impl;
|
||||
|
||||
import com.lyndir.masterpassword.*;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-08-30
|
||||
* @see Version#V1
|
||||
*/
|
||||
public class MPAlgorithmV1 extends MPAlgorithmV0 {
|
||||
|
||||
// Configuration
|
||||
|
||||
@Override
|
||||
public Version version() {
|
||||
return MPAlgorithm.Version.V1;
|
||||
}
|
||||
}
|
@@ -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.impl;
|
||||
|
||||
import com.lyndir.masterpassword.MPAlgorithm;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-08-30
|
||||
* @see Version#V2
|
||||
*/
|
||||
public class MPAlgorithmV2 extends MPAlgorithmV1 {
|
||||
|
||||
// Configuration
|
||||
|
||||
@Override
|
||||
public Version version() {
|
||||
return MPAlgorithm.Version.V2;
|
||||
}
|
||||
}
|
@@ -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.impl;
|
||||
|
||||
import com.lyndir.masterpassword.MPAlgorithm;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-08-30
|
||||
* @see Version#V3
|
||||
*/
|
||||
public class MPAlgorithmV3 extends MPAlgorithmV2 {
|
||||
|
||||
// Configuration
|
||||
|
||||
@Override
|
||||
public Version version() {
|
||||
return MPAlgorithm.Version.V3;
|
||||
}
|
||||
}
|
@@ -0,0 +1,84 @@
|
||||
//==============================================================================
|
||||
// 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.impl;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import java.io.*;
|
||||
import java.util.Locale;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2018-05-22
|
||||
*/
|
||||
public final class Native {
|
||||
|
||||
private static final Logger logger = Logger.get( Native.class );
|
||||
|
||||
@SuppressWarnings("HardcodedFileSeparator")
|
||||
private static final char RESOURCE_SEPARATOR = '/';
|
||||
private static final char EXTENSION_SEPARATOR = '.';
|
||||
private static final String NATIVES_PATH = "lib";
|
||||
|
||||
@SuppressWarnings({ "HardcodedFileSeparator", "LoadLibraryWithNonConstantString" })
|
||||
public static void load(final Class<?> context, final String name) {
|
||||
try {
|
||||
// Find and open a stream to the packaged library resource.
|
||||
String library = System.mapLibraryName( name );
|
||||
int libraryDot = library.lastIndexOf( EXTENSION_SEPARATOR );
|
||||
String libraryName = (libraryDot > 0)? library.substring( 0, libraryDot ): library;
|
||||
String libraryExtension = (libraryDot > 0)? library.substring( libraryDot ): ".lib";
|
||||
String libraryResource = getLibraryResource( library );
|
||||
InputStream libraryStream = context.getResourceAsStream( libraryResource );
|
||||
if (libraryStream == null)
|
||||
throw new IllegalStateException(
|
||||
"Library: " + name + " (" + libraryResource + "), not found in class loader for: " + context );
|
||||
|
||||
// Write the library resource to a temporary file.
|
||||
File libraryFile = File.createTempFile( libraryName, libraryExtension );
|
||||
FileOutputStream libraryFileStream = new FileOutputStream( libraryFile );
|
||||
try {
|
||||
libraryFile.deleteOnExit();
|
||||
ByteStreams.copy( libraryStream, libraryFileStream );
|
||||
}
|
||||
finally {
|
||||
libraryFileStream.close();
|
||||
libraryStream.close();
|
||||
}
|
||||
|
||||
// Load the library from the temporary file.
|
||||
System.load( libraryFile.getAbsolutePath() );
|
||||
}
|
||||
catch (final IOException e) {
|
||||
throw new IllegalStateException( "Couldn't extract library: " + name, e );
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private static String getLibraryResource(final String library) {
|
||||
String system = System.getProperty( "os.name" ).toLowerCase( Locale.ROOT );
|
||||
String architecture = System.getProperty( "os.arch" ).toLowerCase( Locale.ROOT );
|
||||
if ("Mac OS X".equalsIgnoreCase( system ))
|
||||
system = "macos";
|
||||
|
||||
return Joiner.on( RESOURCE_SEPARATOR ).join( "", NATIVES_PATH, system, architecture, library );
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
//==============================================================================
|
||||
// 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/>.
|
||||
//==============================================================================
|
||||
|
||||
/**
|
||||
* @author lhunath, 2018-05-15
|
||||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
package com.lyndir.masterpassword.impl;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
@@ -0,0 +1,26 @@
|
||||
//==============================================================================
|
||||
// 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/>.
|
||||
//==============================================================================
|
||||
|
||||
/**
|
||||
* @author lhunath, 15-02-04
|
||||
*/
|
||||
|
||||
@ParametersAreNonnullByDefault
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
24
platform-independent/java/core/model/build.gradle
Normal file
24
platform-independent/java/core/model/build.gradle
Normal file
@@ -0,0 +1,24 @@
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'net.ltgt.apt' version '0.9'
|
||||
}
|
||||
|
||||
description = 'Master Password Site Model'
|
||||
|
||||
dependencies {
|
||||
implementation group: 'com.lyndir.lhunath.opal', name: 'opal-system', version: '1.7-p1'
|
||||
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.9.5'
|
||||
|
||||
api project( ':masterpassword-algorithm' )
|
||||
api group: 'joda-time', name: 'joda-time', version: '2.4'
|
||||
api group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.9.5'
|
||||
api group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.5'
|
||||
api group: 'com.google.code.findbugs', name: 'findbugs-annotations', version: '3.0.1'
|
||||
|
||||
compileOnly group: 'com.google.auto.value', name: 'auto-value', version: '1.2'
|
||||
apt group: 'com.google.auto.value', name: 'auto-value', version: '1.2'
|
||||
|
||||
testCompile group: 'org.testng', name: 'testng', version: '6.8.5'
|
||||
testCompile group: 'ch.qos.logback', name: 'logback-classic', version: '1.1.2'
|
||||
}
|
||||
test.useTestNG()
|
@@ -0,0 +1,46 @@
|
||||
//==============================================================================
|
||||
// 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 org.joda.time.format.DateTimeFormatter;
|
||||
import org.joda.time.format.ISODateTimeFormat;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2016-10-29
|
||||
*/
|
||||
public final class MPConstant {
|
||||
|
||||
/* Environment */
|
||||
|
||||
/**
|
||||
* mpw: default path to look for run configuration files if the platform default is not desired.
|
||||
*/
|
||||
public static final String env_rcDir = "MPW_RCDIR";
|
||||
/**
|
||||
* mpw: permit automatic update checks.
|
||||
*/
|
||||
public static final String env_checkUpdates = "MPW_CHECKUPDATES";
|
||||
|
||||
/* Algorithm */
|
||||
|
||||
public static final int MS_PER_S = 1000;
|
||||
|
||||
public static final DateTimeFormatter dateTimeFormatter = ISODateTimeFormat.dateTimeNoMillis().withZoneUTC();
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
//==============================================================================
|
||||
// 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.MPException;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 14-12-17
|
||||
*/
|
||||
public class MPIncorrectMasterPasswordException extends MPException {
|
||||
|
||||
private final MPUser<?> user;
|
||||
|
||||
public MPIncorrectMasterPasswordException(final MPUser<?> user) {
|
||||
super( "Incorrect master password for user: " + user.getFullName() );
|
||||
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public MPUser<?> getUser() {
|
||||
return user;
|
||||
}
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
//==============================================================================
|
||||
// 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.*;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2018-05-15
|
||||
*/
|
||||
public interface MPQuestion extends Comparable<MPQuestion> {
|
||||
|
||||
// -- Meta
|
||||
|
||||
@Nonnull
|
||||
String getKeyword();
|
||||
|
||||
// -- Algorithm
|
||||
|
||||
@Nonnull
|
||||
MPResultType getType();
|
||||
|
||||
void setType(MPResultType type);
|
||||
|
||||
@Nonnull
|
||||
String getAnswer(@Nullable String state)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException;
|
||||
|
||||
// -- Relationship
|
||||
|
||||
@Nonnull
|
||||
MPSite<?> getSite();
|
||||
}
|
@@ -0,0 +1,71 @@
|
||||
//==============================================================================
|
||||
// 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.primitives.UnsignedInteger;
|
||||
import com.lyndir.masterpassword.*;
|
||||
import java.util.Collection;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2018-05-14
|
||||
*/
|
||||
public interface MPSite<Q extends MPQuestion> extends Comparable<MPSite<?>> {
|
||||
|
||||
// - Meta
|
||||
|
||||
String getName();
|
||||
|
||||
void setName(String name);
|
||||
|
||||
// - Algorithm
|
||||
|
||||
MPAlgorithm getAlgorithm();
|
||||
|
||||
void setAlgorithm(MPAlgorithm algorithm);
|
||||
|
||||
UnsignedInteger getCounter();
|
||||
|
||||
void setCounter(UnsignedInteger counter);
|
||||
|
||||
MPResultType getResultType();
|
||||
|
||||
void setResultType(MPResultType resultType);
|
||||
|
||||
MPResultType getLoginType();
|
||||
|
||||
void setLoginType(@Nullable MPResultType loginType);
|
||||
|
||||
String getResult(MPKeyPurpose keyPurpose, @Nullable String keyContext, @Nullable String state)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException;
|
||||
|
||||
String getLogin(@Nullable String state)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException;
|
||||
|
||||
// - Relations
|
||||
|
||||
MPUser<? extends MPSite<?>> getUser();
|
||||
|
||||
void addQuestion(Q question);
|
||||
|
||||
void deleteQuestion(Q question);
|
||||
|
||||
Collection<Q> getQuestions();
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
//==============================================================================
|
||||
// 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.StringUtils.*;
|
||||
|
||||
import java.util.Objects;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 14-12-07
|
||||
*/
|
||||
public class MPSiteResult implements Comparable<MPSiteResult> {
|
||||
|
||||
private final MPSite<?> site;
|
||||
|
||||
public MPSiteResult(final MPSite<?> site) {
|
||||
this.site = site;
|
||||
}
|
||||
|
||||
public MPSite<?> getSite() {
|
||||
return site;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode( getSite() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
return (this == obj) || ((obj instanceof MPSiteResult) && Objects.equals( getSite(), ((MPSiteResult) obj).getSite() ));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull final MPSiteResult o) {
|
||||
return getSite().compareTo( o.getSite() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return strf( "{%s: %s}", getClass().getSimpleName(), getSite() );
|
||||
}
|
||||
}
|
@@ -0,0 +1,96 @@
|
||||
//==============================================================================
|
||||
// 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.*;
|
||||
import java.util.Collection;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2018-05-14
|
||||
*/
|
||||
public interface MPUser<S extends MPSite<?>> extends Comparable<MPUser<?>> {
|
||||
|
||||
// - Meta
|
||||
|
||||
int getAvatar();
|
||||
|
||||
void setAvatar(int avatar);
|
||||
|
||||
@Nonnull
|
||||
String getFullName();
|
||||
|
||||
// - Algorithm
|
||||
|
||||
@Nonnull
|
||||
MPAlgorithm getAlgorithm();
|
||||
|
||||
void setAlgorithm(MPAlgorithm algorithm);
|
||||
|
||||
@Nullable
|
||||
byte[] getKeyID();
|
||||
|
||||
@Nullable
|
||||
String exportKeyID();
|
||||
|
||||
/**
|
||||
* Performs an authentication attempt against the keyID for this user.
|
||||
*
|
||||
* Note: If a keyID is not set, authentication will always succeed and the keyID will be set to match the given master password.
|
||||
*
|
||||
* @param masterPassword The password to authenticate with.
|
||||
* You cannot re-use this array after passing it in, authentication will destroy its contents.
|
||||
*
|
||||
* @throws MPIncorrectMasterPasswordException If authentication fails due to the given master password not matching the user's keyID.
|
||||
*/
|
||||
void authenticate(char[] masterPassword)
|
||||
throws MPIncorrectMasterPasswordException, MPAlgorithmException;
|
||||
|
||||
/**
|
||||
* Performs an authentication attempt against the keyID for this user.
|
||||
*
|
||||
* Note: If a keyID is not set, authentication will always succeed and the keyID will be set to match the given key.
|
||||
*
|
||||
* @param masterKey The master key to authenticate with.
|
||||
*
|
||||
* @throws MPIncorrectMasterPasswordException If authentication fails due to the given master password not matching the user's keyID.
|
||||
*/
|
||||
void authenticate(MPMasterKey masterKey)
|
||||
throws MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException;
|
||||
|
||||
boolean isMasterKeyAvailable();
|
||||
|
||||
@Nonnull
|
||||
MPMasterKey getMasterKey()
|
||||
throws MPKeyUnavailableException;
|
||||
|
||||
// - Relations
|
||||
|
||||
void addSite(S site);
|
||||
|
||||
void deleteSite(S site);
|
||||
|
||||
@Nonnull
|
||||
Collection<S> getSites();
|
||||
|
||||
@Nonnull
|
||||
Collection<S> findSites(String query);
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
//==============================================================================
|
||||
// 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.collect.ImmutableList;
|
||||
import com.google.common.collect.Maps;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 14-12-05
|
||||
*/
|
||||
public abstract class MPUserManager<U extends MPUser<?>> {
|
||||
|
||||
private final Map<String, U> usersByName = Maps.newHashMap();
|
||||
|
||||
protected MPUserManager(final Iterable<U> users) {
|
||||
for (final U user : users)
|
||||
usersByName.put( user.getFullName(), user );
|
||||
}
|
||||
|
||||
public Collection<U> getUsers() {
|
||||
return ImmutableList.copyOf( usersByName.values() );
|
||||
}
|
||||
|
||||
public U getUserNamed(final String fullName) {
|
||||
return usersByName.get( fullName );
|
||||
}
|
||||
|
||||
public void addUser(final U user) {
|
||||
usersByName.put( user.getFullName(), user );
|
||||
}
|
||||
|
||||
public void deleteUser(final U user) {
|
||||
usersByName.remove( user.getFullName() );
|
||||
}
|
||||
}
|
@@ -0,0 +1,92 @@
|
||||
//==============================================================================
|
||||
// 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.impl;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||
|
||||
import com.lyndir.masterpassword.*;
|
||||
import com.lyndir.masterpassword.model.MPQuestion;
|
||||
import java.util.Objects;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2018-05-14
|
||||
*/
|
||||
public abstract class MPBasicQuestion implements MPQuestion {
|
||||
|
||||
private final String keyword;
|
||||
private MPResultType type;
|
||||
|
||||
protected MPBasicQuestion(final String keyword, final MPResultType type) {
|
||||
this.keyword = keyword;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getKeyword() {
|
||||
return keyword;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPResultType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setType(final MPResultType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getAnswer(@Nullable final String state)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
|
||||
return getSite().getResult( MPKeyPurpose.Recovery, getKeyword(), null, getType(), state );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public abstract MPBasicSite<?> getSite();
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode( getKeyword() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
return (this == obj) || ((obj instanceof MPQuestion) && Objects.equals( getKeyword(), ((MPQuestion) obj).getKeyword() ));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull final MPQuestion o) {
|
||||
return getKeyword().compareTo( o.getKeyword() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return strf( "{%s: %s}", getClass().getSimpleName(), getKeyword() );
|
||||
}
|
||||
}
|
@@ -0,0 +1,175 @@
|
||||
//==============================================================================
|
||||
// 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.impl;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.masterpassword.*;
|
||||
import com.lyndir.masterpassword.model.MPQuestion;
|
||||
import com.lyndir.masterpassword.model.MPSite;
|
||||
import java.util.*;
|
||||
import javax.annotation.Nullable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 14-12-16
|
||||
*/
|
||||
public abstract class MPBasicSite<Q extends MPQuestion> implements MPSite<Q> {
|
||||
|
||||
private String name;
|
||||
private MPAlgorithm algorithm;
|
||||
private UnsignedInteger counter;
|
||||
private MPResultType resultType;
|
||||
private MPResultType loginType;
|
||||
|
||||
private final Collection<Q> questions = new LinkedHashSet<>();
|
||||
|
||||
protected MPBasicSite(final String name, final MPAlgorithm algorithm) {
|
||||
this( name, algorithm, null, null, null );
|
||||
}
|
||||
|
||||
protected MPBasicSite(final String name, final MPAlgorithm algorithm, @Nullable final UnsignedInteger counter,
|
||||
@Nullable final MPResultType resultType, @Nullable final MPResultType loginType) {
|
||||
this.name = name;
|
||||
this.algorithm = algorithm;
|
||||
this.counter = (counter == null)? algorithm.mpw_default_counter(): counter;
|
||||
this.resultType = (resultType == null)? algorithm.mpw_default_result_type(): resultType;
|
||||
this.loginType = (loginType == null)? algorithm.mpw_default_login_type(): loginType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MPAlgorithm getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlgorithm(final MPAlgorithm algorithm) {
|
||||
this.algorithm = algorithm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnsignedInteger getCounter() {
|
||||
return counter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCounter(final UnsignedInteger counter) {
|
||||
this.counter = counter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MPResultType getResultType() {
|
||||
return resultType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setResultType(final MPResultType resultType) {
|
||||
this.resultType = resultType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MPResultType getLoginType() {
|
||||
return loginType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLoginType(@Nullable final MPResultType loginType) {
|
||||
this.loginType = ifNotNullElse( loginType, getAlgorithm().mpw_default_login_type() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResult(final MPKeyPurpose keyPurpose, @Nullable final String keyContext, @Nullable final String state)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
|
||||
return getResult( keyPurpose, keyContext, getCounter(), getResultType(), state );
|
||||
}
|
||||
|
||||
protected String getResult(final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
|
||||
@Nullable final UnsignedInteger counter, final MPResultType type, @Nullable final String state)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
|
||||
return getUser().getMasterKey().siteResult(
|
||||
getName(), getAlgorithm(), ifNotNullElse( counter, getAlgorithm().mpw_default_counter() ),
|
||||
keyPurpose, keyContext, type, state );
|
||||
}
|
||||
|
||||
protected String getState(final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
|
||||
@Nullable final UnsignedInteger counter, final MPResultType type, final String state)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
|
||||
return getUser().getMasterKey().siteState(
|
||||
getName(), getAlgorithm(), ifNotNullElse( counter, getAlgorithm().mpw_default_counter() ),
|
||||
keyPurpose, keyContext, type, state );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLogin(@Nullable final String state)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
|
||||
return getResult( MPKeyPurpose.Identification, null, null, getLoginType(), state );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addQuestion(final Q question) {
|
||||
questions.add( question );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteQuestion(final Q question) {
|
||||
questions.remove( question );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Q> getQuestions() {
|
||||
return Collections.unmodifiableCollection( questions );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode( getName() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
return (this == obj) || ((obj instanceof MPSite) && Objects.equals( getName(), ((MPSite<?>) obj).getName() ));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull final MPSite<?> o) {
|
||||
return getName().compareTo( o.getName() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return strf( "{%s: %s}", getClass().getSimpleName(), getName() );
|
||||
}
|
||||
}
|
@@ -0,0 +1,190 @@
|
||||
//==============================================================================
|
||||
// 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.impl;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.lyndir.lhunath.opal.system.CodeUtils;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.masterpassword.*;
|
||||
import com.lyndir.masterpassword.model.MPIncorrectMasterPasswordException;
|
||||
import com.lyndir.masterpassword.model.MPUser;
|
||||
import java.util.*;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-06-08
|
||||
*/
|
||||
public abstract class MPBasicUser<S extends MPBasicSite<?>> implements MPUser<S> {
|
||||
|
||||
protected final Logger logger = Logger.get( getClass() );
|
||||
|
||||
private int avatar;
|
||||
private final String fullName;
|
||||
private MPAlgorithm algorithm;
|
||||
@Nullable
|
||||
protected MPMasterKey masterKey;
|
||||
|
||||
private final Collection<S> sites = new LinkedHashSet<>();
|
||||
|
||||
protected MPBasicUser(final String fullName, final MPAlgorithm algorithm) {
|
||||
this( 0, fullName, algorithm );
|
||||
}
|
||||
|
||||
protected MPBasicUser(final int avatar, final String fullName, final MPAlgorithm algorithm) {
|
||||
this.avatar = avatar;
|
||||
this.fullName = fullName;
|
||||
this.algorithm = algorithm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAvatar() {
|
||||
return avatar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAvatar(final int avatar) {
|
||||
this.avatar = avatar;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getFullName() {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPAlgorithm getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlgorithm(final MPAlgorithm algorithm) {
|
||||
this.algorithm = algorithm;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public byte[] getKeyID() {
|
||||
try {
|
||||
return getMasterKey().getKeyID( getAlgorithm() );
|
||||
}
|
||||
catch (final MPException e) {
|
||||
logger.wrn( e, "While deriving key ID for user: %s", this );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String exportKeyID() {
|
||||
return CodeUtils.encodeHex( getKeyID() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authenticate(final char[] masterPassword)
|
||||
throws MPIncorrectMasterPasswordException, MPAlgorithmException {
|
||||
try {
|
||||
authenticate( new MPMasterKey( getFullName(), masterPassword ) );
|
||||
}
|
||||
catch (final MPKeyUnavailableException e) {
|
||||
throw new IllegalStateException( e );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authenticate(final MPMasterKey masterKey)
|
||||
throws MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException {
|
||||
if (!masterKey.getFullName().equals( getFullName() ))
|
||||
throw new IllegalArgumentException(
|
||||
"Master key (for " + masterKey.getFullName() + ") is not for this user (" + getFullName() + ")." );
|
||||
|
||||
byte[] keyID = getKeyID();
|
||||
if ((keyID != null) && !Arrays.equals( masterKey.getKeyID( getAlgorithm() ), keyID ))
|
||||
throw new MPIncorrectMasterPasswordException( this );
|
||||
|
||||
this.masterKey = masterKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMasterKeyAvailable() {
|
||||
return masterKey != null;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPMasterKey getMasterKey()
|
||||
throws MPKeyUnavailableException {
|
||||
if (masterKey == null)
|
||||
throw new MPKeyUnavailableException( "Master key was not yet set." );
|
||||
|
||||
return masterKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSite(final S site) {
|
||||
sites.add( site );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteSite(final S site) {
|
||||
sites.remove( site );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Collection<S> getSites() {
|
||||
return Collections.unmodifiableCollection( sites );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Collection<S> findSites(final String query) {
|
||||
ImmutableList.Builder<S> results = ImmutableList.builder();
|
||||
for (final S site : getSites())
|
||||
if (site.getName().startsWith( query ))
|
||||
results.add( site );
|
||||
|
||||
return results.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode( getFullName() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
return (this == obj) || ((obj instanceof MPUser) && Objects.equals( getFullName(), ((MPUser<?>) obj).getFullName() ));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(final MPUser<?> o) {
|
||||
return getFullName().compareTo( o.getFullName() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return strf( "{%s: %s}", getClass().getSimpleName(), getFullName() );
|
||||
}
|
||||
}
|
@@ -0,0 +1,67 @@
|
||||
//==============================================================================
|
||||
// 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.impl;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||
|
||||
import com.lyndir.masterpassword.*;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2018-05-14
|
||||
*/
|
||||
public class MPFileQuestion extends MPBasicQuestion {
|
||||
|
||||
private final MPFileSite site;
|
||||
|
||||
@Nullable
|
||||
private String state;
|
||||
|
||||
public MPFileQuestion(final MPFileSite site, final String keyword,
|
||||
@Nullable final MPResultType type, @Nullable final String state) {
|
||||
super( keyword, ifNotNullElse( type, site.getAlgorithm().mpw_default_answer_type() ) );
|
||||
|
||||
this.site = site;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public String getAnswer()
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
return getAnswer( state );
|
||||
}
|
||||
|
||||
public void setAnswer(final MPResultType type, @Nullable final String answer)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
setType( type );
|
||||
|
||||
if (answer == null)
|
||||
this.state = null;
|
||||
else
|
||||
this.state = getSite().getState(
|
||||
MPKeyPurpose.Recovery, getKeyword(), null, getType(), answer );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPFileSite getSite() {
|
||||
return site;
|
||||
}
|
||||
}
|
@@ -0,0 +1,150 @@
|
||||
//==============================================================================
|
||||
// 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.impl;
|
||||
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.masterpassword.*;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import org.joda.time.Instant;
|
||||
import org.joda.time.ReadableInstant;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 14-12-05
|
||||
*/
|
||||
public class MPFileSite extends MPBasicSite<MPFileQuestion> {
|
||||
|
||||
private final MPFileUser user;
|
||||
|
||||
@Nullable
|
||||
private String url;
|
||||
private int uses;
|
||||
private ReadableInstant lastUsed;
|
||||
|
||||
@Nullable
|
||||
private String resultState;
|
||||
@Nullable
|
||||
private String loginState;
|
||||
|
||||
public MPFileSite(final MPFileUser user, final String name) {
|
||||
this( user, name, null, null, null );
|
||||
}
|
||||
|
||||
public MPFileSite(final MPFileUser user, final String name,
|
||||
@Nullable final MPAlgorithm algorithm, @Nullable final UnsignedInteger counter,
|
||||
@Nullable final MPResultType resultType) {
|
||||
this( user, name, algorithm, counter, resultType, null,
|
||||
null, null, null, 0, new Instant() );
|
||||
}
|
||||
|
||||
protected MPFileSite(final MPFileUser user, final String name,
|
||||
@Nullable final MPAlgorithm algorithm, @Nullable final UnsignedInteger counter,
|
||||
@Nullable final MPResultType resultType, @Nullable final String resultState,
|
||||
@Nullable final MPResultType loginType, @Nullable final String loginState,
|
||||
@Nullable final String url, final int uses, final ReadableInstant lastUsed) {
|
||||
super( name, (algorithm == null)? user.getAlgorithm(): algorithm, counter,
|
||||
(resultType == null)? user.getDefaultType(): resultType, loginType );
|
||||
|
||||
this.user = user;
|
||||
this.resultState = resultState;
|
||||
this.loginState = loginState;
|
||||
this.url = url;
|
||||
this.uses = uses;
|
||||
this.lastUsed = lastUsed;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(@Nullable final String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public int getUses() {
|
||||
return uses;
|
||||
}
|
||||
|
||||
public ReadableInstant getLastUsed() {
|
||||
return lastUsed;
|
||||
}
|
||||
|
||||
public void use() {
|
||||
uses++;
|
||||
lastUsed = new Instant();
|
||||
user.use();
|
||||
}
|
||||
|
||||
public String getResult()
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
|
||||
return getResult( MPKeyPurpose.Authentication, null );
|
||||
}
|
||||
|
||||
public String getResult(final MPKeyPurpose keyPurpose, @Nullable final String keyContext)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
|
||||
return getResult( keyPurpose, keyContext, getResultState() );
|
||||
}
|
||||
|
||||
public String getLogin()
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
|
||||
return getLogin( getLoginState() );
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getResultState() {
|
||||
return resultState;
|
||||
}
|
||||
|
||||
public void setSitePassword(final MPResultType resultType, @Nullable final String password)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
setResultType( resultType );
|
||||
|
||||
if (password == null)
|
||||
this.resultState = null;
|
||||
else
|
||||
this.resultState = getState(
|
||||
MPKeyPurpose.Authentication, null, getCounter(), getResultType(), password );
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getLoginState() {
|
||||
return loginState;
|
||||
}
|
||||
|
||||
public void setLoginName(@Nonnull final MPResultType loginType, @Nullable final String loginName)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
setLoginType( loginType );
|
||||
|
||||
if (loginName == null)
|
||||
this.loginState = null;
|
||||
else
|
||||
this.loginState = getState(
|
||||
MPKeyPurpose.Identification, null, null, getLoginType(), loginName );
|
||||
}
|
||||
|
||||
@Override
|
||||
public MPFileUser getUser() {
|
||||
return user;
|
||||
}
|
||||
}
|
@@ -0,0 +1,161 @@
|
||||
//==============================================================================
|
||||
// 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.impl;
|
||||
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.masterpassword.*;
|
||||
import com.lyndir.masterpassword.model.MPIncorrectMasterPasswordException;
|
||||
import com.lyndir.masterpassword.model.MPUser;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import org.joda.time.Instant;
|
||||
import org.joda.time.ReadableInstant;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 14-12-07
|
||||
*/
|
||||
@SuppressWarnings("ComparableImplementedButEqualsNotOverridden")
|
||||
public class MPFileUser extends MPBasicUser<MPFileSite> {
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( MPFileUser.class );
|
||||
|
||||
@Nullable
|
||||
private byte[] keyID;
|
||||
private MPMarshalFormat format;
|
||||
private MPMarshaller.ContentMode contentMode;
|
||||
|
||||
private MPResultType defaultType;
|
||||
private ReadableInstant lastUsed;
|
||||
|
||||
@Nullable
|
||||
private MPJSONFile json;
|
||||
|
||||
public MPFileUser(final String fullName) {
|
||||
this( fullName, null, MPAlgorithm.Version.CURRENT.getAlgorithm() );
|
||||
}
|
||||
|
||||
public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPAlgorithm algorithm) {
|
||||
this( fullName, keyID, algorithm, 0, algorithm.mpw_default_result_type(), new Instant(),
|
||||
MPMarshalFormat.DEFAULT, MPMarshaller.ContentMode.PROTECTED );
|
||||
}
|
||||
|
||||
public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPAlgorithm algorithm,
|
||||
final int avatar, final MPResultType defaultType, final ReadableInstant lastUsed,
|
||||
final MPMarshalFormat format, final MPMarshaller.ContentMode contentMode) {
|
||||
super( avatar, fullName, algorithm );
|
||||
|
||||
this.keyID = (keyID == null)? null: keyID.clone();
|
||||
this.defaultType = defaultType;
|
||||
this.lastUsed = lastUsed;
|
||||
this.format = format;
|
||||
this.contentMode = contentMode;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public byte[] getKeyID() {
|
||||
return (keyID == null)? null: keyID.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlgorithm(final MPAlgorithm algorithm) {
|
||||
if (!algorithm.equals( getAlgorithm() ) && (keyID != null)) {
|
||||
if (masterKey == null)
|
||||
throw new IllegalStateException( "Cannot update algorithm when keyID is set but masterKey is unavailable." );
|
||||
|
||||
try {
|
||||
keyID = masterKey.getKeyID( algorithm );
|
||||
}
|
||||
catch (final MPKeyUnavailableException e) {
|
||||
throw new IllegalStateException( "Cannot update algorithm when keyID is set but masterKey is unavailable.", e );
|
||||
}
|
||||
catch (final MPAlgorithmException e) {
|
||||
throw new IllegalStateException( e );
|
||||
}
|
||||
}
|
||||
|
||||
super.setAlgorithm( algorithm );
|
||||
}
|
||||
|
||||
public MPMarshalFormat getFormat() {
|
||||
return format;
|
||||
}
|
||||
|
||||
public void setFormat(final MPMarshalFormat format) {
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
public MPMarshaller.ContentMode getContentMode() {
|
||||
return contentMode;
|
||||
}
|
||||
|
||||
public void setContentMode(final MPMarshaller.ContentMode contentMode) {
|
||||
this.contentMode = contentMode;
|
||||
}
|
||||
|
||||
public MPResultType getDefaultType() {
|
||||
return defaultType;
|
||||
}
|
||||
|
||||
public void setDefaultType(final MPResultType defaultType) {
|
||||
this.defaultType = defaultType;
|
||||
}
|
||||
|
||||
public ReadableInstant getLastUsed() {
|
||||
return lastUsed;
|
||||
}
|
||||
|
||||
public void use() {
|
||||
lastUsed = new Instant();
|
||||
}
|
||||
|
||||
public void setJSON(final MPJSONFile json) {
|
||||
this.json = json;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public MPJSONFile getJSON() {
|
||||
return (json == null)? json = new MPJSONFile(): json;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authenticate(final MPMasterKey masterKey)
|
||||
throws MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException {
|
||||
super.authenticate( masterKey );
|
||||
|
||||
if (keyID == null)
|
||||
keyID = masterKey.getKeyID( getAlgorithm() );
|
||||
}
|
||||
|
||||
void save()
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
MPFileUserManager.get().save( this, getMasterKey() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(final MPUser<?> o) {
|
||||
int comparison = (o instanceof MPFileUser)? getLastUsed().compareTo( ((MPFileUser) o).getLastUsed() ): 0;
|
||||
if (comparison != 0)
|
||||
return comparison;
|
||||
|
||||
return super.compareTo( o );
|
||||
}
|
||||
}
|
@@ -0,0 +1,148 @@
|
||||
//==============================================================================
|
||||
// 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.impl;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.io.CharSink;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.masterpassword.*;
|
||||
import com.lyndir.masterpassword.model.*;
|
||||
import java.io.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
|
||||
/**
|
||||
* Manages user data stored in user-specific {@code .mpsites} files under {@code .mpw.d}.
|
||||
*
|
||||
* @author lhunath, 14-12-07
|
||||
*/
|
||||
@SuppressWarnings("CallToSystemGetenv")
|
||||
public class MPFileUserManager extends MPUserManager<MPFileUser> {
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( MPFileUserManager.class );
|
||||
private static final MPFileUserManager instance;
|
||||
|
||||
static {
|
||||
String rcDir = System.getenv( MPConstant.env_rcDir );
|
||||
if (rcDir != null)
|
||||
instance = create( new File( rcDir ) );
|
||||
else
|
||||
instance = create( new File( ifNotNullElseNullable( System.getProperty( "user.home" ), System.getenv( "HOME" ) ), ".mpw.d" ) );
|
||||
}
|
||||
|
||||
private final File path;
|
||||
|
||||
public static MPFileUserManager get() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static MPFileUserManager create(final File path) {
|
||||
return new MPFileUserManager( path );
|
||||
}
|
||||
|
||||
protected MPFileUserManager(final File path) {
|
||||
|
||||
super( unmarshallUsers( path ) );
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
private static Iterable<MPFileUser> unmarshallUsers(final File userFilesDirectory) {
|
||||
if (!userFilesDirectory.mkdirs() && !userFilesDirectory.isDirectory()) {
|
||||
logger.err( "Couldn't create directory for user files: %s", userFilesDirectory );
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
Map<String, MPFileUser> users = new HashMap<>();
|
||||
for (final File userFile : listUserFiles( userFilesDirectory ))
|
||||
for (final MPMarshalFormat format : MPMarshalFormat.values())
|
||||
if (userFile.getName().endsWith( format.fileSuffix() ))
|
||||
try {
|
||||
MPFileUser user = format.unmarshaller().unmarshall( userFile, null );
|
||||
MPFileUser previousUser = users.put( user.getFullName(), user );
|
||||
if ((previousUser != null) && (previousUser.getFormat().ordinal() > user.getFormat().ordinal()))
|
||||
users.put( previousUser.getFullName(), previousUser );
|
||||
}
|
||||
catch (final IOException | MPMarshalException e) {
|
||||
logger.err( e, "Couldn't read user from: %s", userFile );
|
||||
}
|
||||
catch (final MPKeyUnavailableException | MPIncorrectMasterPasswordException | MPAlgorithmException e) {
|
||||
logger.err( e, "Couldn't authenticate user for: %s", userFile );
|
||||
}
|
||||
|
||||
return users.values();
|
||||
}
|
||||
|
||||
private static ImmutableList<File> listUserFiles(final File userFilesDirectory) {
|
||||
return ImmutableList.copyOf( ifNotNullElse( userFilesDirectory.listFiles( (dir, name) -> {
|
||||
for (final MPMarshalFormat format : MPMarshalFormat.values())
|
||||
if (name.endsWith( format.fileSuffix() ))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
} ), new File[0] ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteUser(final MPFileUser user) {
|
||||
super.deleteUser( user );
|
||||
|
||||
// Remove deleted users.
|
||||
File userFile = getUserFile( user, user.getFormat() );
|
||||
if (userFile.exists() && !userFile.delete())
|
||||
logger.err( "Couldn't delete file: %s", userFile );
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the current user state to disk.
|
||||
*/
|
||||
public void save(final MPFileUser user, final MPMasterKey masterKey)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
try {
|
||||
MPMarshalFormat format = user.getFormat();
|
||||
new CharSink() {
|
||||
@Override
|
||||
public Writer openStream()
|
||||
throws IOException {
|
||||
return new OutputStreamWriter( new FileOutputStream( getUserFile( user, format ) ), Charsets.UTF_8 );
|
||||
}
|
||||
}.write( format.marshaller().marshall( user ) );
|
||||
}
|
||||
catch (final MPMarshalException | IOException e) {
|
||||
logger.err( e, "Unable to save sites for user: %s", user );
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private File getUserFile(final MPUser<?> user, final MPMarshalFormat format) {
|
||||
return new File( path, user.getFullName() + format.fileSuffix() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The location on the file system where the user models are stored.
|
||||
*/
|
||||
public File getPath() {
|
||||
return path;
|
||||
}
|
||||
}
|
@@ -0,0 +1,85 @@
|
||||
//==============================================================================
|
||||
// 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.impl;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||
|
||||
import com.lyndir.masterpassword.MPAlgorithmException;
|
||||
import com.lyndir.masterpassword.MPKeyUnavailableException;
|
||||
import com.lyndir.masterpassword.model.MPConstant;
|
||||
import javax.annotation.Nonnull;
|
||||
import org.joda.time.Instant;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2017-09-20
|
||||
*/
|
||||
@SuppressWarnings({ "HardcodedLineSeparator", "MagicCharacter" })
|
||||
public class MPFlatMarshaller implements MPMarshaller {
|
||||
|
||||
private static final int FORMAT = 1;
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String marshall(final MPFileUser user)
|
||||
throws MPKeyUnavailableException, MPMarshalException, MPAlgorithmException {
|
||||
StringBuilder content = new StringBuilder();
|
||||
content.append( "# Master Password site export\n" );
|
||||
content.append( "# " ).append( user.getContentMode().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( user.getAlgorithm().version().toInt() ).append( '\n' );
|
||||
content.append( "# Default Type: " ).append( user.getDefaultType().getType() ).append( '\n' );
|
||||
content.append( "# Passwords: " ).append( user.getContentMode().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 MPFileSite site : user.getSites()) {
|
||||
String loginName = site.getLoginState();
|
||||
String password = site.getResultState();
|
||||
if (!user.getContentMode().isRedacted()) {
|
||||
loginName = site.getLogin();
|
||||
password = site.getResult();
|
||||
}
|
||||
|
||||
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.getAlgorithm().version().toInt(), // algorithm
|
||||
site.getCounter().intValue() ), // counter
|
||||
ifNotNullElse( loginName, "" ), // loginName
|
||||
site.getName(), // siteName
|
||||
ifNotNullElse( password, "" ) // password
|
||||
) );
|
||||
}
|
||||
|
||||
return content.toString();
|
||||
}
|
||||
}
|
@@ -0,0 +1,159 @@
|
||||
//==============================================================================
|
||||
// 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.impl;
|
||||
|
||||
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.logging.Logger;
|
||||
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
|
||||
import com.lyndir.masterpassword.*;
|
||||
import com.lyndir.masterpassword.model.MPConstant;
|
||||
import com.lyndir.masterpassword.model.MPIncorrectMasterPasswordException;
|
||||
import java.io.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import org.joda.time.Instant;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 14-12-07
|
||||
*/
|
||||
public class MPFlatUnmarshaller implements MPUnmarshaller {
|
||||
|
||||
private static final Logger logger = Logger.get( MPFlatUnmarshaller.class );
|
||||
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 MPFileUser unmarshall(@Nonnull final File file, @Nullable final char[] masterPassword)
|
||||
throws IOException, MPMarshalException, MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException {
|
||||
try (Reader reader = new InputStreamReader( new FileInputStream( file ), Charsets.UTF_8 )) {
|
||||
return unmarshall( CharStreams.toString( reader ), masterPassword );
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPFileUser unmarshall(@Nonnull final String content, @Nullable final char[] masterPassword)
|
||||
throws MPMarshalException, MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException {
|
||||
MPFileUser user = null;
|
||||
byte[] keyID = null;
|
||||
String fullName = null;
|
||||
int mpVersion = 0, importFormat = 0, avatar = 0;
|
||||
boolean clearContent = false, headerStarted = false;
|
||||
MPResultType defaultType = null;
|
||||
|
||||
//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 MPFileUser( fullName, keyID, MPAlgorithm.Version.fromInt( mpVersion ).getAlgorithm(),
|
||||
avatar, defaultType, new Instant( 0 ), MPMarshalFormat.Flat,
|
||||
clearContent? MPMarshaller.ContentMode.VISIBLE: MPMarshaller.ContentMode.PROTECTED );
|
||||
|
||||
// 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()) {
|
||||
logger.wrn( "Couldn't parse line: %s, skipping.", line );
|
||||
continue;
|
||||
}
|
||||
|
||||
MPFileSite site;
|
||||
switch (importFormat) {
|
||||
case 0:
|
||||
site = new MPFileSite( user, //
|
||||
siteMatcher.group( 5 ), MPAlgorithm.Version.fromInt( ConversionUtils.toIntegerNN(
|
||||
colon.matcher( siteMatcher.group( 4 ) ).replaceAll( "" ) ) ).getAlgorithm(),
|
||||
user.getAlgorithm().mpw_default_counter(),
|
||||
MPResultType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),
|
||||
clearContent? null: siteMatcher.group( 6 ),
|
||||
null, null, null, ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ),
|
||||
MPConstant.dateTimeFormatter.parseDateTime( siteMatcher.group( 1 ) ).toInstant() );
|
||||
if (clearContent)
|
||||
site.setSitePassword( site.getResultType(), siteMatcher.group( 6 ) );
|
||||
break;
|
||||
|
||||
case 1:
|
||||
site = new MPFileSite( user, //
|
||||
siteMatcher.group( 7 ), MPAlgorithm.Version.fromInt( ConversionUtils.toIntegerNN(
|
||||
colon.matcher( siteMatcher.group( 4 ) ).replaceAll( "" ) ) ).getAlgorithm(),
|
||||
UnsignedInteger.valueOf( colon.matcher( siteMatcher.group( 5 ) ).replaceAll( "" ) ),
|
||||
MPResultType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),
|
||||
clearContent? null: siteMatcher.group( 8 ),
|
||||
MPResultType.GeneratedName, clearContent? null: siteMatcher.group( 6 ), null,
|
||||
ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ),
|
||||
MPConstant.dateTimeFormatter.parseDateTime( siteMatcher.group( 1 ) ).toInstant() );
|
||||
if (clearContent) {
|
||||
site.setSitePassword( site.getResultType(), siteMatcher.group( 8 ) );
|
||||
site.setLoginName( MPResultType.StoredPersonal, siteMatcher.group( 6 ) );
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new MPMarshalException( "Unexpected format: " + importFormat );
|
||||
}
|
||||
|
||||
user.addSite( site );
|
||||
}
|
||||
|
||||
if (user == null)
|
||||
throw new MPMarshalException( "No full header found in import file." );
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
//==============================================================================
|
||||
// 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.impl;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAnyGetter;
|
||||
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
||||
import java.util.*;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2018-05-14
|
||||
*/
|
||||
class MPJSONAnyObject {
|
||||
|
||||
@JsonAnySetter
|
||||
final Map<String, Object> any = new LinkedHashMap<>();
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> getAny() {
|
||||
return Collections.unmodifiableMap( any );
|
||||
}
|
||||
}
|
@@ -0,0 +1,244 @@
|
||||
//==============================================================================
|
||||
// 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.impl;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||
|
||||
import com.fasterxml.jackson.annotation.*;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.lhunath.opal.system.CodeUtils;
|
||||
import com.lyndir.masterpassword.*;
|
||||
import com.lyndir.masterpassword.model.MPConstant;
|
||||
import com.lyndir.masterpassword.model.MPIncorrectMasterPasswordException;
|
||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
import org.joda.time.Instant;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2018-04-27
|
||||
*/
|
||||
@SuppressFBWarnings( "URF_UNREAD_FIELD" )
|
||||
public class MPJSONFile extends MPJSONAnyObject {
|
||||
|
||||
protected static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
static {
|
||||
objectMapper.setSerializationInclusion( JsonInclude.Include.NON_EMPTY );
|
||||
objectMapper.setVisibility( PropertyAccessor.FIELD, JsonAutoDetect.Visibility.NON_PRIVATE );
|
||||
}
|
||||
|
||||
public MPJSONFile write(final MPFileUser modelUser)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
// Section: "export"
|
||||
if (export == null)
|
||||
export = new Export();
|
||||
export.format = 1;
|
||||
export.redacted = modelUser.getContentMode().isRedacted();
|
||||
export.date = MPConstant.dateTimeFormatter.print( new Instant() );
|
||||
|
||||
// Section: "user"
|
||||
if (user == null)
|
||||
user = new User();
|
||||
user.avatar = modelUser.getAvatar();
|
||||
user.full_name = modelUser.getFullName();
|
||||
user.last_used = MPConstant.dateTimeFormatter.print( modelUser.getLastUsed() );
|
||||
user.key_id = modelUser.exportKeyID();
|
||||
user.algorithm = modelUser.getAlgorithm().version();
|
||||
user.default_type = modelUser.getDefaultType();
|
||||
|
||||
// Section "sites"
|
||||
if (sites == null)
|
||||
sites = new LinkedHashMap<>();
|
||||
for (final MPFileSite modelSite : modelUser.getSites()) {
|
||||
String content = null, loginContent = null;
|
||||
if (!export.redacted) {
|
||||
// Clear Text
|
||||
content = modelSite.getResult();
|
||||
loginContent = modelUser.getMasterKey().siteResult(
|
||||
modelSite.getName(), modelSite.getAlgorithm(), modelSite.getAlgorithm().mpw_default_counter(),
|
||||
MPKeyPurpose.Identification, null, modelSite.getLoginType(), modelSite.getLoginState() );
|
||||
} else {
|
||||
// Redacted
|
||||
if (modelSite.getResultType().supportsTypeFeature( MPSiteFeature.ExportContent ))
|
||||
content = modelSite.getResultState();
|
||||
if (modelSite.getLoginType().supportsTypeFeature( MPSiteFeature.ExportContent ))
|
||||
loginContent = modelSite.getLoginState();
|
||||
}
|
||||
|
||||
Site site = sites.get( modelSite.getName() );
|
||||
if (site == null)
|
||||
sites.put( modelSite.getName(), site = new Site() );
|
||||
site.type = modelSite.getResultType();
|
||||
site.counter = modelSite.getCounter().longValue();
|
||||
site.algorithm = modelSite.getAlgorithm().version();
|
||||
site.password = content;
|
||||
site.login_name = loginContent;
|
||||
site.login_type = modelSite.getLoginType();
|
||||
|
||||
site.uses = modelSite.getUses();
|
||||
site.last_used = MPConstant.dateTimeFormatter.print( modelSite.getLastUsed() );
|
||||
|
||||
if (site._ext_mpw == null)
|
||||
site._ext_mpw = new Site.Ext();
|
||||
site._ext_mpw.url = modelSite.getUrl();
|
||||
|
||||
if (site.questions == null)
|
||||
site.questions = new LinkedHashMap<>();
|
||||
// for (size_t q = 0; q < site.questions_count; ++q) {
|
||||
// MPMarshalledQuestion *question = &site.questions[q];
|
||||
// if (!question.keyword)
|
||||
// continue;
|
||||
//
|
||||
// json_object *json_site_question = json_object_new_object();
|
||||
// json_object_object_add( json_site_questions, question.keyword, json_site_question );
|
||||
// json_object_object_add( json_site_question, "type = question.type;
|
||||
//
|
||||
// if (!user.redacted) {
|
||||
// // Clear Text
|
||||
// const char *answerContent = mpw_siteResult( masterKey, site.name, MPCounterValueInitial,
|
||||
// MPKeyPurposeRecovery, question.keyword, question.type, question.content, site.algorithm );
|
||||
// json_object_object_add( json_site_question, "answer = answerContent;
|
||||
// }
|
||||
// else {
|
||||
// // Redacted
|
||||
// if (site.type & MPSiteFeatureExportContent && question.content && strlen( question.content ))
|
||||
// json_object_object_add( json_site_question, "answer = question.content;
|
||||
// }
|
||||
// }
|
||||
|
||||
// json_object *json_site_mpw = json_object_new_object();
|
||||
// fileSite._ext_mpw = json_site_mpw;
|
||||
// if (site.url)
|
||||
// json_object_object_add( json_site_mpw, "url", site.url );
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public MPFileUser read(@Nullable final char[] masterPassword)
|
||||
throws MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException {
|
||||
MPAlgorithm algorithm = ifNotNullElse( user.algorithm, MPAlgorithm.Version.CURRENT ).getAlgorithm();
|
||||
MPFileUser model = new MPFileUser(
|
||||
user.full_name, CodeUtils.decodeHex( user.key_id ), algorithm, user.avatar,
|
||||
(user.default_type != null)? user.default_type: algorithm.mpw_default_result_type(),
|
||||
(user.last_used != null)? MPConstant.dateTimeFormatter.parseDateTime( user.last_used ): new Instant(),
|
||||
MPMarshalFormat.JSON, export.redacted? MPMarshaller.ContentMode.PROTECTED: MPMarshaller.ContentMode.VISIBLE );
|
||||
model.setJSON( this );
|
||||
if (masterPassword != null)
|
||||
model.authenticate( masterPassword );
|
||||
|
||||
for (final Map.Entry<String, Site> siteEntry : sites.entrySet()) {
|
||||
String siteName = siteEntry.getKey();
|
||||
Site fileSite = siteEntry.getValue();
|
||||
MPFileSite site = new MPFileSite(
|
||||
model, siteName, fileSite.algorithm.getAlgorithm(), UnsignedInteger.valueOf( fileSite.counter ), fileSite.type,
|
||||
export.redacted? fileSite.password: null,
|
||||
fileSite.login_type, export.redacted? fileSite.login_name: null,
|
||||
(fileSite._ext_mpw != null)? fileSite._ext_mpw.url: null, fileSite.uses,
|
||||
(fileSite.last_used != null)? MPConstant.dateTimeFormatter.parseDateTime( fileSite.last_used ): new Instant() );
|
||||
|
||||
if (!export.redacted) {
|
||||
if (fileSite.password != null)
|
||||
site.setSitePassword( (fileSite.type != null)? fileSite.type: MPResultType.StoredPersonal, fileSite.password );
|
||||
if (fileSite.login_name != null)
|
||||
site.setLoginName( (fileSite.login_type != null)? fileSite.login_type: MPResultType.StoredPersonal,
|
||||
fileSite.login_name );
|
||||
}
|
||||
|
||||
model.addSite( site );
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
// -- Data
|
||||
|
||||
Export export;
|
||||
User user;
|
||||
Map<String, Site> sites;
|
||||
|
||||
|
||||
public static class Export extends MPJSONAnyObject {
|
||||
|
||||
int format;
|
||||
boolean redacted;
|
||||
@Nullable
|
||||
String date;
|
||||
}
|
||||
|
||||
|
||||
public static class User extends MPJSONAnyObject {
|
||||
|
||||
int avatar;
|
||||
String full_name;
|
||||
String last_used;
|
||||
@Nullable
|
||||
MPAlgorithm.Version algorithm;
|
||||
@Nullable
|
||||
String key_id;
|
||||
@Nullable
|
||||
MPResultType default_type;
|
||||
}
|
||||
|
||||
|
||||
public static class Site extends MPJSONAnyObject {
|
||||
|
||||
long counter;
|
||||
MPAlgorithm.Version algorithm;
|
||||
@Nullable
|
||||
MPResultType type;
|
||||
@Nullable
|
||||
String password;
|
||||
@Nullable
|
||||
MPResultType login_type;
|
||||
@Nullable
|
||||
String login_name;
|
||||
|
||||
int uses;
|
||||
@Nullable
|
||||
String last_used;
|
||||
|
||||
@Nullable
|
||||
Map<String, Question> questions;
|
||||
|
||||
@Nullable
|
||||
Ext _ext_mpw;
|
||||
|
||||
|
||||
public static class Ext extends MPJSONAnyObject {
|
||||
|
||||
@Nullable
|
||||
String url;
|
||||
}
|
||||
|
||||
|
||||
public static class Question extends MPJSONAnyObject {
|
||||
|
||||
@Nullable
|
||||
MPResultType type;
|
||||
@Nullable
|
||||
String answer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,46 @@
|
||||
//==============================================================================
|
||||
// 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.impl;
|
||||
|
||||
import static com.lyndir.masterpassword.model.impl.MPJSONFile.*;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.lyndir.masterpassword.MPAlgorithmException;
|
||||
import com.lyndir.masterpassword.MPKeyUnavailableException;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2017-09-20
|
||||
*/
|
||||
public class MPJSONMarshaller implements MPMarshaller {
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String marshall(final MPFileUser user)
|
||||
throws MPKeyUnavailableException, MPMarshalException, MPAlgorithmException {
|
||||
|
||||
try {
|
||||
return objectMapper.writeValueAsString( user.getJSON().write( user ) );
|
||||
}
|
||||
catch (final JsonProcessingException e) {
|
||||
throw new MPMarshalException( "Couldn't compose JSON for: " + user, e );
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,73 @@
|
||||
//==============================================================================
|
||||
// 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.impl;
|
||||
|
||||
import static com.lyndir.masterpassword.model.impl.MPJSONFile.*;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParseException;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
import com.lyndir.masterpassword.MPAlgorithmException;
|
||||
import com.lyndir.masterpassword.MPKeyUnavailableException;
|
||||
import com.lyndir.masterpassword.model.MPIncorrectMasterPasswordException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2017-09-20
|
||||
*/
|
||||
public class MPJSONUnmarshaller implements MPUnmarshaller {
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPFileUser unmarshall(@Nonnull final File file, @Nullable final char[] masterPassword)
|
||||
throws IOException, MPMarshalException, MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException {
|
||||
|
||||
try {
|
||||
return objectMapper.readValue( file, MPJSONFile.class ).read( masterPassword );
|
||||
}
|
||||
catch (final JsonParseException e) {
|
||||
throw new MPMarshalException( "Couldn't parse JSON in: " + file, e );
|
||||
}
|
||||
catch (final JsonMappingException e) {
|
||||
throw new MPMarshalException( "Couldn't map JSON in: " + file, e );
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPFileUser unmarshall(@Nonnull final String content, @Nullable final char[] masterPassword)
|
||||
throws MPMarshalException, MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException {
|
||||
|
||||
try {
|
||||
return objectMapper.readValue( content, MPJSONFile.class ).read( masterPassword );
|
||||
}
|
||||
catch (final JsonParseException e) {
|
||||
throw new MPMarshalException( "Couldn't parse JSON.", e );
|
||||
}
|
||||
catch (final JsonMappingException e) {
|
||||
throw new MPMarshalException( "Couldn't map JSON.", e );
|
||||
}
|
||||
catch (final IOException e) {
|
||||
throw new MPMarshalException( "Couldn't read JSON.", e );
|
||||
}
|
||||
}
|
||||
}
|
@@ -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.impl;
|
||||
|
||||
import com.lyndir.masterpassword.MPException;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2018-04-26
|
||||
*/
|
||||
public class MPMarshalException extends MPException {
|
||||
|
||||
public MPMarshalException(final String message) {
|
||||
super( message );
|
||||
}
|
||||
|
||||
public MPMarshalException(final String message, final Throwable cause) {
|
||||
super( message, cause );
|
||||
}
|
||||
}
|
@@ -0,0 +1,75 @@
|
||||
//==============================================================================
|
||||
// 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.impl;
|
||||
|
||||
/**
|
||||
* @author lhunath, 2017-09-20
|
||||
*
|
||||
* This enum is ordered from oldest to newest format, the latest being most preferred.
|
||||
*/
|
||||
public enum MPMarshalFormat {
|
||||
/**
|
||||
* Marshal using the line-based plain-text format.
|
||||
*/
|
||||
Flat {
|
||||
@Override
|
||||
public MPMarshaller marshaller() {
|
||||
return new MPFlatMarshaller();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MPUnmarshaller unmarshaller() {
|
||||
return new MPFlatUnmarshaller();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String fileSuffix() {
|
||||
return ".mpsites";
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Marshal using the JSON structured format.
|
||||
*/
|
||||
JSON {
|
||||
@Override
|
||||
public MPMarshaller marshaller() {
|
||||
return new MPJSONMarshaller();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MPUnmarshaller unmarshaller() {
|
||||
return new MPJSONUnmarshaller();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String fileSuffix() {
|
||||
return ".mpsites.json";
|
||||
}
|
||||
};
|
||||
|
||||
public static final MPMarshalFormat DEFAULT = JSON;
|
||||
|
||||
public abstract MPMarshaller marshaller();
|
||||
|
||||
public abstract MPUnmarshaller unmarshaller();
|
||||
|
||||
@SuppressWarnings("MethodReturnAlwaysConstant")
|
||||
public abstract String fileSuffix();
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
//==============================================================================
|
||||
// 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.impl;
|
||||
|
||||
import com.lyndir.masterpassword.MPAlgorithmException;
|
||||
import com.lyndir.masterpassword.MPKeyUnavailableException;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 14-12-07
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface MPMarshaller {
|
||||
|
||||
@Nonnull
|
||||
String marshall(MPFileUser user)
|
||||
throws MPKeyUnavailableException, MPMarshalException, MPAlgorithmException;
|
||||
|
||||
enum ContentMode {
|
||||
PROTECTED( "Export of site names and stored passwords (unless device-private) encrypted with the master key.", true ),
|
||||
VISIBLE( "Export of site names and passwords in clear-text.", false );
|
||||
|
||||
private final String description;
|
||||
private final boolean redacted;
|
||||
|
||||
ContentMode(final String description, final boolean redacted) {
|
||||
this.description = description;
|
||||
this.redacted = redacted;
|
||||
}
|
||||
|
||||
public String description() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public boolean isRedacted() {
|
||||
return redacted;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
//==============================================================================
|
||||
// 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.impl;
|
||||
|
||||
import com.lyndir.masterpassword.MPAlgorithmException;
|
||||
import com.lyndir.masterpassword.MPKeyUnavailableException;
|
||||
import com.lyndir.masterpassword.model.MPIncorrectMasterPasswordException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 14-12-07
|
||||
*/
|
||||
public interface MPUnmarshaller {
|
||||
|
||||
@Nonnull
|
||||
MPFileUser unmarshall(@Nonnull File file, @Nullable char[] masterPassword)
|
||||
throws IOException, MPMarshalException, MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException;
|
||||
|
||||
@Nonnull
|
||||
MPFileUser unmarshall(@Nonnull String content, @Nullable char[] masterPassword)
|
||||
throws MPMarshalException, MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException;
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
//==============================================================================
|
||||
// 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/>.
|
||||
//==============================================================================
|
||||
|
||||
/**
|
||||
* @author lhunath, 2018-05-14
|
||||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
package com.lyndir.masterpassword.model.impl;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
@@ -0,0 +1,26 @@
|
||||
//==============================================================================
|
||||
// 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/>.
|
||||
//==============================================================================
|
||||
|
||||
/**
|
||||
* @author lhunath, 15-02-04
|
||||
*/
|
||||
|
||||
@ParametersAreNonnullByDefault
|
||||
package com.lyndir.masterpassword.model;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
19
platform-independent/java/core/tests/build.gradle
Normal file
19
platform-independent/java/core/tests/build.gradle
Normal file
@@ -0,0 +1,19 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
}
|
||||
|
||||
description = 'Master Password Test Suite'
|
||||
|
||||
dependencies {
|
||||
implementation group: 'com.lyndir.lhunath.opal', name: 'opal-system', version: '1.7-p1'
|
||||
|
||||
compile project( ':masterpassword-algorithm' )
|
||||
compile project( ':masterpassword-model' )
|
||||
|
||||
testCompile group: 'org.testng', name: 'testng', version: '6.8.5'
|
||||
testCompile group: 'ch.qos.logback', name: 'logback-classic', version: '1.1.2'
|
||||
}
|
||||
//tasks.withType(Test) {
|
||||
// systemProperty "java.library.path", "/Users/lhunath/Documents/workspace/lyndir/MasterPassword/core/java/algorithm/build/libs/mpw/shared"
|
||||
//}
|
||||
test.useTestNG()
|
@@ -0,0 +1,204 @@
|
||||
//==============================================================================
|
||||
// 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.base.Preconditions;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Callable;
|
||||
import javax.xml.parsers.*;
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.ext.DefaultHandler2;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2015-12-22
|
||||
*/
|
||||
@SuppressWarnings({ "HardCodedStringLiteral", "ProhibitedExceptionDeclared" })
|
||||
public class MPTestSuite implements Callable<Boolean> {
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( MPTestSuite.class );
|
||||
private static final String DEFAULT_RESOURCE_NAME = "mpw_tests.xml";
|
||||
|
||||
private final MPTests tests;
|
||||
private Listener listener;
|
||||
|
||||
public MPTestSuite()
|
||||
throws UnavailableException {
|
||||
this( DEFAULT_RESOURCE_NAME );
|
||||
}
|
||||
|
||||
public MPTestSuite(final String resourceName)
|
||||
throws UnavailableException {
|
||||
try {
|
||||
tests = new MPTests();
|
||||
tests.cases = Lists.newLinkedList();
|
||||
SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
|
||||
Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources( "." );
|
||||
parser.parse( Thread.currentThread().getContextClassLoader().getResourceAsStream( resourceName ), new DefaultHandler2() {
|
||||
private final Deque<String> currentTags = Lists.newLinkedList();
|
||||
private final Deque<StringBuilder> currentTexts = Lists.newLinkedList();
|
||||
private MPTests.Case currentCase;
|
||||
|
||||
@Override
|
||||
public void startElement(final String uri, final String localName, final String qName, final Attributes attributes)
|
||||
throws SAXException {
|
||||
super.startElement( uri, localName, qName, attributes );
|
||||
currentTags.push( qName );
|
||||
currentTexts.push( new StringBuilder() );
|
||||
|
||||
if ("case".equals( qName )) {
|
||||
currentCase = new MPTests.Case();
|
||||
currentCase.identifier = attributes.getValue( "id" );
|
||||
currentCase.parent = attributes.getValue( "parent" );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endElement(final String uri, final String localName, final String qName)
|
||||
throws SAXException {
|
||||
super.endElement( uri, localName, qName );
|
||||
Preconditions.checkState( qName.equals( currentTags.pop() ) );
|
||||
String text = Preconditions.checkNotNull( currentTexts.pop() ).toString();
|
||||
|
||||
if ("case".equals( qName ))
|
||||
tests.cases.add( currentCase );
|
||||
if ("algorithm".equals( qName ))
|
||||
currentCase.algorithm = ConversionUtils.toInteger( text ).orElse( null );
|
||||
if ("fullName".equals( qName ))
|
||||
currentCase.fullName = text;
|
||||
if ("masterPassword".equals( qName ))
|
||||
currentCase.masterPassword = text;
|
||||
if ("keyID".equals( qName ))
|
||||
currentCase.keyID = text;
|
||||
if ("siteName".equals( qName ))
|
||||
currentCase.siteName = text;
|
||||
if ("siteCounter".equals( qName ))
|
||||
currentCase.siteCounter = text.isEmpty()? null: UnsignedInteger.valueOf( text );
|
||||
if ("resultType".equals( qName ))
|
||||
currentCase.resultType = text;
|
||||
if ("keyPurpose".equals( qName ))
|
||||
currentCase.keyPurpose = text;
|
||||
if ("keyContext".equals( qName ))
|
||||
currentCase.keyContext = text;
|
||||
if ("result".equals( qName ))
|
||||
currentCase.result = text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void characters(final char[] ch, final int start, final int length)
|
||||
throws SAXException {
|
||||
super.characters( ch, start, length );
|
||||
|
||||
Preconditions.checkNotNull( currentTexts.peek() ).append( ch, start, length );
|
||||
}
|
||||
} );
|
||||
}
|
||||
catch (final IllegalArgumentException | ParserConfigurationException | SAXException | IOException e) {
|
||||
throw new UnavailableException( e );
|
||||
}
|
||||
|
||||
for (final MPTests.Case testCase : tests.getCases())
|
||||
testCase.initializeParentHierarchy( tests );
|
||||
}
|
||||
|
||||
public void setListener(final Listener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public MPTests getTests() {
|
||||
return tests;
|
||||
}
|
||||
|
||||
public boolean forEach(final String testName, final TestCase testFunction)
|
||||
throws Exception {
|
||||
List<MPTests.Case> cases = tests.getCases();
|
||||
for (int c = 0; c < cases.size(); c++) {
|
||||
MPTests.Case testCase = cases.get( c );
|
||||
if (testCase.getResult().isEmpty())
|
||||
continue;
|
||||
|
||||
progress( Logger.Target.INFO, c, cases.size(), //
|
||||
"[%s] on %s...", testName, testCase.getIdentifier() );
|
||||
|
||||
if (!testFunction.run( testCase )) {
|
||||
progress( Logger.Target.ERROR, cases.size(), cases.size(), //
|
||||
"[%s] on %s: FAILED!", testName, testCase.getIdentifier() );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
progress( Logger.Target.INFO, c + 1, cases.size(), //
|
||||
"[%s] on %s: passed!", testName, testCase.getIdentifier() );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void progress(final Logger.Target target, final int current, final int max, final String format, final Object... args) {
|
||||
logger.log( target, format, args );
|
||||
|
||||
if (listener != null)
|
||||
listener.progress( current, max, format, args );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean call()
|
||||
throws Exception {
|
||||
return forEach( "mpw", testCase -> {
|
||||
MPMasterKey masterKey = new MPMasterKey( testCase.getFullName(), testCase.getMasterPassword().toCharArray() );
|
||||
String sitePassword = masterKey.siteResult( testCase.getSiteName(), testCase.getAlgorithm(), testCase.getSiteCounter(),
|
||||
testCase.getKeyPurpose(), testCase.getKeyContext(),
|
||||
testCase.getResultType(), null );
|
||||
|
||||
return testCase.getResult().equals( sitePassword );
|
||||
} );
|
||||
}
|
||||
|
||||
public static class UnavailableException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public UnavailableException(final Throwable cause) {
|
||||
super( cause );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Listener {
|
||||
|
||||
void progress(int current, int max, String messageFormat, Object... args);
|
||||
}
|
||||
|
||||
|
||||
@FunctionalInterface
|
||||
public interface TestCase {
|
||||
|
||||
boolean run(MPTests.Case testCase)
|
||||
throws Exception;
|
||||
}
|
||||
}
|
@@ -0,0 +1,246 @@
|
||||
//==============================================================================
|
||||
// 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 static com.google.common.base.Preconditions.*;
|
||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.lhunath.opal.system.util.NNSupplier;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.xml.bind.annotation.XmlTransient;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 14-12-05
|
||||
*/
|
||||
public class MPTests {
|
||||
|
||||
private static final String ID_DEFAULT = "default";
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( MPTests.class );
|
||||
|
||||
private final Set<String> filters = new HashSet<>();
|
||||
|
||||
List<Case> cases;
|
||||
|
||||
@Nonnull
|
||||
public List<Case> getCases() {
|
||||
if (filters.isEmpty())
|
||||
return checkNotNull( cases );
|
||||
|
||||
return checkNotNull( cases ).stream().filter( testCase -> {
|
||||
for (final String filter : filters)
|
||||
if (testCase.getIdentifier().startsWith( filter ))
|
||||
return true;
|
||||
return false;
|
||||
} ).collect( Collectors.toList() );
|
||||
}
|
||||
|
||||
public Case getCase(final String identifier) {
|
||||
for (final Case testCase : cases)
|
||||
if (identifier.equals( testCase.getIdentifier() ))
|
||||
return testCase;
|
||||
|
||||
throw new IllegalArgumentException( strf( "No case for identifier: %s", identifier ) );
|
||||
}
|
||||
|
||||
public Case getDefaultCase() {
|
||||
try {
|
||||
return getCase( ID_DEFAULT );
|
||||
}
|
||||
catch (final IllegalArgumentException e) {
|
||||
throw new IllegalStateException( strf( "Missing default case in test suite. Add a case with id: %s", ID_DEFAULT ), e );
|
||||
}
|
||||
}
|
||||
|
||||
public boolean addFilters(final String... filters) {
|
||||
return this.filters.addAll( Arrays.asList( filters ) );
|
||||
}
|
||||
|
||||
public static class Case {
|
||||
|
||||
String identifier;
|
||||
String parent;
|
||||
@Nullable
|
||||
Integer algorithm;
|
||||
String fullName;
|
||||
String masterPassword;
|
||||
String keyID;
|
||||
String siteName;
|
||||
@Nullable
|
||||
UnsignedInteger siteCounter;
|
||||
String resultType;
|
||||
String keyPurpose;
|
||||
String keyContext;
|
||||
String result;
|
||||
|
||||
@XmlTransient
|
||||
private Case parentCase;
|
||||
|
||||
public void initializeParentHierarchy(final MPTests tests) {
|
||||
|
||||
if (parent != null) {
|
||||
parentCase = tests.getCase( parent );
|
||||
parentCase.initializeParentHierarchy( tests );
|
||||
}
|
||||
|
||||
algorithm = ifNotNullElse( algorithm, new NNSupplier<Integer>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public Integer get() {
|
||||
return checkNotNull( parentCase.algorithm );
|
||||
}
|
||||
} );
|
||||
fullName = ifNotNullElse( fullName, new NNSupplier<String>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public String get() {
|
||||
return checkNotNull( parentCase.fullName );
|
||||
}
|
||||
} );
|
||||
masterPassword = ifNotNullElse( masterPassword, new NNSupplier<String>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public String get() {
|
||||
return checkNotNull( parentCase.masterPassword );
|
||||
}
|
||||
} );
|
||||
keyID = ifNotNullElse( keyID, new NNSupplier<String>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public String get() {
|
||||
return checkNotNull( parentCase.keyID );
|
||||
}
|
||||
} );
|
||||
siteName = ifNotNullElse( siteName, new NNSupplier<String>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public String get() {
|
||||
return checkNotNull( parentCase.siteName );
|
||||
}
|
||||
} );
|
||||
siteCounter = ifNotNullElse( siteCounter, new NNSupplier<UnsignedInteger>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public UnsignedInteger get() {
|
||||
return checkNotNull( parentCase.siteCounter );
|
||||
}
|
||||
} );
|
||||
resultType = ifNotNullElse( resultType, new NNSupplier<String>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public String get() {
|
||||
return checkNotNull( parentCase.resultType );
|
||||
}
|
||||
} );
|
||||
keyPurpose = ifNotNullElse( keyPurpose, new NNSupplier<String>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public String get() {
|
||||
return checkNotNull( parentCase.keyPurpose );
|
||||
}
|
||||
} );
|
||||
keyContext = ifNotNullElse( keyContext, new NNSupplier<String>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public String get() {
|
||||
return (parentCase == null)? "": checkNotNull( parentCase.keyContext );
|
||||
}
|
||||
} );
|
||||
result = ifNotNullElse( result, new NNSupplier<String>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public String get() {
|
||||
return (parentCase == null)? "": checkNotNull( parentCase.result );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Case getParentCase() {
|
||||
return parentCase;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public MPAlgorithm getAlgorithm() {
|
||||
return MPAlgorithm.Version.fromInt( checkNotNull( algorithm ) ).getAlgorithm();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getFullName() {
|
||||
return checkNotNull( fullName );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getMasterPassword() {
|
||||
return checkNotNull( masterPassword );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getKeyID() {
|
||||
return checkNotNull( keyID );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getSiteName() {
|
||||
return checkNotNull( siteName );
|
||||
}
|
||||
|
||||
public UnsignedInteger getSiteCounter() {
|
||||
return ifNotNullElse( siteCounter, UnsignedInteger.valueOf( 1 ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public MPResultType getResultType() {
|
||||
return MPResultType.forName( checkNotNull( resultType ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public MPKeyPurpose getKeyPurpose() {
|
||||
return MPKeyPurpose.forName( checkNotNull( keyPurpose ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getKeyContext() {
|
||||
return checkNotNull( keyContext );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getResult() {
|
||||
return checkNotNull( result );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return identifier;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1 @@
|
||||
../../../../../../c/cli/mpw_tests.xml
|
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"export": {
|
||||
"format": 1,
|
||||
"redacted": true,
|
||||
"date": "2018-05-10T03:41:18Z",
|
||||
"_ext_mpw": {
|
||||
"save": "me"
|
||||
},
|
||||
"_ext_other": {
|
||||
"save": "me"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"avatar": 3,
|
||||
"full_name": "Robert Lee Mitchell",
|
||||
"last_used": "2018-05-10T03:41:18Z",
|
||||
"key_id": "98EEF4D1DF46D849574A82A03C3177056B15DFFCA29BB3899DE4628453675302",
|
||||
"algorithm": 3,
|
||||
"default_type": 17,
|
||||
"_ext_mpw": {
|
||||
"save": "me"
|
||||
},
|
||||
"_ext_other": {
|
||||
"save": "me"
|
||||
}
|
||||
},
|
||||
"sites": {
|
||||
"masterpasswordapp.com": {
|
||||
"type": 17,
|
||||
"counter": 1,
|
||||
"algorithm": 3,
|
||||
"login_type": 30,
|
||||
"uses": 2,
|
||||
"last_used": "2018-05-10T03:41:18Z",
|
||||
"questions": {
|
||||
"": {
|
||||
"type": 31
|
||||
},
|
||||
"mother": {
|
||||
"type": 31
|
||||
}
|
||||
},
|
||||
"_ext_mpw": {
|
||||
"url": "https://masterpasswordapp.com",
|
||||
"save": "me"
|
||||
},
|
||||
"_ext_other": {
|
||||
"save": "me"
|
||||
}
|
||||
},
|
||||
"personal.site": {
|
||||
"type": 1056,
|
||||
"counter": 1,
|
||||
"algorithm": 3,
|
||||
"password": "ZTgr4cY6L28wG7DsO+iz\/hrTQxM3UHz0x8ZU99LjgxjHG+bLIJygkbg\/7HdjEIFH6A3z+Dt2H1gpt9yPyQGZcewTiPXJX0pNpVsIKAAdzVNcUfYoqkWjoFRoZD7sM\/ctxWDH4JUuJ+rjoBkWtRLK9kYBvu7UD1QdlEZI\/wPKv1A=",
|
||||
"login_type": 30,
|
||||
"uses": 1,
|
||||
"last_used": "2018-05-10T03:48:35Z"
|
||||
}
|
||||
},
|
||||
"_ext_mpw": {
|
||||
"save": "me"
|
||||
},
|
||||
"_ext_other": {
|
||||
"save": "me"
|
||||
}
|
||||
}
|
@@ -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;
|
||||
|
||||
import static org.testng.Assert.*;
|
||||
|
||||
import com.lyndir.lhunath.opal.system.CodeUtils;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import java.util.Random;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
|
||||
public class MPMasterKeyTest {
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( MPMasterKeyTest.class );
|
||||
|
||||
private MPTestSuite testSuite;
|
||||
|
||||
@BeforeMethod
|
||||
public void setUp()
|
||||
throws Exception {
|
||||
|
||||
testSuite = new MPTestSuite();
|
||||
//testSuite.getTests().addFilters( "v3_type_maximum" );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMasterKey()
|
||||
throws Exception {
|
||||
|
||||
testSuite.forEach( "testMasterKey", testCase -> {
|
||||
char[] masterPassword = testCase.getMasterPassword().toCharArray();
|
||||
MPMasterKey masterKey = new MPMasterKey( testCase.getFullName(), masterPassword );
|
||||
|
||||
// Test key
|
||||
assertEquals(
|
||||
CodeUtils.encodeHex( masterKey.getKeyID( testCase.getAlgorithm() ) ),
|
||||
testCase.getKeyID(),
|
||||
"[testMasterKey] keyID mismatch for test case: " + testCase );
|
||||
|
||||
// Test invalidation
|
||||
masterKey.invalidate();
|
||||
try {
|
||||
masterKey.getKeyID( testCase.getAlgorithm() );
|
||||
fail( "[testMasterKey] invalidate ineffective for test case: " + testCase );
|
||||
}
|
||||
catch (final MPKeyUnavailableException ignored) {
|
||||
}
|
||||
assertNotEquals(
|
||||
masterPassword,
|
||||
testCase.getMasterPassword().toCharArray(),
|
||||
"[testMasterKey] masterPassword not wiped for test case: " + testCase );
|
||||
|
||||
return true;
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSiteResult()
|
||||
throws Exception {
|
||||
|
||||
testSuite.forEach( "testSiteResult", testCase -> {
|
||||
char[] masterPassword = testCase.getMasterPassword().toCharArray();
|
||||
MPMasterKey masterKey = new MPMasterKey( testCase.getFullName(), masterPassword );
|
||||
|
||||
// Test site result
|
||||
assertEquals(
|
||||
masterKey.siteResult( testCase.getSiteName(), testCase.getAlgorithm(), testCase.getSiteCounter(),
|
||||
testCase.getKeyPurpose(),
|
||||
testCase.getKeyContext(), testCase.getResultType(),
|
||||
null ),
|
||||
testCase.getResult(),
|
||||
"[testSiteResult] result mismatch for test case: " + testCase );
|
||||
|
||||
return true;
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSiteState()
|
||||
throws Exception {
|
||||
|
||||
MPTests.Case testCase = testSuite.getTests().getDefaultCase();
|
||||
char[] masterPassword = testCase.getMasterPassword().toCharArray();
|
||||
MPMasterKey masterKey = new MPMasterKey( testCase.getFullName(), masterPassword );
|
||||
|
||||
String password = randomString( 8 );
|
||||
MPResultType resultType = MPResultType.StoredPersonal;
|
||||
for (final MPAlgorithm.Version version : MPAlgorithm.Version.values()) {
|
||||
MPAlgorithm algorithm = version.getAlgorithm();
|
||||
|
||||
// Test site state
|
||||
String state = masterKey.siteState( testCase.getSiteName(), algorithm, testCase.getSiteCounter(), testCase.getKeyPurpose(),
|
||||
testCase.getKeyContext(), resultType, password );
|
||||
String result = masterKey.siteResult( testCase.getSiteName(), algorithm, testCase.getSiteCounter(), testCase.getKeyPurpose(),
|
||||
testCase.getKeyContext(), resultType, state );
|
||||
|
||||
assertEquals(
|
||||
result,
|
||||
password,
|
||||
"[testSiteState] state mismatch for test case: " + testCase );
|
||||
}
|
||||
}
|
||||
|
||||
private static String randomString(int length) {
|
||||
Random random = new Random();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
while (length > 0) {
|
||||
int codePoint = random.nextInt( Character.MAX_CODE_POINT - Character.MIN_CODE_POINT ) + Character.MIN_CODE_POINT;
|
||||
if (!Character.isDefined( codePoint ) || (Character.getType( codePoint ) == Character.PRIVATE_USE) || Character.isSurrogate(
|
||||
(char) codePoint ))
|
||||
continue;
|
||||
|
||||
builder.appendCodePoint( codePoint );
|
||||
length--;
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
<configuration scan="false">
|
||||
|
||||
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%-8relative %22c{0} [%-5level] %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="com.lyndir" level="${mp.log.level:-TRACE}" />
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="stdout" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
Reference in New Issue
Block a user