Clean up now that implementation is Native only.
This commit is contained in:
parent
aec5e371b8
commit
023749049a
@ -5,6 +5,10 @@ add_library( mpw SHARED
|
||||
"${PROJECT_SOURCE_DIR}/../platform-independent/c/core/src/base64.c"
|
||||
"${PROJECT_SOURCE_DIR}/../platform-independent/c/core/src/aes.c"
|
||||
"${PROJECT_SOURCE_DIR}/../platform-independent/c/core/src/mpw-algorithm.c"
|
||||
"${PROJECT_SOURCE_DIR}/../platform-independent/c/core/src/mpw-algorithm_v0.c"
|
||||
"${PROJECT_SOURCE_DIR}/../platform-independent/c/core/src/mpw-algorithm_v1.c"
|
||||
"${PROJECT_SOURCE_DIR}/../platform-independent/c/core/src/mpw-algorithm_v2.c"
|
||||
"${PROJECT_SOURCE_DIR}/../platform-independent/c/core/src/mpw-algorithm_v3.c"
|
||||
"${PROJECT_SOURCE_DIR}/../platform-independent/c/core/src/mpw-types.c"
|
||||
"${PROJECT_SOURCE_DIR}/../platform-independent/c/core/src/mpw-util.c"
|
||||
"${PROJECT_SOURCE_DIR}/../platform-independent/c/core/src/mpw-marshal-util.c"
|
||||
|
@ -316,8 +316,8 @@ public class EmergencyActivity extends Activity {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
sitePassword = masterKey.siteResult( siteName, version.getAlgorithm(), counter,
|
||||
MPKeyPurpose.Authentication, null, type, null );
|
||||
sitePassword = masterKey.siteResult(
|
||||
siteName, version, counter, MPKeyPurpose.Authentication, null, type, null );
|
||||
|
||||
runOnUiThread( new Runnable() {
|
||||
@Override
|
||||
|
@ -149,7 +149,7 @@ public final class Preferences {
|
||||
@Nonnull
|
||||
public MPResultType getDefaultResultType() {
|
||||
return MPResultType.values()[
|
||||
prefs().getInt( PREF_RESULT_TYPE, getDefaultVersion().getAlgorithm().mpw_default_result_type().ordinal() )];
|
||||
prefs().getInt( PREF_RESULT_TYPE, getDefaultVersion().mpw_default_result_type().ordinal() )];
|
||||
}
|
||||
|
||||
public boolean setDefaultVersion(final MPAlgorithm.Version value) {
|
||||
|
@ -0,0 +1,47 @@
|
||||
/* DO NOT EDIT THIS FILE - it is machine generated */
|
||||
#include <jni.h>
|
||||
/* Header for class com_lyndir_masterpassword_MPAlgorithm_Version */
|
||||
|
||||
#ifndef _Included_com_lyndir_masterpassword_MPAlgorithm_Version
|
||||
#define _Included_com_lyndir_masterpassword_MPAlgorithm_Version
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#undef com_lyndir_masterpassword_MPAlgorithm_Version_AES_BLOCKSIZE
|
||||
#define com_lyndir_masterpassword_MPAlgorithm_Version_AES_BLOCKSIZE 128L
|
||||
/*
|
||||
* Class: com_lyndir_masterpassword_MPAlgorithm_Version
|
||||
* Method: _masterKey
|
||||
* Signature: (Ljava/lang/String;[BI)[B
|
||||
*/
|
||||
JNIEXPORT jbyteArray JNICALL Java_com_lyndir_masterpassword_MPAlgorithm_00024Version__1masterKey
|
||||
(JNIEnv *, jobject, jstring, jbyteArray, jint);
|
||||
|
||||
/*
|
||||
* Class: com_lyndir_masterpassword_MPAlgorithm_Version
|
||||
* Method: _siteKey
|
||||
* Signature: ([BLjava/lang/String;JILjava/lang/String;I)[B
|
||||
*/
|
||||
JNIEXPORT jbyteArray JNICALL Java_com_lyndir_masterpassword_MPAlgorithm_00024Version__1siteKey
|
||||
(JNIEnv *, jobject, jbyteArray, jstring, jlong, jint, jstring, jint);
|
||||
|
||||
/*
|
||||
* Class: com_lyndir_masterpassword_MPAlgorithm_Version
|
||||
* Method: _siteResult
|
||||
* Signature: ([B[BLjava/lang/String;JILjava/lang/String;ILjava/lang/String;I)Ljava/lang/String;
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL Java_com_lyndir_masterpassword_MPAlgorithm_00024Version__1siteResult
|
||||
(JNIEnv *, jobject, jbyteArray, jbyteArray, jstring, jlong, jint, jstring, jint, jstring, jint);
|
||||
|
||||
/*
|
||||
* Class: com_lyndir_masterpassword_MPAlgorithm_Version
|
||||
* Method: _siteState
|
||||
* Signature: ([B[BLjava/lang/String;JILjava/lang/String;ILjava/lang/String;I)Ljava/lang/String;
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL Java_com_lyndir_masterpassword_MPAlgorithm_00024Version__1siteState
|
||||
(JNIEnv *, jobject, jbyteArray, jbyteArray, jstring, jlong, jint, jstring, jint, jstring, jint);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
@ -1,47 +0,0 @@
|
||||
/* DO NOT EDIT THIS FILE - it is machine generated */
|
||||
#include <jni.h>
|
||||
/* Header for class com_lyndir_masterpassword_impl_MPAlgorithmV0 */
|
||||
|
||||
#ifndef _Included_com_lyndir_masterpassword_impl_MPAlgorithmV0
|
||||
#define _Included_com_lyndir_masterpassword_impl_MPAlgorithmV0
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#undef com_lyndir_masterpassword_impl_MPAlgorithmV0_AES_BLOCKSIZE
|
||||
#define com_lyndir_masterpassword_impl_MPAlgorithmV0_AES_BLOCKSIZE 128L
|
||||
/*
|
||||
* Class: com_lyndir_masterpassword_impl_MPAlgorithmV0
|
||||
* Method: _masterKey
|
||||
* Signature: (Ljava/lang/String;[BI)[B
|
||||
*/
|
||||
JNIEXPORT jbyteArray JNICALL Java_com_lyndir_masterpassword_impl_MPAlgorithmV0__1masterKey
|
||||
(JNIEnv *, jobject, jstring, jbyteArray, jint);
|
||||
|
||||
/*
|
||||
* Class: com_lyndir_masterpassword_impl_MPAlgorithmV0
|
||||
* Method: _siteKey
|
||||
* Signature: ([BLjava/lang/String;JILjava/lang/String;I)[B
|
||||
*/
|
||||
JNIEXPORT jbyteArray JNICALL Java_com_lyndir_masterpassword_impl_MPAlgorithmV0__1siteKey
|
||||
(JNIEnv *, jobject, jbyteArray, jstring, jlong, jint, jstring, jint);
|
||||
|
||||
/*
|
||||
* Class: com_lyndir_masterpassword_impl_MPAlgorithmV0
|
||||
* Method: _siteResult
|
||||
* Signature: ([B[BLjava/lang/String;JILjava/lang/String;ILjava/lang/String;I)Ljava/lang/String;
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL Java_com_lyndir_masterpassword_impl_MPAlgorithmV0__1siteResult
|
||||
(JNIEnv *, jobject, jbyteArray, jbyteArray, jstring, jlong, jint, jstring, jint, jstring, jint);
|
||||
|
||||
/*
|
||||
* Class: com_lyndir_masterpassword_impl_MPAlgorithmV0
|
||||
* Method: _siteState
|
||||
* Signature: ([B[BLjava/lang/String;JILjava/lang/String;ILjava/lang/String;I)Ljava/lang/String;
|
||||
*/
|
||||
JNIEXPORT jstring JNICALL Java_com_lyndir_masterpassword_impl_MPAlgorithmV0__1siteState
|
||||
(JNIEnv *, jobject, jbyteArray, jbyteArray, jstring, jlong, jint, jstring, jint, jstring, jint);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
@ -1,6 +1,6 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "java/com_lyndir_masterpassword_impl_MPAlgorithmV0.h"
|
||||
#include "java/com_lyndir_masterpassword_MPAlgorithm_Version.h"
|
||||
|
||||
#include "mpw-algorithm.h"
|
||||
#include "mpw-util.h"
|
||||
|
@ -22,12 +22,15 @@ 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.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.impl.*;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.*;
|
||||
import java.nio.charset.*;
|
||||
import java.util.Arrays;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@ -36,7 +39,7 @@ import javax.annotation.Nullable;
|
||||
* @see Version
|
||||
*/
|
||||
@SuppressWarnings({ "FieldMayBeStatic", "NewMethodNamingConvention", "MethodReturnAlwaysConstant" })
|
||||
public abstract class MPAlgorithm {
|
||||
public interface MPAlgorithm {
|
||||
|
||||
/**
|
||||
* Derive a master key that describes a user's identity.
|
||||
@ -45,7 +48,7 @@ public abstract class MPAlgorithm {
|
||||
* @param masterPassword The user's secret that authenticates his access to the identity.
|
||||
*/
|
||||
@Nullable
|
||||
public abstract byte[] masterKey(String fullName, char[] masterPassword);
|
||||
byte[] masterKey(String fullName, char[] masterPassword);
|
||||
|
||||
/**
|
||||
* Derive a site key that describes a user's access to a specific entity.
|
||||
@ -57,7 +60,7 @@ public abstract class MPAlgorithm {
|
||||
* @param keyContext An action-specific context within which to scope the key.
|
||||
*/
|
||||
@Nullable
|
||||
public abstract byte[] siteKey(byte[] masterKey, String siteName, UnsignedInteger siteCounter,
|
||||
byte[] siteKey(byte[] masterKey, String siteName, UnsignedInteger siteCounter,
|
||||
MPKeyPurpose keyPurpose, @Nullable String keyContext);
|
||||
|
||||
/**
|
||||
@ -67,7 +70,7 @@ public abstract class MPAlgorithm {
|
||||
* @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,
|
||||
String siteResult(byte[] masterKey, byte[] siteKey, String siteName, UnsignedInteger siteCounter,
|
||||
MPKeyPurpose keyPurpose, @Nullable String keyContext,
|
||||
MPResultType resultType, @Nullable String resultParam);
|
||||
|
||||
@ -80,7 +83,7 @@ public abstract class MPAlgorithm {
|
||||
* @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,
|
||||
String siteState(byte[] masterKey, byte[] siteKey, String siteName, UnsignedInteger siteCounter,
|
||||
MPKeyPurpose keyPurpose, @Nullable String keyContext,
|
||||
MPResultType resultType, String resultParam);
|
||||
|
||||
@ -90,115 +93,105 @@ public abstract class MPAlgorithm {
|
||||
* The linear version identifier of this algorithm's implementation.
|
||||
*/
|
||||
@Nonnull
|
||||
public abstract Version version();
|
||||
Version version();
|
||||
|
||||
/**
|
||||
* mpw: defaults: initial counter value.
|
||||
*/
|
||||
@Nonnull
|
||||
public abstract UnsignedInteger mpw_default_counter();
|
||||
UnsignedInteger mpw_default_counter();
|
||||
|
||||
/**
|
||||
* mpw: defaults: password result type.
|
||||
*/
|
||||
@Nonnull
|
||||
public abstract MPResultType mpw_default_result_type();
|
||||
MPResultType mpw_default_result_type();
|
||||
|
||||
/**
|
||||
* mpw: defaults: login result type.
|
||||
*/
|
||||
@Nonnull
|
||||
public abstract MPResultType mpw_default_login_type();
|
||||
MPResultType mpw_default_login_type();
|
||||
|
||||
/**
|
||||
* mpw: defaults: answer result type.
|
||||
*/
|
||||
@Nonnull
|
||||
public abstract MPResultType mpw_default_answer_type();
|
||||
MPResultType mpw_default_answer_type();
|
||||
|
||||
/**
|
||||
* mpw: Input character encoding.
|
||||
*/
|
||||
@Nonnull
|
||||
public abstract Charset mpw_charset();
|
||||
Charset mpw_charset();
|
||||
|
||||
/**
|
||||
* mpw: Platform-agnostic byte order.
|
||||
*/
|
||||
@Nonnull
|
||||
public abstract ByteOrder mpw_byteOrder();
|
||||
ByteOrder mpw_byteOrder();
|
||||
|
||||
/**
|
||||
* mpw: Key ID hash.
|
||||
*/
|
||||
@Nonnull
|
||||
public abstract MessageDigests mpw_hash();
|
||||
MessageDigests mpw_hash();
|
||||
|
||||
/**
|
||||
* mpw: Site digest.
|
||||
*/
|
||||
@Nonnull
|
||||
public abstract MessageAuthenticationDigests mpw_digest();
|
||||
MessageAuthenticationDigests mpw_digest();
|
||||
|
||||
/**
|
||||
* mpw: Master key size (byte).
|
||||
*/
|
||||
public abstract int mpw_dkLen();
|
||||
int mpw_dkLen();
|
||||
|
||||
/**
|
||||
* mpw: Minimum size for derived keys (bit).
|
||||
*/
|
||||
public abstract int mpw_keySize_min();
|
||||
int mpw_keySize_min();
|
||||
|
||||
/**
|
||||
* mpw: Maximum size for derived keys (bit).
|
||||
*/
|
||||
public abstract int mpw_keySize_max();
|
||||
int mpw_keySize_max();
|
||||
|
||||
/**
|
||||
* mpw: validity for the time-based rolling counter (s).
|
||||
*/
|
||||
public abstract long mpw_otp_window();
|
||||
long mpw_otp_window();
|
||||
|
||||
/**
|
||||
* scrypt: CPU cost parameter.
|
||||
*/
|
||||
public abstract int scrypt_N();
|
||||
int scrypt_N();
|
||||
|
||||
/**
|
||||
* scrypt: Memory cost parameter.
|
||||
*/
|
||||
public abstract int scrypt_r();
|
||||
int scrypt_r();
|
||||
|
||||
/**
|
||||
* scrypt: Parallelization parameter.
|
||||
*/
|
||||
public abstract int scrypt_p();
|
||||
int scrypt_p();
|
||||
|
||||
// Utilities
|
||||
|
||||
@Nonnull
|
||||
protected abstract byte[] toBytes(int number);
|
||||
byte[] toBytes(final int number);
|
||||
|
||||
@Nonnull
|
||||
protected abstract byte[] toBytes(UnsignedInteger number);
|
||||
byte[] toBytes(final UnsignedInteger number);
|
||||
|
||||
@Nonnull
|
||||
protected abstract byte[] toBytes(char[] characters);
|
||||
byte[] toBytes(final char[] characters);
|
||||
|
||||
@Nonnull
|
||||
protected abstract byte[] toID(byte[] bytes);
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
return strf( "%d, %s", version().toInt(), getClass().getSimpleName() );
|
||||
}
|
||||
byte[] toID(final byte[] bytes);
|
||||
|
||||
/**
|
||||
* The algorithm iterations.
|
||||
*/
|
||||
public enum Version {
|
||||
enum Version implements MPAlgorithm {
|
||||
|
||||
/**
|
||||
* bugs:
|
||||
@ -206,38 +199,38 @@ public abstract class MPAlgorithm {
|
||||
* - miscounted the byte-length for multi-byte site names.
|
||||
* - miscounted the byte-length for multi-byte user names.
|
||||
*/
|
||||
V0( new MPAlgorithmV0() ),
|
||||
V0,
|
||||
|
||||
/**
|
||||
* bugs:
|
||||
* - miscounted the byte-length for multi-byte site names.
|
||||
* - miscounted the byte-length for multi-byte user names.
|
||||
*/
|
||||
V1( new MPAlgorithmV1() ),
|
||||
V1,
|
||||
|
||||
/**
|
||||
* bugs:
|
||||
* - miscounted the byte-length for multi-byte user names.
|
||||
*/
|
||||
V2( new MPAlgorithmV2() ),
|
||||
V2,
|
||||
|
||||
/**
|
||||
* bugs:
|
||||
* - no known issues.
|
||||
*/
|
||||
V3( new MPAlgorithmV3() );
|
||||
V3;
|
||||
|
||||
public static final Version CURRENT = V3;
|
||||
|
||||
private final MPAlgorithm algorithm;
|
||||
@SuppressWarnings("HardcodedFileSeparator")
|
||||
private static final String AES_TRANSFORMATION = "AES/CBC/PKCS5Padding";
|
||||
private static final int AES_BLOCKSIZE = 128 /* bit */;
|
||||
|
||||
Version(final MPAlgorithm algorithm) {
|
||||
this.algorithm = algorithm;
|
||||
static {
|
||||
Native.load( MPAlgorithm.class, "mpw" );
|
||||
}
|
||||
|
||||
public MPAlgorithm getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
protected final Logger logger = Logger.get( getClass() );
|
||||
|
||||
@JsonCreator
|
||||
public static Version fromInt(final int algorithmVersion) {
|
||||
@ -250,5 +243,207 @@ public abstract class MPAlgorithm {
|
||||
|
||||
return ordinal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
return strf( "%d, %s", version().toInt(), getClass().getSimpleName() );
|
||||
}
|
||||
|
||||
@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.isError())
|
||||
throw new IllegalStateException( result.toString() );
|
||||
result = encoder.flush( masterPasswordBuffer );
|
||||
if (result.isError())
|
||||
throw new IllegalStateException( result.toString() );
|
||||
|
||||
return _masterKey( fullName, masterPasswordBytes, version().toInt() );
|
||||
}
|
||||
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
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Version version() {
|
||||
return MPAlgorithm.Version.V0;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public UnsignedInteger mpw_default_counter() {
|
||||
return UnsignedInteger.ONE;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPResultType mpw_default_result_type() {
|
||||
return MPResultType.GeneratedLong;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPResultType mpw_default_login_type() {
|
||||
return MPResultType.GeneratedName;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPResultType mpw_default_answer_type() {
|
||||
return MPResultType.GeneratedPhrase;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Charset mpw_charset() {
|
||||
return Charsets.UTF_8;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public ByteOrder mpw_byteOrder() {
|
||||
return ByteOrder.BIG_ENDIAN;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MessageDigests mpw_hash() {
|
||||
return MessageDigests.SHA256;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@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
|
||||
|
||||
@Nonnull
|
||||
public byte[] toBytes(final int number) {
|
||||
return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( mpw_byteOrder() ).putInt( number ).array();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public byte[] toBytes(final UnsignedInteger number) {
|
||||
return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( mpw_byteOrder() ).putInt( number.intValue() ).array();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
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;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public byte[] toID(final byte[] bytes) {
|
||||
return mpw_hash().of( bytes );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,250 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword.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.Nonnull;
|
||||
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, "mpw" );
|
||||
}
|
||||
|
||||
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.isError())
|
||||
throw new IllegalStateException( result.toString() );
|
||||
result = encoder.flush( masterPasswordBuffer );
|
||||
if (result.isError())
|
||||
throw new IllegalStateException( result.toString() );
|
||||
|
||||
return _masterKey( fullName, masterPasswordBytes, version().toInt() );
|
||||
}
|
||||
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
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Version version() {
|
||||
return MPAlgorithm.Version.V0;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public UnsignedInteger mpw_default_counter() {
|
||||
return UnsignedInteger.ONE;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPResultType mpw_default_result_type() {
|
||||
return MPResultType.GeneratedLong;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPResultType mpw_default_login_type() {
|
||||
return MPResultType.GeneratedName;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPResultType mpw_default_answer_type() {
|
||||
return MPResultType.GeneratedPhrase;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Charset mpw_charset() {
|
||||
return Charsets.UTF_8;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public ByteOrder mpw_byteOrder() {
|
||||
return ByteOrder.BIG_ENDIAN;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MessageDigests mpw_hash() {
|
||||
return MessageDigests.SHA256;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@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
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public byte[] toBytes(final int number) {
|
||||
return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( mpw_byteOrder() ).putInt( number ).array();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public byte[] toBytes(final UnsignedInteger number) {
|
||||
return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( mpw_byteOrder() ).putInt( number.intValue() ).array();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@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;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public byte[] toID(final byte[] bytes) {
|
||||
return mpw_hash().of( bytes );
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword.impl;
|
||||
|
||||
import com.lyndir.masterpassword.*;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-08-30
|
||||
* @see Version#V1
|
||||
*/
|
||||
public class MPAlgorithmV1 extends MPAlgorithmV0 {
|
||||
|
||||
// Configuration
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Version version() {
|
||||
return MPAlgorithm.Version.V1;
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword.impl;
|
||||
|
||||
import com.lyndir.masterpassword.MPAlgorithm;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-08-30
|
||||
* @see Version#V2
|
||||
*/
|
||||
public class MPAlgorithmV2 extends MPAlgorithmV1 {
|
||||
|
||||
// Configuration
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Version version() {
|
||||
return MPAlgorithm.Version.V2;
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
package com.lyndir.masterpassword.impl;
|
||||
|
||||
import com.lyndir.masterpassword.MPAlgorithm;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-08-30
|
||||
* @see Version#V3
|
||||
*/
|
||||
public class MPAlgorithmV3 extends MPAlgorithmV2 {
|
||||
|
||||
// Configuration
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Version version() {
|
||||
return MPAlgorithm.Version.V3;
|
||||
}
|
||||
}
|
@ -30,7 +30,7 @@ import javax.annotation.Nullable;
|
||||
public class MPIncognitoUser extends MPBasicUser<MPIncognitoSite> {
|
||||
|
||||
public MPIncognitoUser(final String fullName) {
|
||||
super( fullName, MPAlgorithm.Version.CURRENT.getAlgorithm() );
|
||||
super( fullName, MPAlgorithm.Version.CURRENT );
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
@ -577,7 +577,7 @@ public class UserContentPanel extends JPanel implements State.Listener, MPUser.L
|
||||
|
||||
components.add( Components.label( "Default Algorithm:" ),
|
||||
Components.comboBox( MPAlgorithm.Version.values(), MPAlgorithm.Version::name,
|
||||
user.getAlgorithm().version(), version -> user.setAlgorithm( version.getAlgorithm() ) ) );
|
||||
user.getAlgorithm().version(), user::setAlgorithm ) );
|
||||
|
||||
components.add( Components.label( "Default Password Type:" ),
|
||||
Components.comboBox( MPResultType.values(), MPResultType::getLongName,
|
||||
@ -614,7 +614,7 @@ public class UserContentPanel extends JPanel implements State.Listener, MPUser.L
|
||||
components.add( Components.label( "Algorithm:" ),
|
||||
Components.comboBox( MPAlgorithm.Version.values(), MPAlgorithm.Version::name,
|
||||
site.getAlgorithm().version(),
|
||||
version -> site.setAlgorithm( version.getAlgorithm() ) ) );
|
||||
site::setAlgorithm ) );
|
||||
|
||||
components.add( Components.label( "Counter:" ),
|
||||
Components.spinner( new UnsignedIntegerModel( site.getCounter(), UnsignedInteger.ONE )
|
||||
|
@ -59,7 +59,7 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
||||
}
|
||||
|
||||
public MPFileUser(final String fullName, final File location) {
|
||||
this( fullName, null, MPAlgorithm.Version.CURRENT.getAlgorithm(), location );
|
||||
this( fullName, null, MPAlgorithm.Version.CURRENT, location );
|
||||
}
|
||||
|
||||
public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPAlgorithm algorithm, final File location) {
|
||||
|
@ -68,7 +68,7 @@ public class MPFlatUnmarshaller implements MPUnmarshaller {
|
||||
else if ((fullName != null) && (keyID != null))
|
||||
// Ends the header.
|
||||
return new MPFileUser(
|
||||
fullName, keyID, MPAlgorithm.Version.fromInt( mpVersion ).getAlgorithm(), avatar, defaultType,
|
||||
fullName, keyID, MPAlgorithm.Version.fromInt( mpVersion ), avatar, defaultType,
|
||||
date, false, clearContent? MPMarshaller.ContentMode.VISIBLE: MPMarshaller.ContentMode.PROTECTED,
|
||||
MPMarshalFormat.Flat, file
|
||||
);
|
||||
@ -157,7 +157,7 @@ public class MPFlatUnmarshaller implements MPUnmarshaller {
|
||||
site = new MPFileSite(
|
||||
user, siteMatcher.group( 5 ),
|
||||
MPAlgorithm.Version.fromInt( ConversionUtils.toIntegerNN(
|
||||
colon.matcher( siteMatcher.group( 4 ) ).replaceAll( "" ) ) ).getAlgorithm(),
|
||||
colon.matcher( siteMatcher.group( 4 ) ).replaceAll( "" ) ) ),
|
||||
user.getAlgorithm().mpw_default_counter(),
|
||||
MPResultType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),
|
||||
clearContent? null: siteMatcher.group( 6 ),
|
||||
@ -171,7 +171,7 @@ public class MPFlatUnmarshaller implements MPUnmarshaller {
|
||||
site = new MPFileSite(
|
||||
user, siteMatcher.group( 7 ),
|
||||
MPAlgorithm.Version.fromInt( ConversionUtils.toIntegerNN(
|
||||
colon.matcher( siteMatcher.group( 4 ) ).replaceAll( "" ) ) ).getAlgorithm(),
|
||||
colon.matcher( siteMatcher.group( 4 ) ).replaceAll( "" ) ) ),
|
||||
UnsignedInteger.valueOf(
|
||||
colon.matcher( siteMatcher.group( 5 ) ).replaceAll( "" ) ),
|
||||
MPResultType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),
|
||||
|
@ -128,7 +128,7 @@ public class MPJSONFile extends MPJSONAnyObject {
|
||||
}
|
||||
|
||||
MPFileUser readUser(final File file) {
|
||||
MPAlgorithm algorithm = ifNotNullElse( user.algorithm, MPAlgorithm.Version.CURRENT ).getAlgorithm();
|
||||
MPAlgorithm algorithm = ifNotNullElse( user.algorithm, MPAlgorithm.Version.CURRENT );
|
||||
|
||||
return new MPFileUser(
|
||||
user.full_name, CodeUtils.decodeHex( user.key_id ), algorithm, user.avatar,
|
||||
@ -146,7 +146,7 @@ public class MPJSONFile extends MPJSONAnyObject {
|
||||
String siteName = siteEntry.getKey();
|
||||
Site fileSite = siteEntry.getValue();
|
||||
MPFileSite site = new MPFileSite(
|
||||
user, siteName, fileSite.algorithm.getAlgorithm(), UnsignedInteger.valueOf( fileSite.counter ),
|
||||
user, siteName, fileSite.algorithm, 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,
|
||||
|
@ -191,7 +191,7 @@ public class MPTests {
|
||||
|
||||
@Nonnull
|
||||
public MPAlgorithm getAlgorithm() {
|
||||
return MPAlgorithm.Version.fromInt( checkNotNull( algorithm ) ).getAlgorithm();
|
||||
return MPAlgorithm.Version.fromInt( checkNotNull( algorithm ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
|
@ -106,9 +106,7 @@ public class MPMasterKeyTest {
|
||||
|
||||
String password = randomString( 8 );
|
||||
MPResultType resultType = MPResultType.StoredPersonal;
|
||||
for (final MPAlgorithm.Version version : MPAlgorithm.Version.values()) {
|
||||
MPAlgorithm algorithm = version.getAlgorithm();
|
||||
|
||||
for (final MPAlgorithm.Version algorithm : MPAlgorithm.Version.values()) {
|
||||
// Test site state
|
||||
String state = masterKey.siteState( testCase.getSiteName(), algorithm, testCase.getSiteCounter(), testCase.getKeyPurpose(),
|
||||
testCase.getKeyContext(), resultType, password );
|
||||
|
Loading…
Reference in New Issue
Block a user