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>
|
36
platform-independent/java/gui/build.gradle
Normal file
36
platform-independent/java/gui/build.gradle
Normal file
@@ -0,0 +1,36 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'application'
|
||||
id 'com.github.johnrengelman.shadow' version '2.0.4'
|
||||
}
|
||||
|
||||
description = 'Master Password GUI'
|
||||
mainClassName = 'com.lyndir.masterpassword.gui.GUI'
|
||||
|
||||
dependencies {
|
||||
implementation group: 'com.lyndir.lhunath.opal', name: 'opal-system', version: '1.7-p1'
|
||||
implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.1.2'
|
||||
implementation group: 'com.yuvimasory', name: 'orange-extensions', version: '1.3.0'
|
||||
|
||||
compile project( ':masterpassword-model' )
|
||||
}
|
||||
|
||||
|
||||
// release with: STORE_PW=$(mpw masterpassword.keystore) KEY_PW_ANDROID=$(mpw masterpassword-android) gradle masterpassword-gui:shadowJar
|
||||
shadowJar.doLast {
|
||||
if (System.getenv( 'KEY_PW_DESKTOP' ) != null)
|
||||
ant.signjar(
|
||||
jar: archivePath,
|
||||
alias: 'masterpassword-desktop',
|
||||
keystore: 'masterpassword.keystore',
|
||||
storepass: System.getenv( 'STORE_PW' ),
|
||||
keypass: System.getenv( 'KEY_PW_DESKTOP' ),
|
||||
preservelastmodified: 'true',
|
||||
destdir: '.'
|
||||
)
|
||||
}
|
||||
|
||||
run {
|
||||
// I don't fully understand why this is necessary, but without it -Dmp.log.level is lost.
|
||||
systemProperties = System.properties
|
||||
}
|
1
platform-independent/java/gui/masterpassword.keystore
Symbolic link
1
platform-independent/java/gui/masterpassword.keystore
Symbolic link
@@ -0,0 +1 @@
|
||||
/Users/lhunath/annex/secret/masterpassword.keystore
|
@@ -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.gui;
|
||||
|
||||
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
|
||||
import com.lyndir.masterpassword.model.MPConstant;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-08-31
|
||||
*/
|
||||
@SuppressWarnings("CallToSystemGetenv")
|
||||
public class Config {
|
||||
|
||||
private static final Config instance = new Config();
|
||||
|
||||
public static Config get() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public boolean checkForUpdates() {
|
||||
return ConversionUtils.toBoolean( System.getenv( MPConstant.env_checkUpdates ) ).orElse( true );
|
||||
}
|
||||
}
|
@@ -0,0 +1,128 @@
|
||||
//==============================================================================
|
||||
// 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.gui;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.io.CharSource;
|
||||
import com.google.common.io.Resources;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.lhunath.opal.system.util.TypeUtils;
|
||||
import com.lyndir.masterpassword.gui.view.PasswordFrame;
|
||||
import com.lyndir.masterpassword.gui.view.UnlockFrame;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Optional;
|
||||
import java.util.jar.*;
|
||||
import javax.swing.*;
|
||||
|
||||
|
||||
/**
|
||||
* <p> <i>Jun 10, 2008</i> </p>
|
||||
*
|
||||
* @author mbillemo
|
||||
*/
|
||||
public class GUI implements UnlockFrame.SignInCallback {
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( GUI.class );
|
||||
|
||||
private final UnlockFrame unlockFrame = new UnlockFrame( this );
|
||||
private PasswordFrame<?, ?> passwordFrame;
|
||||
|
||||
public static void main(final String... args) {
|
||||
if (Config.get().checkForUpdates())
|
||||
checkUpdate();
|
||||
|
||||
// Try and set the system look & feel, if available.
|
||||
try {
|
||||
UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
|
||||
}
|
||||
catch (final UnsupportedLookAndFeelException | ClassNotFoundException | InstantiationException | IllegalAccessException ignored) {
|
||||
}
|
||||
|
||||
try {
|
||||
// AppleGUI adds support for macOS features.
|
||||
Optional<Class<GUI>> appleGUI = TypeUtils.loadClass( "com.lyndir.masterpassword.gui.platform.mac.AppleGUI" );
|
||||
if (appleGUI.isPresent())
|
||||
appleGUI.get().getConstructor().newInstance().open();
|
||||
|
||||
else // No special platform handling.
|
||||
new GUI().open();
|
||||
}
|
||||
catch (final IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) {
|
||||
throw logger.bug( e );
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkUpdate() {
|
||||
try {
|
||||
Enumeration<URL> manifestURLs = Thread.currentThread().getContextClassLoader().getResources( JarFile.MANIFEST_NAME );
|
||||
while (manifestURLs.hasMoreElements())
|
||||
try (InputStream manifestStream = manifestURLs.nextElement().openStream()) {
|
||||
Attributes attributes = new Manifest( manifestStream ).getMainAttributes();
|
||||
if (!GUI.class.getCanonicalName().equals( attributes.getValue( Attributes.Name.MAIN_CLASS ) ))
|
||||
continue;
|
||||
|
||||
String manifestRevision = attributes.getValue( Attributes.Name.IMPLEMENTATION_VERSION );
|
||||
String upstreamRevisionURL = "https://masterpassword.app/masterpassword-gui.jar.rev";
|
||||
CharSource upstream = Resources.asCharSource( URI.create( upstreamRevisionURL ).toURL(), Charsets.UTF_8 );
|
||||
String upstreamRevision = upstream.readFirstLine();
|
||||
if ((manifestRevision != null) && (upstreamRevision != null) && !manifestRevision.equalsIgnoreCase(
|
||||
upstreamRevision )) {
|
||||
logger.inf( "Local Revision: <%s>", manifestRevision );
|
||||
logger.inf( "Upstream Revision: <%s>", upstreamRevision );
|
||||
logger.wrn( "You are not running the current official version. Please update from:%n%s",
|
||||
"https://masterpassword.app/masterpassword-gui.jar" );
|
||||
JOptionPane.showMessageDialog( null,
|
||||
strf( "A new version of Master Password is available.%n "
|
||||
+ "Please download the latest version from %s",
|
||||
"https://masterpassword.app" ),
|
||||
"Update Available", JOptionPane.WARNING_MESSAGE );
|
||||
}
|
||||
}
|
||||
catch (final IOException e) {
|
||||
logger.wrn( e, "Couldn't check for version update." );
|
||||
}
|
||||
}
|
||||
catch (final IOException e) {
|
||||
logger.wrn( e, "Couldn't inspect JAR." );
|
||||
}
|
||||
}
|
||||
|
||||
protected void open() {
|
||||
SwingUtilities.invokeLater( () -> {
|
||||
if (passwordFrame == null)
|
||||
unlockFrame.setVisible( true );
|
||||
else
|
||||
passwordFrame.setVisible( true );
|
||||
} );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void signedIn(final PasswordFrame<?, ?> passwordFrame) {
|
||||
this.passwordFrame = passwordFrame;
|
||||
open();
|
||||
}
|
||||
}
|
@@ -0,0 +1,359 @@
|
||||
//==============================================================================
|
||||
// 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.gui;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.io.Resources;
|
||||
import com.google.common.util.concurrent.JdkFutureAdapters;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.masterpassword.MPIdenticon;
|
||||
import java.awt.*;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.image.ImageObserver;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.swing.*;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-06-11
|
||||
*/
|
||||
@SuppressWarnings({ "HardcodedFileSeparator", "MethodReturnAlwaysConstant", "SpellCheckingInspection" })
|
||||
public abstract class Res {
|
||||
|
||||
private static final int AVATAR_COUNT = 19;
|
||||
private static final Map<Window, ScheduledExecutorService> executorByWindow = new WeakHashMap<>();
|
||||
private static final Logger logger = Logger.get( Res.class );
|
||||
private static final Colors colors = new Colors();
|
||||
|
||||
public static Future<?> execute(final Window host, final Runnable job) {
|
||||
return schedule( host, job, 0, TimeUnit.MILLISECONDS );
|
||||
}
|
||||
|
||||
public static Future<?> schedule(final Window host, final Runnable job, final long delay, final TimeUnit timeUnit) {
|
||||
return getExecutor( host ).schedule( () -> {
|
||||
try {
|
||||
job.run();
|
||||
}
|
||||
catch (final Throwable t) {
|
||||
logger.err( t, "Unexpected: %s", t.getLocalizedMessage() );
|
||||
}
|
||||
}, delay, timeUnit );
|
||||
}
|
||||
|
||||
public static <V> ListenableFuture<V> execute(final Window host, final Callable<V> job) {
|
||||
return schedule( host, job, 0, TimeUnit.MILLISECONDS );
|
||||
}
|
||||
|
||||
public static <V> ListenableFuture<V> schedule(final Window host, final Callable<V> job, final long delay, final TimeUnit timeUnit) {
|
||||
ScheduledExecutorService executor = getExecutor( host );
|
||||
return JdkFutureAdapters.listenInPoolThread( executor.schedule( () -> {
|
||||
try {
|
||||
return job.call();
|
||||
}
|
||||
catch (final Throwable t) {
|
||||
logger.err( t, "Unexpected: %s", t.getLocalizedMessage() );
|
||||
throw Throwables.propagate( t );
|
||||
}
|
||||
}, delay, timeUnit ), executor );
|
||||
}
|
||||
|
||||
private static ScheduledExecutorService getExecutor(final Window host) {
|
||||
ScheduledExecutorService executor = executorByWindow.get( host );
|
||||
|
||||
if (executor == null) {
|
||||
executorByWindow.put( host, executor = Executors.newSingleThreadScheduledExecutor() );
|
||||
|
||||
host.addWindowListener( new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosed(final WindowEvent e) {
|
||||
ExecutorService executor = executorByWindow.remove( host );
|
||||
if (executor != null)
|
||||
executor.shutdownNow();
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
return executor;
|
||||
}
|
||||
|
||||
public static Icon iconAdd() {
|
||||
return new RetinaIcon( Resources.getResource( "media/icon_add@2x.png" ) );
|
||||
}
|
||||
|
||||
public static Icon iconDelete() {
|
||||
return new RetinaIcon( Resources.getResource( "media/icon_delete@2x.png" ) );
|
||||
}
|
||||
|
||||
public static Icon iconQuestion() {
|
||||
return new RetinaIcon( Resources.getResource( "media/icon_question@2x.png" ) );
|
||||
}
|
||||
|
||||
public static Icon avatar(final int index) {
|
||||
return new RetinaIcon( Resources.getResource( strf( "media/avatar-%d@2x.png", index % avatars() ) ) );
|
||||
}
|
||||
|
||||
public static int avatars() {
|
||||
return AVATAR_COUNT;
|
||||
}
|
||||
|
||||
public static Font emoticonsFont() {
|
||||
return emoticonsRegular();
|
||||
}
|
||||
|
||||
public static Font controlFont() {
|
||||
return arimoRegular();
|
||||
}
|
||||
|
||||
public static Font valueFont() {
|
||||
return sourceSansProRegular();
|
||||
}
|
||||
|
||||
public static Font bigValueFont() {
|
||||
return sourceSansProBlack();
|
||||
}
|
||||
|
||||
public static Font emoticonsRegular() {
|
||||
return font( "fonts/Emoticons-Regular.otf" );
|
||||
}
|
||||
|
||||
public static Font sourceCodeProRegular() {
|
||||
return font( "fonts/SourceCodePro-Regular.otf" );
|
||||
}
|
||||
|
||||
public static Font sourceCodeProBlack() {
|
||||
return font( "fonts/SourceCodePro-Bold.otf" );
|
||||
}
|
||||
|
||||
public static Font sourceSansProRegular() {
|
||||
return font( "fonts/SourceSansPro-Regular.otf" );
|
||||
}
|
||||
|
||||
public static Font sourceSansProBlack() {
|
||||
return font( "fonts/SourceSansPro-Bold.otf" );
|
||||
}
|
||||
|
||||
public static Font exoBold() {
|
||||
return font( "fonts/Exo2.0-Bold.otf" );
|
||||
}
|
||||
|
||||
public static Font exoExtraBold() {
|
||||
return font( "fonts/Exo2.0-ExtraBold.otf" );
|
||||
}
|
||||
|
||||
public static Font exoRegular() {
|
||||
return font( "fonts/Exo2.0-Regular.otf" );
|
||||
}
|
||||
|
||||
public static Font exoThin() {
|
||||
return font( "fonts/Exo2.0-Thin.otf" );
|
||||
}
|
||||
|
||||
public static Font arimoBold() {
|
||||
return font( "fonts/Arimo-Bold.ttf" );
|
||||
}
|
||||
|
||||
public static Font arimoBoldItalic() {
|
||||
return font( "fonts/Arimo-BoldItalic.ttf" );
|
||||
}
|
||||
|
||||
public static Font arimoItalic() {
|
||||
return font( "fonts/Arimo-Italic.ttf" );
|
||||
}
|
||||
|
||||
public static Font arimoRegular() {
|
||||
return font( "fonts/Arimo-Regular.ttf" );
|
||||
}
|
||||
|
||||
private static Font font(@NonNls final String fontResourceName) {
|
||||
Map<String, SoftReference<Font>> fontsByResourceName = Maps.newHashMap();
|
||||
SoftReference<Font> fontRef = fontsByResourceName.get( fontResourceName );
|
||||
Font font = (fontRef == null)? null: fontRef.get();
|
||||
if (font == null)
|
||||
try {
|
||||
fontsByResourceName.put( fontResourceName, new SoftReference<>(
|
||||
font = Font.createFont( Font.TRUETYPE_FONT, Resources.getResource( fontResourceName ).openStream() ) ) );
|
||||
}
|
||||
catch (final FontFormatException | IOException e) {
|
||||
throw Throwables.propagate( e );
|
||||
}
|
||||
|
||||
return font;
|
||||
}
|
||||
|
||||
public static Colors colors() {
|
||||
return colors;
|
||||
}
|
||||
|
||||
private static final class RetinaIcon extends ImageIcon {
|
||||
|
||||
private static final Pattern scalePattern = Pattern.compile( ".*@(\\d+)x.[^.]+$" );
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final float scale;
|
||||
|
||||
private RetinaIcon(final URL url) {
|
||||
super( url );
|
||||
|
||||
Matcher scaleMatcher = scalePattern.matcher( url.getPath() );
|
||||
scale = scaleMatcher.matches()? Float.parseFloat( scaleMatcher.group( 1 ) ): 1;
|
||||
}
|
||||
|
||||
//private static URL retinaURL(final URL url) {
|
||||
// try {
|
||||
// final boolean[] isRetina = new boolean[1];
|
||||
// new apple.awt.CImage.HiDPIScaledImage(1,1, BufferedImage.TYPE_INT_ARGB) {
|
||||
// @Override
|
||||
// public void drawIntoImage(BufferedImage image, float v) {
|
||||
// isRetina[0] = v > 1;
|
||||
// }
|
||||
// };
|
||||
// return isRetina[0];
|
||||
// } catch (Throwable e) {
|
||||
// e.printStackTrace();
|
||||
// return url;
|
||||
// }
|
||||
//}
|
||||
|
||||
@Override
|
||||
public int getIconWidth() {
|
||||
return (int) (super.getIconWidth() / scale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIconHeight() {
|
||||
return (int) (super.getIconHeight() / scale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void paintIcon(final Component c, final Graphics g, final int x, final int y) {
|
||||
ImageObserver observer = ifNotNullElse( getImageObserver(), c );
|
||||
|
||||
Image image = getImage();
|
||||
int width = image.getWidth( observer );
|
||||
int height = image.getHeight( observer );
|
||||
Graphics2D g2d = (Graphics2D) g.create( x, y, width, height );
|
||||
|
||||
g2d.scale( 1 / scale, 1 / scale );
|
||||
g2d.drawImage( image, 0, 0, observer );
|
||||
g2d.scale( 1, 1 );
|
||||
g2d.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class Colors {
|
||||
|
||||
private final Color frameBg = Color.decode( "#5A5D6B" );
|
||||
private final Color controlBg = Color.decode( "#ECECEC" );
|
||||
private final Color controlBorder = Color.decode( "#BFBFBF" );
|
||||
|
||||
public Color frameBg() {
|
||||
return frameBg;
|
||||
}
|
||||
|
||||
public Color controlBg() {
|
||||
return controlBg;
|
||||
}
|
||||
|
||||
public Color controlBorder() {
|
||||
return controlBorder;
|
||||
}
|
||||
|
||||
public Color fromIdenticonColor(final MPIdenticon.Color identiconColor, final BackgroundMode backgroundMode) {
|
||||
switch (identiconColor) {
|
||||
case RED:
|
||||
switch (backgroundMode) {
|
||||
case DARK:
|
||||
return Color.decode( "#dc322f" );
|
||||
case LIGHT:
|
||||
return Color.decode( "#dc322f" );
|
||||
}
|
||||
break;
|
||||
case GREEN:
|
||||
switch (backgroundMode) {
|
||||
case DARK:
|
||||
return Color.decode( "#859900" );
|
||||
case LIGHT:
|
||||
return Color.decode( "#859900" );
|
||||
}
|
||||
break;
|
||||
case YELLOW:
|
||||
switch (backgroundMode) {
|
||||
case DARK:
|
||||
return Color.decode( "#b58900" );
|
||||
case LIGHT:
|
||||
return Color.decode( "#b58900" );
|
||||
}
|
||||
break;
|
||||
case BLUE:
|
||||
switch (backgroundMode) {
|
||||
case DARK:
|
||||
return Color.decode( "#268bd2" );
|
||||
case LIGHT:
|
||||
return Color.decode( "#268bd2" );
|
||||
}
|
||||
break;
|
||||
case MAGENTA:
|
||||
switch (backgroundMode) {
|
||||
case DARK:
|
||||
return Color.decode( "#d33682" );
|
||||
case LIGHT:
|
||||
return Color.decode( "#d33682" );
|
||||
}
|
||||
break;
|
||||
case CYAN:
|
||||
switch (backgroundMode) {
|
||||
case DARK:
|
||||
return Color.decode( "#2aa198" );
|
||||
case LIGHT:
|
||||
return Color.decode( "#2aa198" );
|
||||
}
|
||||
break;
|
||||
case MONO:
|
||||
switch (backgroundMode) {
|
||||
case DARK:
|
||||
return Color.decode( "#93a1a1" );
|
||||
case LIGHT:
|
||||
return Color.decode( "#586e75" );
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException( strf( "Color: %s or mode: %s not supported: ", identiconColor, backgroundMode ) );
|
||||
}
|
||||
|
||||
public enum BackgroundMode {
|
||||
DARK, LIGHT
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
//==============================================================================
|
||||
// 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.gui.model;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||
|
||||
import com.lyndir.masterpassword.MPResultType;
|
||||
import com.lyndir.masterpassword.model.impl.MPBasicQuestion;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2018-05-16
|
||||
*/
|
||||
public class MPIncognitoQuestion extends MPBasicQuestion {
|
||||
|
||||
private final MPIncognitoSite site;
|
||||
|
||||
public MPIncognitoQuestion(final MPIncognitoSite site, final String keyword, @Nullable final MPResultType type) {
|
||||
super( keyword, ifNotNullElse( type, site.getAlgorithm().mpw_default_answer_type() ) );
|
||||
|
||||
this.site = site;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPIncognitoSite getSite() {
|
||||
return site;
|
||||
}
|
||||
}
|
@@ -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.gui.model;
|
||||
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.masterpassword.MPAlgorithm;
|
||||
import com.lyndir.masterpassword.MPResultType;
|
||||
import com.lyndir.masterpassword.model.impl.MPBasicSite;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 14-12-16
|
||||
*/
|
||||
public class MPIncognitoSite extends MPBasicSite<MPIncognitoQuestion> {
|
||||
|
||||
private final MPIncognitoUser user;
|
||||
|
||||
public MPIncognitoSite(final MPIncognitoUser user, final String name) {
|
||||
this( user, name, null, null, null, null );
|
||||
}
|
||||
|
||||
public MPIncognitoSite(final MPIncognitoUser user, final String name,
|
||||
@Nullable final MPAlgorithm algorithm, @Nullable final UnsignedInteger counter,
|
||||
@Nullable final MPResultType resultType, @Nullable final MPResultType loginType) {
|
||||
super( name, (algorithm == null)? user.getAlgorithm(): algorithm, counter, resultType, loginType );
|
||||
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MPIncognitoUser getUser() {
|
||||
return user;
|
||||
}
|
||||
}
|
@@ -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.gui.model;
|
||||
|
||||
import com.lyndir.masterpassword.MPAlgorithm;
|
||||
import com.lyndir.masterpassword.model.impl.MPBasicUser;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-06-08
|
||||
*/
|
||||
public class MPIncognitoUser extends MPBasicUser<MPIncognitoSite> {
|
||||
|
||||
public MPIncognitoUser(final String fullName) {
|
||||
super( fullName, MPAlgorithm.Version.CURRENT.getAlgorithm() );
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public byte[] getKeyID() {
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -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.gui.model;
|
||||
|
||||
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.gui;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
@@ -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.gui.platform.mac;
|
||||
|
||||
import com.apple.eawt.*;
|
||||
import com.lyndir.masterpassword.gui.GUI;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-06-10
|
||||
*/
|
||||
public class AppleGUI extends GUI {
|
||||
|
||||
public AppleGUI() {
|
||||
|
||||
Application application = Application.getApplication();
|
||||
application.addAppEventListener( new AppForegroundListener() {
|
||||
|
||||
@Override
|
||||
public void appMovedToBackground(final AppEvent.AppForegroundEvent arg0) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appRaisedToForeground(final AppEvent.AppForegroundEvent arg0) {
|
||||
open();
|
||||
}
|
||||
} );
|
||||
application.addAppEventListener( new AppReOpenedListener() {
|
||||
@Override
|
||||
public void appReOpened(final AppEvent.AppReOpenedEvent arg0) {
|
||||
open();
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
@@ -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-04-26
|
||||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
package com.lyndir.masterpassword.gui.platform.mac;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
@@ -0,0 +1,257 @@
|
||||
//==============================================================================
|
||||
// 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.gui.util;
|
||||
|
||||
import com.lyndir.masterpassword.gui.Res;
|
||||
import java.awt.*;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.Border;
|
||||
import javax.swing.border.CompoundBorder;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-06-08
|
||||
*/
|
||||
public abstract class Components {
|
||||
|
||||
private static final float CONTROL_TEXT_SIZE = 12f;
|
||||
|
||||
public static GradientPanel boxLayout(final int axis, final Component... components) {
|
||||
GradientPanel container = gradientPanel( null, null );
|
||||
// container.setBackground( Color.red );
|
||||
container.setLayout( new BoxLayout( container, axis ) );
|
||||
for (final Component component : components)
|
||||
container.add( component );
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
public static GradientPanel borderPanel(final JComponent component, @Nullable final Border border) {
|
||||
return borderPanel( component, border, null );
|
||||
}
|
||||
|
||||
public static GradientPanel borderPanel(final JComponent component, @Nullable final Border border, @Nullable final Color background) {
|
||||
GradientPanel box = boxLayout( BoxLayout.LINE_AXIS, component );
|
||||
|
||||
if (border != null)
|
||||
box.setBorder( border );
|
||||
|
||||
if (background != null)
|
||||
box.setBackground( background );
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
public static GradientPanel gradientPanel(@Nullable final LayoutManager layout, @Nullable final Color color) {
|
||||
return new GradientPanel( layout, color ) {
|
||||
{
|
||||
setOpaque( color != null );
|
||||
setBackground( null );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
setAlignmentY( BOTTOM_ALIGNMENT );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static JTextField textField() {
|
||||
return new JTextField() {
|
||||
{
|
||||
setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
|
||||
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) );
|
||||
setFont( Res.valueFont().deriveFont( CONTROL_TEXT_SIZE ) );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
setAlignmentY( BOTTOM_ALIGNMENT );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getMaximumSize() {
|
||||
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static JPasswordField passwordField() {
|
||||
return new JPasswordField() {
|
||||
{
|
||||
setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
|
||||
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
setAlignmentY( BOTTOM_ALIGNMENT );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getMaximumSize() {
|
||||
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static JButton button(final String label) {
|
||||
return new JButton( label ) {
|
||||
{
|
||||
setFont( Res.controlFont().deriveFont( CONTROL_TEXT_SIZE ) );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
setAlignmentY( BOTTOM_ALIGNMENT );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getMaximumSize() {
|
||||
return new Dimension( 20, getPreferredSize().height );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Component stud() {
|
||||
Dimension studDimension = new Dimension( 8, 8 );
|
||||
Box.Filler rigidArea = new Box.Filler( studDimension, studDimension, studDimension );
|
||||
rigidArea.setAlignmentX( Component.LEFT_ALIGNMENT );
|
||||
rigidArea.setAlignmentY( Component.BOTTOM_ALIGNMENT );
|
||||
rigidArea.setBackground( Color.red );
|
||||
return rigidArea;
|
||||
}
|
||||
|
||||
public static JSpinner spinner(final SpinnerModel model) {
|
||||
return new JSpinner( model ) {
|
||||
{
|
||||
CompoundBorder editorBorder = BorderFactory.createCompoundBorder(
|
||||
BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
|
||||
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) );
|
||||
((DefaultEditor) getEditor()).getTextField().setBorder( editorBorder );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
setAlignmentY( BOTTOM_ALIGNMENT );
|
||||
setBorder( null );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getMaximumSize() {
|
||||
return new Dimension( 20, getPreferredSize().height );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static JLabel label(@Nullable final String label) {
|
||||
return label( label, SwingConstants.LEADING );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param horizontalAlignment One of the following constants
|
||||
* defined in {@code SwingConstants}:
|
||||
* {@code LEFT},
|
||||
* {@code CENTER},
|
||||
* {@code RIGHT},
|
||||
* {@code LEADING} or
|
||||
* {@code TRAILING}.
|
||||
*/
|
||||
public static JLabel label(@Nullable final String label, final int horizontalAlignment) {
|
||||
return new JLabel( label, horizontalAlignment ) {
|
||||
{
|
||||
setFont( Res.controlFont().deriveFont( CONTROL_TEXT_SIZE ) );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
setAlignmentY( BOTTOM_ALIGNMENT );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getMaximumSize() {
|
||||
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static JCheckBox checkBox(final String label) {
|
||||
return new JCheckBox( label ) {
|
||||
{
|
||||
setFont( Res.controlFont().deriveFont( CONTROL_TEXT_SIZE ) );
|
||||
setBackground( null );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
setAlignmentY( BOTTOM_ALIGNMENT );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <V> JComboBox<V> comboBox(final V... values) {
|
||||
return comboBox( new DefaultComboBoxModel<>( values ) );
|
||||
}
|
||||
|
||||
public static <M> JComboBox<M> comboBox(final ComboBoxModel<M> model) {
|
||||
return new JComboBox<M>( model ) {
|
||||
{
|
||||
// CompoundBorder editorBorder = BorderFactory.createCompoundBorder(
|
||||
// BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
|
||||
// BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) );
|
||||
// ((JComponent) ((BasicComboBoxEditor) getEditor()).getEditorComponent()).setBorder(editorBorder);
|
||||
setFont( Res.controlFont().deriveFont( CONTROL_TEXT_SIZE ) );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
setAlignmentY( BOTTOM_ALIGNMENT );
|
||||
// setBorder(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getMaximumSize() {
|
||||
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static class GradientPanel extends JPanel {
|
||||
|
||||
@Nullable
|
||||
private Color gradientColor;
|
||||
|
||||
@Nullable
|
||||
private GradientPaint paint;
|
||||
|
||||
protected GradientPanel(@Nullable final LayoutManager layout, @Nullable final Color gradientColor) {
|
||||
super( layout );
|
||||
this.gradientColor = gradientColor;
|
||||
setBackground( null );
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Color getGradientColor() {
|
||||
return gradientColor;
|
||||
}
|
||||
|
||||
public void setGradientColor(@Nullable final Color gradientColor) {
|
||||
this.gradientColor = gradientColor;
|
||||
revalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doLayout() {
|
||||
super.doLayout();
|
||||
|
||||
if (gradientColor != null) {
|
||||
paint = new GradientPaint( new Point( 0, 0 ), gradientColor, new Point( getWidth(), getHeight() ), gradientColor.darker() );
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintComponent(final Graphics g) {
|
||||
super.paintComponent( g );
|
||||
|
||||
if (paint != null) {
|
||||
((Graphics2D) g).setPaint( paint );
|
||||
g.fillRect( 0, 0, getWidth(), getHeight() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,58 @@
|
||||
//==============================================================================
|
||||
// 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.gui.util;
|
||||
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import javax.swing.*;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2016-10-29
|
||||
*/
|
||||
public class UnsignedIntegerModel extends SpinnerNumberModel {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public UnsignedIntegerModel() {
|
||||
this( UnsignedInteger.ZERO, UnsignedInteger.ZERO, UnsignedInteger.MAX_VALUE, UnsignedInteger.ONE );
|
||||
}
|
||||
|
||||
public UnsignedIntegerModel(final UnsignedInteger value) {
|
||||
this( value, UnsignedInteger.ZERO, UnsignedInteger.MAX_VALUE, UnsignedInteger.ONE );
|
||||
}
|
||||
|
||||
public UnsignedIntegerModel(final UnsignedInteger value, final UnsignedInteger minimum) {
|
||||
this( value, minimum, UnsignedInteger.MAX_VALUE, UnsignedInteger.ONE );
|
||||
}
|
||||
|
||||
public UnsignedIntegerModel(final UnsignedInteger value, final UnsignedInteger minimum, final UnsignedInteger maximum) {
|
||||
this( value, minimum, maximum, UnsignedInteger.ONE );
|
||||
}
|
||||
|
||||
@SuppressWarnings("TypeMayBeWeakened")
|
||||
public UnsignedIntegerModel(final UnsignedInteger value, final UnsignedInteger minimum, final UnsignedInteger maximum,
|
||||
final UnsignedInteger stepSize) {
|
||||
super( value, minimum, maximum, stepSize );
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnsignedInteger getNumber() {
|
||||
return (UnsignedInteger) super.getNumber();
|
||||
}
|
||||
}
|
@@ -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.gui.util;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
@@ -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.gui.view;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.lyndir.masterpassword.gui.Res;
|
||||
import com.lyndir.masterpassword.gui.util.Components;
|
||||
import com.lyndir.masterpassword.model.MPUser;
|
||||
import java.awt.*;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.*;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-06-11
|
||||
*/
|
||||
public abstract class AuthenticationPanel<U extends MPUser<?>> extends Components.GradientPanel {
|
||||
|
||||
protected final UnlockFrame unlockFrame;
|
||||
protected final JLabel avatarLabel;
|
||||
|
||||
protected AuthenticationPanel(final UnlockFrame unlockFrame) {
|
||||
super( null, null );
|
||||
this.unlockFrame = unlockFrame;
|
||||
|
||||
setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) );
|
||||
|
||||
// Avatar
|
||||
add( Box.createVerticalGlue() );
|
||||
add( avatarLabel = new JLabel( Res.avatar( 0 ) ) {
|
||||
@Override
|
||||
public Dimension getMaximumSize() {
|
||||
return new Dimension( Integer.MAX_VALUE, Integer.MAX_VALUE );
|
||||
}
|
||||
} );
|
||||
add( Box.createVerticalGlue() );
|
||||
|
||||
avatarLabel.setToolTipText( "The avatar for your user. Click to change it." );
|
||||
}
|
||||
|
||||
protected void updateUser(final boolean repack) {
|
||||
unlockFrame.updateUser( getSelectedUser() );
|
||||
validate();
|
||||
|
||||
if (repack)
|
||||
unlockFrame.repack();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected abstract U getSelectedUser();
|
||||
|
||||
@Nonnull
|
||||
public abstract char[] getMasterPassword();
|
||||
|
||||
@Nullable
|
||||
public Component getFocusComponent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Iterable<? extends JButton> getButtons() {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
public abstract void reset();
|
||||
|
||||
public abstract PasswordFrame<?, ?> newPasswordFrame();
|
||||
}
|
@@ -0,0 +1,127 @@
|
||||
//==============================================================================
|
||||
// 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.gui.view;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.masterpassword.MPAlgorithm;
|
||||
import com.lyndir.masterpassword.MPResultType;
|
||||
import com.lyndir.masterpassword.gui.Res;
|
||||
import com.lyndir.masterpassword.gui.model.MPIncognitoSite;
|
||||
import com.lyndir.masterpassword.gui.model.MPIncognitoUser;
|
||||
import com.lyndir.masterpassword.gui.util.Components;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-06-11
|
||||
*/
|
||||
@SuppressWarnings({ "serial", "MagicNumber" })
|
||||
public class IncognitoAuthenticationPanel extends AuthenticationPanel<MPIncognitoUser> implements DocumentListener, ActionListener {
|
||||
|
||||
private final JTextField fullNameField;
|
||||
private final JPasswordField masterPasswordField;
|
||||
|
||||
public IncognitoAuthenticationPanel(final UnlockFrame unlockFrame) {
|
||||
|
||||
// Full Name
|
||||
super( unlockFrame );
|
||||
add( Components.stud() );
|
||||
|
||||
JLabel fullNameLabel = Components.label( "Full Name:" );
|
||||
add( fullNameLabel );
|
||||
|
||||
fullNameField = Components.textField();
|
||||
fullNameField.setFont( Res.valueFont().deriveFont( 12f ) );
|
||||
fullNameField.getDocument().addDocumentListener( this );
|
||||
fullNameField.addActionListener( this );
|
||||
add( fullNameField );
|
||||
add( Components.stud() );
|
||||
|
||||
// Master Password
|
||||
JLabel masterPasswordLabel = Components.label( "Master Password:" );
|
||||
add( masterPasswordLabel );
|
||||
|
||||
masterPasswordField = Components.passwordField();
|
||||
masterPasswordField.addActionListener( this );
|
||||
masterPasswordField.getDocument().addDocumentListener( this );
|
||||
add( masterPasswordField );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getFocusComponent() {
|
||||
return fullNameField;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
masterPasswordField.setText( "" );
|
||||
}
|
||||
|
||||
@Override
|
||||
public PasswordFrame<MPIncognitoUser, ?> newPasswordFrame() {
|
||||
return new PasswordFrame<MPIncognitoUser, MPIncognitoSite>( Preconditions.checkNotNull( getSelectedUser() ) ) {
|
||||
@Override
|
||||
protected MPIncognitoSite createSite(final MPIncognitoUser user, final String siteName, final UnsignedInteger siteCounter,
|
||||
final MPResultType resultType, final MPAlgorithm algorithm) {
|
||||
return new MPIncognitoSite( user, siteName, algorithm, siteCounter, resultType, null );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected MPIncognitoUser getSelectedUser() {
|
||||
return new MPIncognitoUser( fullNameField.getText() );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public char[] getMasterPassword() {
|
||||
return masterPasswordField.getPassword();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertUpdate(final DocumentEvent e) {
|
||||
updateUser( false );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(final DocumentEvent e) {
|
||||
updateUser( false );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(final DocumentEvent e) {
|
||||
updateUser( false );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
updateUser( false );
|
||||
unlockFrame.trySignIn( fullNameField, masterPasswordField );
|
||||
}
|
||||
}
|
@@ -0,0 +1,248 @@
|
||||
//==============================================================================
|
||||
// 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.gui.view;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.masterpassword.MPAlgorithm;
|
||||
import com.lyndir.masterpassword.MPResultType;
|
||||
import com.lyndir.masterpassword.gui.Res;
|
||||
import com.lyndir.masterpassword.gui.util.Components;
|
||||
import com.lyndir.masterpassword.model.MPUser;
|
||||
import com.lyndir.masterpassword.model.impl.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import javax.swing.plaf.metal.MetalComboBoxEditor;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-06-11
|
||||
*/
|
||||
public class ModelAuthenticationPanel extends AuthenticationPanel<MPFileUser> implements ItemListener, ActionListener, DocumentListener {
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( ModelAuthenticationPanel.class );
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final JComboBox<MPFileUser> userField;
|
||||
private final JLabel masterPasswordLabel;
|
||||
private final JPasswordField masterPasswordField;
|
||||
|
||||
public ModelAuthenticationPanel(final UnlockFrame unlockFrame) {
|
||||
super( unlockFrame );
|
||||
add( Components.stud() );
|
||||
|
||||
// Avatar
|
||||
avatarLabel.addMouseListener( new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(final MouseEvent e) {
|
||||
MPFileUser selectedUser = getSelectedUser();
|
||||
if (selectedUser != null) {
|
||||
selectedUser.setAvatar( selectedUser.getAvatar() + 1 );
|
||||
updateUser( false );
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
// User
|
||||
JLabel userLabel = Components.label( "User:" );
|
||||
add( userLabel );
|
||||
|
||||
userField = Components.comboBox( readConfigUsers() );
|
||||
userField.setFont( Res.valueFont().deriveFont( userField.getFont().getSize2D() ) );
|
||||
userField.addItemListener( this );
|
||||
userField.addActionListener( this );
|
||||
userField.setRenderer( new DefaultListCellRenderer() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Component getListCellRendererComponent(final JList<?> list, final Object value, final int index,
|
||||
final boolean isSelected, final boolean cellHasFocus) {
|
||||
String userValue = ((MPUser<MPFileSite>) value).getFullName();
|
||||
return super.getListCellRendererComponent( list, userValue, index, isSelected, cellHasFocus );
|
||||
}
|
||||
} );
|
||||
userField.setEditor( new MetalComboBoxEditor() {
|
||||
@Override
|
||||
protected JTextField createEditorComponent() {
|
||||
JTextField editorComponents = Components.textField();
|
||||
editorComponents.setForeground( Color.red );
|
||||
return editorComponents;
|
||||
}
|
||||
} );
|
||||
|
||||
add( userField );
|
||||
add( Components.stud() );
|
||||
|
||||
// Master Password
|
||||
masterPasswordLabel = Components.label( "Master Password:" );
|
||||
add( masterPasswordLabel );
|
||||
|
||||
masterPasswordField = Components.passwordField();
|
||||
masterPasswordField.addActionListener( this );
|
||||
masterPasswordField.getDocument().addDocumentListener( this );
|
||||
add( masterPasswordField );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getFocusComponent() {
|
||||
return masterPasswordField.isVisible()? masterPasswordField: null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateUser(boolean repack) {
|
||||
MPFileUser selectedUser = getSelectedUser();
|
||||
if (selectedUser != null) {
|
||||
avatarLabel.setIcon( Res.avatar( selectedUser.getAvatar() ) );
|
||||
boolean showPasswordField = !selectedUser.isMasterKeyAvailable(); // TODO: is this the same as keySaved()?
|
||||
if (masterPasswordField.isVisible() != showPasswordField) {
|
||||
masterPasswordLabel.setVisible( showPasswordField );
|
||||
masterPasswordField.setVisible( showPasswordField );
|
||||
repack = true;
|
||||
}
|
||||
}
|
||||
|
||||
super.updateUser( repack );
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected MPFileUser getSelectedUser() {
|
||||
int selectedIndex = userField.getSelectedIndex();
|
||||
if (selectedIndex < 0)
|
||||
return null;
|
||||
|
||||
return userField.getModel().getElementAt( selectedIndex );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public char[] getMasterPassword() {
|
||||
return masterPasswordField.getPassword();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<? extends JButton> getButtons() {
|
||||
return ImmutableList.of( new JButton( Res.iconAdd() ) {
|
||||
{
|
||||
addActionListener( new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
String fullName = JOptionPane.showInputDialog( ModelAuthenticationPanel.this, //
|
||||
"Enter your full name, ensuring it is correctly spelled and capitalized:",
|
||||
"New User", JOptionPane.QUESTION_MESSAGE );
|
||||
MPFileUserManager.get().addUser( new MPFileUser( fullName ) );
|
||||
userField.setModel( new DefaultComboBoxModel<>( readConfigUsers() ) );
|
||||
updateUser( true );
|
||||
}
|
||||
} );
|
||||
setToolTipText( "Add a new user to the list." );
|
||||
}
|
||||
}, new JButton( Res.iconDelete() ) {
|
||||
{
|
||||
addActionListener( new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
MPFileUser deleteUser = getSelectedUser();
|
||||
if (deleteUser == null)
|
||||
return;
|
||||
|
||||
if (JOptionPane.showConfirmDialog( ModelAuthenticationPanel.this, //
|
||||
strf( "Are you sure you want to delete the user and sites remembered for:%n%s.",
|
||||
deleteUser.getFullName() ), //
|
||||
"Delete User", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE )
|
||||
== JOptionPane.CANCEL_OPTION)
|
||||
return;
|
||||
|
||||
MPFileUserManager.get().deleteUser( deleteUser );
|
||||
userField.setModel( new DefaultComboBoxModel<>( readConfigUsers() ) );
|
||||
updateUser( true );
|
||||
}
|
||||
} );
|
||||
setToolTipText( "Delete the selected user." );
|
||||
}
|
||||
}, new JButton( Res.iconQuestion() ) {
|
||||
{
|
||||
addActionListener( e -> JOptionPane.showMessageDialog(
|
||||
ModelAuthenticationPanel.this, //
|
||||
strf( "Reads users and sites from the directory at:%n%s",
|
||||
MPFileUserManager.get().getPath().getAbsolutePath() ), //
|
||||
"Help", JOptionPane.INFORMATION_MESSAGE ) );
|
||||
setToolTipText( "More information." );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
masterPasswordField.setText( "" );
|
||||
}
|
||||
|
||||
@Override
|
||||
public PasswordFrame<MPFileUser, MPFileSite> newPasswordFrame() {
|
||||
return new PasswordFrame<MPFileUser, MPFileSite>( Preconditions.checkNotNull( getSelectedUser() ) ) {
|
||||
@Override
|
||||
protected MPFileSite createSite(final MPFileUser user, final String siteName, final UnsignedInteger siteCounter,
|
||||
final MPResultType resultType,
|
||||
final MPAlgorithm algorithm) {
|
||||
return new MPFileSite( user, siteName, algorithm, siteCounter, resultType );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static MPFileUser[] readConfigUsers() {
|
||||
return MPFileUserManager.get().getUsers().toArray( new MPFileUser[0] );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void itemStateChanged(final ItemEvent e) {
|
||||
updateUser( false );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
updateUser( false );
|
||||
unlockFrame.trySignIn( userField );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertUpdate(final DocumentEvent e) {
|
||||
updateUser( false );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(final DocumentEvent e) {
|
||||
updateUser( false );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(final DocumentEvent e) {
|
||||
updateUser( false );
|
||||
}
|
||||
}
|
@@ -0,0 +1,281 @@
|
||||
//==============================================================================
|
||||
// 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.gui.view;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.google.common.util.concurrent.*;
|
||||
import com.lyndir.masterpassword.*;
|
||||
import com.lyndir.masterpassword.gui.Res;
|
||||
import com.lyndir.masterpassword.gui.util.Components;
|
||||
import com.lyndir.masterpassword.gui.util.UnsignedIntegerModel;
|
||||
import com.lyndir.masterpassword.model.MPSite;
|
||||
import com.lyndir.masterpassword.model.MPUser;
|
||||
import com.lyndir.masterpassword.model.impl.MPFileSite;
|
||||
import com.lyndir.masterpassword.model.impl.MPFileUser;
|
||||
import java.awt.*;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
import java.awt.datatransfer.Transferable;
|
||||
import java.awt.event.*;
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-06-08
|
||||
*/
|
||||
public abstract class PasswordFrame<U extends MPUser<S>, S extends MPSite<?>> extends JFrame implements DocumentListener {
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private final Components.GradientPanel root;
|
||||
private final JTextField siteNameField;
|
||||
private final JButton siteActionButton;
|
||||
private final JComboBox<MPAlgorithm.Version> siteVersionField;
|
||||
private final JSpinner siteCounterField;
|
||||
private final UnsignedIntegerModel siteCounterModel;
|
||||
private final JComboBox<MPResultType> resultTypeField;
|
||||
private final JPasswordField passwordField;
|
||||
private final JLabel tipLabel;
|
||||
private final JCheckBox maskPasswordField;
|
||||
private final char passwordEchoChar;
|
||||
private final Font passwordEchoFont;
|
||||
private final U user;
|
||||
|
||||
@Nullable
|
||||
private S currentSite;
|
||||
private boolean updatingUI;
|
||||
|
||||
@SuppressWarnings("MagicNumber")
|
||||
protected PasswordFrame(final U user) {
|
||||
super( "Master Password" );
|
||||
this.user = user;
|
||||
|
||||
setDefaultCloseOperation( DISPOSE_ON_CLOSE );
|
||||
setContentPane( root = Components.gradientPanel( new FlowLayout(), Res.colors().frameBg() ) );
|
||||
root.setLayout( new BoxLayout( root, BoxLayout.PAGE_AXIS ) );
|
||||
root.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
|
||||
|
||||
// Site
|
||||
JPanel sitePanel = Components.boxLayout( BoxLayout.PAGE_AXIS );
|
||||
sitePanel.setOpaque( true );
|
||||
sitePanel.setBackground( Res.colors().controlBg() );
|
||||
sitePanel.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
|
||||
root.add( Components.borderPanel( sitePanel, BorderFactory.createRaisedBevelBorder(), Res.colors().frameBg() ) );
|
||||
|
||||
// User
|
||||
sitePanel.add( Components.label( strf( "Generating passwords for: %s", user.getFullName() ), SwingConstants.CENTER ) );
|
||||
sitePanel.add( Components.stud() );
|
||||
|
||||
// Site Name
|
||||
sitePanel.add( Components.label( "Site Name:" ) );
|
||||
JComponent siteControls = Components.boxLayout( BoxLayout.LINE_AXIS, //
|
||||
siteNameField = Components.textField(), Components.stud(),
|
||||
siteActionButton = Components.button( "Add Site" ) );
|
||||
siteNameField.getDocument().addDocumentListener( this );
|
||||
siteNameField.addActionListener( new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
Futures.addCallback( updatePassword( true ), new FutureCallback<String>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable final String sitePassword) {
|
||||
Transferable clipboardContents = new StringSelection( sitePassword );
|
||||
Toolkit.getDefaultToolkit().getSystemClipboard().setContents( clipboardContents, null );
|
||||
|
||||
SwingUtilities.invokeLater( () -> {
|
||||
passwordField.setText( null );
|
||||
siteNameField.setText( null );
|
||||
|
||||
dispatchEvent( new WindowEvent( PasswordFrame.this, WindowEvent.WINDOW_CLOSING ) );
|
||||
} );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@Nonnull final Throwable t) {
|
||||
}
|
||||
} );
|
||||
}
|
||||
} );
|
||||
siteActionButton.addActionListener( e -> {
|
||||
if (currentSite == null)
|
||||
return;
|
||||
if (currentSite instanceof MPFileSite)
|
||||
this.user.deleteSite( currentSite );
|
||||
else
|
||||
this.user.addSite( currentSite );
|
||||
siteNameField.requestFocus();
|
||||
|
||||
updatePassword( true );
|
||||
} );
|
||||
sitePanel.add( siteControls );
|
||||
sitePanel.add( Components.stud() );
|
||||
|
||||
// Site Type & Counter
|
||||
siteCounterModel = new UnsignedIntegerModel( UnsignedInteger.ONE, UnsignedInteger.ONE );
|
||||
MPResultType[] types = Iterables.toArray( MPResultType.forClass( MPResultTypeClass.Template ), MPResultType.class );
|
||||
JComponent siteSettings = Components.boxLayout( BoxLayout.LINE_AXIS, //
|
||||
resultTypeField = Components.comboBox( types ), //
|
||||
Components.stud(), //
|
||||
siteVersionField = Components.comboBox( MPAlgorithm.Version.values() ), //
|
||||
Components.stud(), //
|
||||
siteCounterField = Components.spinner( siteCounterModel ) );
|
||||
sitePanel.add( siteSettings );
|
||||
resultTypeField.setFont( Res.valueFont().deriveFont( resultTypeField.getFont().getSize2D() ) );
|
||||
resultTypeField.setSelectedItem( user.getAlgorithm().mpw_default_result_type() );
|
||||
resultTypeField.addItemListener( e -> updatePassword( true ) );
|
||||
|
||||
siteVersionField.setFont( Res.valueFont().deriveFont( siteVersionField.getFont().getSize2D() ) );
|
||||
siteVersionField.setAlignmentX( RIGHT_ALIGNMENT );
|
||||
siteVersionField.setSelectedItem( user.getAlgorithm() );
|
||||
siteVersionField.addItemListener( e -> updatePassword( true ) );
|
||||
|
||||
siteCounterField.setFont( Res.valueFont().deriveFont( siteCounterField.getFont().getSize2D() ) );
|
||||
siteCounterField.setAlignmentX( RIGHT_ALIGNMENT );
|
||||
siteCounterField.addChangeListener( e -> updatePassword( true ) );
|
||||
|
||||
// Mask
|
||||
maskPasswordField = Components.checkBox( "Hide Password" );
|
||||
maskPasswordField.setAlignmentX( Component.CENTER_ALIGNMENT );
|
||||
maskPasswordField.setSelected( true );
|
||||
maskPasswordField.addItemListener( e -> updateMask() );
|
||||
|
||||
// Password
|
||||
passwordField = Components.passwordField();
|
||||
passwordField.setAlignmentX( Component.CENTER_ALIGNMENT );
|
||||
passwordField.setHorizontalAlignment( SwingConstants.CENTER );
|
||||
passwordField.putClientProperty( "JPasswordField.cutCopyAllowed", true );
|
||||
passwordField.setEditable( false );
|
||||
passwordField.setBackground( null );
|
||||
passwordField.setBorder( null );
|
||||
passwordEchoChar = passwordField.getEchoChar();
|
||||
passwordEchoFont = passwordField.getFont().deriveFont( 40f );
|
||||
updateMask();
|
||||
|
||||
// Tip
|
||||
tipLabel = Components.label( " ", SwingConstants.CENTER );
|
||||
tipLabel.setAlignmentX( Component.CENTER_ALIGNMENT );
|
||||
JPanel passwordContainer = Components.boxLayout( BoxLayout.PAGE_AXIS, maskPasswordField, Box.createGlue(), passwordField,
|
||||
Box.createGlue(), tipLabel );
|
||||
passwordContainer.setOpaque( true );
|
||||
passwordContainer.setBackground( Color.white );
|
||||
passwordContainer.setBorder( BorderFactory.createEmptyBorder( 8, 8, 8, 8 ) );
|
||||
root.add( Box.createVerticalStrut( 8 ) );
|
||||
root.add( Components.borderPanel( passwordContainer, BorderFactory.createLoweredSoftBevelBorder(), Res.colors().frameBg() ),
|
||||
BorderLayout.SOUTH );
|
||||
|
||||
pack();
|
||||
setMinimumSize( new Dimension( Math.max( 600, getPreferredSize().width ), Math.max( 300, getPreferredSize().height ) ) );
|
||||
pack();
|
||||
|
||||
setLocationByPlatform( true );
|
||||
setLocationRelativeTo( null );
|
||||
}
|
||||
|
||||
@SuppressWarnings("MagicNumber")
|
||||
private void updateMask() {
|
||||
passwordField.setEchoChar( maskPasswordField.isSelected()? passwordEchoChar: (char) 0 );
|
||||
passwordField.setFont( maskPasswordField.isSelected()? passwordEchoFont: Res.bigValueFont().deriveFont( 40f ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private ListenableFuture<String> updatePassword(final boolean allowNameCompletion) {
|
||||
|
||||
String siteNameQuery = siteNameField.getText();
|
||||
if (updatingUI)
|
||||
return Futures.immediateCancelledFuture();
|
||||
if ((siteNameQuery == null) || siteNameQuery.isEmpty() || !user.isMasterKeyAvailable()) {
|
||||
siteActionButton.setVisible( false );
|
||||
tipLabel.setText( null );
|
||||
passwordField.setText( null );
|
||||
return Futures.immediateCancelledFuture();
|
||||
}
|
||||
|
||||
MPResultType resultType = resultTypeField.getModel().getElementAt( resultTypeField.getSelectedIndex() );
|
||||
MPAlgorithm siteAlgorithm = siteVersionField.getItemAt( siteVersionField.getSelectedIndex() ).getAlgorithm();
|
||||
UnsignedInteger siteCounter = siteCounterModel.getNumber();
|
||||
|
||||
Collection<S> siteResults = user.findSites( siteNameQuery );
|
||||
if (!allowNameCompletion)
|
||||
siteResults = siteResults.stream().filter(
|
||||
siteResult -> (siteResult != null) && siteNameQuery.equals( siteResult.getName() ) ).collect( Collectors.toList() );
|
||||
S site = ifNotNullElse( Iterables.getFirst( siteResults, null ),
|
||||
createSite( user, siteNameQuery, siteCounter, resultType, siteAlgorithm ) );
|
||||
if ((currentSite != null) && currentSite.getName().equals( site.getName() )) {
|
||||
site.setResultType( resultType );
|
||||
site.setAlgorithm( siteAlgorithm );
|
||||
site.setCounter( siteCounter );
|
||||
}
|
||||
|
||||
ListenableFuture<String> passwordFuture = Res.execute( this, () -> site.getResult( MPKeyPurpose.Authentication, null, null ) );
|
||||
Futures.addCallback( passwordFuture, new FutureCallback<String>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable final String sitePassword) {
|
||||
SwingUtilities.invokeLater( () -> {
|
||||
updatingUI = true;
|
||||
currentSite = site;
|
||||
siteActionButton.setVisible( user instanceof MPFileUser );
|
||||
if (currentSite instanceof MPFileSite)
|
||||
siteActionButton.setText( "Delete Site" );
|
||||
else
|
||||
siteActionButton.setText( "Add Site" );
|
||||
resultTypeField.setSelectedItem( currentSite.getResultType() );
|
||||
siteVersionField.setSelectedItem( currentSite.getAlgorithm() );
|
||||
siteCounterField.setValue( currentSite.getCounter() );
|
||||
siteNameField.setText( currentSite.getName() );
|
||||
if (siteNameField.getText().startsWith( siteNameQuery ))
|
||||
siteNameField.select( siteNameQuery.length(), siteNameField.getText().length() );
|
||||
|
||||
passwordField.setText( sitePassword );
|
||||
tipLabel.setText( "Press [Enter] to copy the password. Then paste it into the password field." );
|
||||
updatingUI = false;
|
||||
} );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@Nonnull final Throwable t) {
|
||||
}
|
||||
} );
|
||||
|
||||
return passwordFuture;
|
||||
}
|
||||
|
||||
protected abstract S createSite(U user, String siteName, UnsignedInteger siteCounter, MPResultType resultType, MPAlgorithm algorithm);
|
||||
|
||||
@Override
|
||||
public void insertUpdate(final DocumentEvent e) {
|
||||
updatePassword( true );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(final DocumentEvent e) {
|
||||
updatePassword( false );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(final DocumentEvent e) {
|
||||
updatePassword( true );
|
||||
}
|
||||
}
|
@@ -0,0 +1,216 @@
|
||||
//==============================================================================
|
||||
// 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.gui.view;
|
||||
|
||||
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.MPIdenticon;
|
||||
import com.lyndir.masterpassword.gui.Res;
|
||||
import com.lyndir.masterpassword.gui.util.Components;
|
||||
import com.lyndir.masterpassword.model.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.*;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-06-08
|
||||
*/
|
||||
@SuppressWarnings({ "MagicNumber", "serial" })
|
||||
public class UnlockFrame extends JFrame {
|
||||
|
||||
private final SignInCallback signInCallback;
|
||||
private final Components.GradientPanel root;
|
||||
private final JLabel identiconLabel;
|
||||
private final JButton signInButton;
|
||||
private final JPanel authenticationContainer;
|
||||
private AuthenticationPanel<?> authenticationPanel;
|
||||
private Future<?> identiconFuture;
|
||||
private boolean incognito;
|
||||
@Nullable
|
||||
private MPUser<? extends MPSite<?>> user;
|
||||
|
||||
public UnlockFrame(final SignInCallback signInCallback) {
|
||||
super( "Unlock Master Password" );
|
||||
this.signInCallback = signInCallback;
|
||||
|
||||
setDefaultCloseOperation( DISPOSE_ON_CLOSE );
|
||||
addWindowFocusListener( new WindowAdapter() {
|
||||
@Override
|
||||
public void windowGainedFocus(final WindowEvent e) {
|
||||
root.setGradientColor( Res.colors().frameBg() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowLostFocus(final WindowEvent e) {
|
||||
root.setGradientColor( Color.RED );
|
||||
}
|
||||
} );
|
||||
|
||||
// Sign In
|
||||
JPanel signInBox = Components.boxLayout( BoxLayout.LINE_AXIS, Box.createGlue(), signInButton = Components.button( "Sign In" ),
|
||||
Box.createGlue() );
|
||||
signInBox.setBackground( null );
|
||||
|
||||
setContentPane( root = Components.gradientPanel( new FlowLayout(), Res.colors().frameBg() ) );
|
||||
root.setLayout( new BoxLayout( root, BoxLayout.PAGE_AXIS ) );
|
||||
root.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
|
||||
root.add( Components.borderPanel( authenticationContainer = Components.boxLayout( BoxLayout.PAGE_AXIS ),
|
||||
BorderFactory.createRaisedBevelBorder(), Res.colors().frameBg() ) );
|
||||
root.add( Box.createVerticalStrut( 8 ) );
|
||||
root.add( identiconLabel = Components.label( " ", SwingConstants.CENTER ) );
|
||||
root.add( Box.createVerticalStrut( 8 ) );
|
||||
root.add( signInBox );
|
||||
|
||||
authenticationContainer.setOpaque( true );
|
||||
authenticationContainer.setBackground( Res.colors().controlBg() );
|
||||
authenticationContainer.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
|
||||
identiconLabel.setFont( Res.emoticonsFont().deriveFont( 14.f ) );
|
||||
identiconLabel.setToolTipText(
|
||||
strf( "A representation of your identity across all Master Password apps.%nIt should always be the same." ) );
|
||||
signInButton.addActionListener( new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
trySignIn();
|
||||
}
|
||||
} );
|
||||
|
||||
createAuthenticationPanel();
|
||||
|
||||
setLocationByPlatform( true );
|
||||
setLocationRelativeTo( null );
|
||||
}
|
||||
|
||||
protected void repack() {
|
||||
pack();
|
||||
setMinimumSize( new Dimension( Math.max( 300, getPreferredSize().width ), Math.max( 300, getPreferredSize().height ) ) );
|
||||
pack();
|
||||
}
|
||||
|
||||
private void createAuthenticationPanel() {
|
||||
authenticationContainer.removeAll();
|
||||
|
||||
if (incognito) {
|
||||
authenticationPanel = new IncognitoAuthenticationPanel( this );
|
||||
} else {
|
||||
authenticationPanel = new ModelAuthenticationPanel( this );
|
||||
}
|
||||
authenticationPanel.updateUser( false );
|
||||
authenticationContainer.add( authenticationPanel );
|
||||
authenticationContainer.add( Components.stud() );
|
||||
|
||||
JCheckBox incognitoCheckBox = Components.checkBox( "Incognito" );
|
||||
incognitoCheckBox.setToolTipText( "Log in without saving any information." );
|
||||
incognitoCheckBox.setSelected( incognito );
|
||||
incognitoCheckBox.addItemListener( e -> {
|
||||
incognito = incognitoCheckBox.isSelected();
|
||||
SwingUtilities.invokeLater( this::createAuthenticationPanel );
|
||||
} );
|
||||
|
||||
JComponent toolsPanel = Components.boxLayout( BoxLayout.LINE_AXIS, incognitoCheckBox, Box.createGlue() );
|
||||
authenticationContainer.add( toolsPanel );
|
||||
for (final JButton button : authenticationPanel.getButtons()) {
|
||||
toolsPanel.add( button );
|
||||
button.setBorder( BorderFactory.createEmptyBorder() );
|
||||
button.setMargin( new Insets( 0, 0, 0, 0 ) );
|
||||
button.setAlignmentX( RIGHT_ALIGNMENT );
|
||||
button.setContentAreaFilled( false );
|
||||
}
|
||||
|
||||
checkSignIn();
|
||||
validate();
|
||||
repack();
|
||||
|
||||
SwingUtilities.invokeLater( () -> ifNotNullElse( authenticationPanel.getFocusComponent(), signInButton ).requestFocusInWindow() );
|
||||
}
|
||||
|
||||
void updateUser(@Nullable final MPUser<? extends MPSite<?>> user) {
|
||||
this.user = user;
|
||||
checkSignIn();
|
||||
}
|
||||
|
||||
boolean checkSignIn() {
|
||||
if (identiconFuture != null)
|
||||
identiconFuture.cancel( false );
|
||||
identiconFuture = Res.schedule( this, () -> SwingUtilities.invokeLater( () -> {
|
||||
String fullName = (user == null)? "": user.getFullName();
|
||||
char[] masterPassword = authenticationPanel.getMasterPassword();
|
||||
|
||||
if (fullName.isEmpty() || (masterPassword.length == 0)) {
|
||||
identiconLabel.setText( " " );
|
||||
return;
|
||||
}
|
||||
|
||||
MPIdenticon identicon = new MPIdenticon( fullName, masterPassword );
|
||||
identiconLabel.setText( identicon.getText() );
|
||||
identiconLabel.setForeground(
|
||||
Res.colors().fromIdenticonColor( identicon.getColor(), Res.Colors.BackgroundMode.DARK ) );
|
||||
} ), 300, TimeUnit.MILLISECONDS );
|
||||
|
||||
String fullName = (user == null)? "": user.getFullName();
|
||||
char[] masterPassword = authenticationPanel.getMasterPassword();
|
||||
boolean enabled = !fullName.isEmpty() && (masterPassword.length > 0);
|
||||
signInButton.setEnabled( enabled );
|
||||
|
||||
return enabled;
|
||||
}
|
||||
|
||||
void trySignIn(final JComponent... signInComponents) {
|
||||
if ((user == null) || !checkSignIn())
|
||||
return;
|
||||
|
||||
for (final JComponent signInComponent : signInComponents)
|
||||
signInComponent.setEnabled( false );
|
||||
|
||||
signInButton.setEnabled( false );
|
||||
signInButton.setText( "Signing In..." );
|
||||
|
||||
Res.execute( this, () -> {
|
||||
try {
|
||||
user.authenticate( authenticationPanel.getMasterPassword() );
|
||||
|
||||
SwingUtilities.invokeLater( () -> {
|
||||
signInCallback.signedIn( authenticationPanel.newPasswordFrame() );
|
||||
dispose();
|
||||
} );
|
||||
}
|
||||
catch (final MPIncorrectMasterPasswordException | MPAlgorithmException e) {
|
||||
SwingUtilities.invokeLater( () -> {
|
||||
JOptionPane.showMessageDialog( null, e.getLocalizedMessage(), "Sign In Failed", JOptionPane.ERROR_MESSAGE );
|
||||
authenticationPanel.reset();
|
||||
signInButton.setText( "Sign In" );
|
||||
for (final JComponent signInComponent : signInComponents)
|
||||
signInComponent.setEnabled( true );
|
||||
checkSignIn();
|
||||
} );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface SignInCallback {
|
||||
|
||||
void signedIn(PasswordFrame<?, ?> passwordFrame);
|
||||
}
|
||||
}
|
@@ -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-04-26
|
||||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
package com.lyndir.masterpassword.gui.view;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
BIN
platform-independent/java/gui/src/main/resources/fonts/Arimo-Bold.ttf
Executable file
BIN
platform-independent/java/gui/src/main/resources/fonts/Arimo-Bold.ttf
Executable file
Binary file not shown.
BIN
platform-independent/java/gui/src/main/resources/fonts/Arimo-BoldItalic.ttf
Executable file
BIN
platform-independent/java/gui/src/main/resources/fonts/Arimo-BoldItalic.ttf
Executable file
Binary file not shown.
BIN
platform-independent/java/gui/src/main/resources/fonts/Arimo-Italic.ttf
Executable file
BIN
platform-independent/java/gui/src/main/resources/fonts/Arimo-Italic.ttf
Executable file
Binary file not shown.
BIN
platform-independent/java/gui/src/main/resources/fonts/Arimo-Regular.ttf
Executable file
BIN
platform-independent/java/gui/src/main/resources/fonts/Arimo-Regular.ttf
Executable file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
platform-independent/java/gui/src/main/resources/fonts/SourceSansPro-Black.otf
Executable file
BIN
platform-independent/java/gui/src/main/resources/fonts/SourceSansPro-Black.otf
Executable file
Binary file not shown.
BIN
platform-independent/java/gui/src/main/resources/fonts/SourceSansPro-BlackIt.otf
Executable file
BIN
platform-independent/java/gui/src/main/resources/fonts/SourceSansPro-BlackIt.otf
Executable file
Binary file not shown.
BIN
platform-independent/java/gui/src/main/resources/fonts/SourceSansPro-Bold.otf
Executable file
BIN
platform-independent/java/gui/src/main/resources/fonts/SourceSansPro-Bold.otf
Executable file
Binary file not shown.
BIN
platform-independent/java/gui/src/main/resources/fonts/SourceSansPro-BoldIt.otf
Executable file
BIN
platform-independent/java/gui/src/main/resources/fonts/SourceSansPro-BoldIt.otf
Executable file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
platform-independent/java/gui/src/main/resources/fonts/SourceSansPro-It.otf
Executable file
BIN
platform-independent/java/gui/src/main/resources/fonts/SourceSansPro-It.otf
Executable file
Binary file not shown.
BIN
platform-independent/java/gui/src/main/resources/fonts/SourceSansPro-Light.otf
Executable file
BIN
platform-independent/java/gui/src/main/resources/fonts/SourceSansPro-Light.otf
Executable file
Binary file not shown.
BIN
platform-independent/java/gui/src/main/resources/fonts/SourceSansPro-LightIt.otf
Executable file
BIN
platform-independent/java/gui/src/main/resources/fonts/SourceSansPro-LightIt.otf
Executable file
Binary file not shown.
BIN
platform-independent/java/gui/src/main/resources/fonts/SourceSansPro-Regular.otf
Executable file
BIN
platform-independent/java/gui/src/main/resources/fonts/SourceSansPro-Regular.otf
Executable file
Binary file not shown.
Binary file not shown.
Binary file not shown.
15
platform-independent/java/gui/src/main/resources/logback.xml
Normal file
15
platform-independent/java/gui/src/main/resources/logback.xml
Normal file
@@ -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:-INFO}" />
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="stdout" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user