Move identicon and toID to mpw native.
Clean out all unused Java MPAlgorithm stuff. Fix master password entries stuck in memory.
This commit is contained in:
parent
ff9596aef0
commit
1c3ea3826f
@ -7,8 +7,6 @@
|
|||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
#undef com_lyndir_masterpassword_MPAlgorithm_Version_AES_BLOCKSIZE
|
|
||||||
#define com_lyndir_masterpassword_MPAlgorithm_Version_AES_BLOCKSIZE 128L
|
|
||||||
/*
|
/*
|
||||||
* Class: com_lyndir_masterpassword_MPAlgorithm_Version
|
* Class: com_lyndir_masterpassword_MPAlgorithm_Version
|
||||||
* Method: _masterKey
|
* Method: _masterKey
|
||||||
@ -41,6 +39,22 @@ JNIEXPORT jstring JNICALL Java_com_lyndir_masterpassword_MPAlgorithm_00024Versio
|
|||||||
JNIEXPORT jstring JNICALL Java_com_lyndir_masterpassword_MPAlgorithm_00024Version__1siteState
|
JNIEXPORT jstring JNICALL Java_com_lyndir_masterpassword_MPAlgorithm_00024Version__1siteState
|
||||||
(JNIEnv *, jobject, jbyteArray, jbyteArray, jstring, jlong, jint, jstring, jint, jstring, jint);
|
(JNIEnv *, jobject, jbyteArray, jbyteArray, jstring, jlong, jint, jstring, jint, jstring, jint);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Class: com_lyndir_masterpassword_MPAlgorithm_Version
|
||||||
|
* Method: _identicon
|
||||||
|
* Signature: (Ljava/lang/String;[B)Lcom/lyndir/masterpassword/MPIdenticon;
|
||||||
|
*/
|
||||||
|
JNIEXPORT jobject JNICALL Java_com_lyndir_masterpassword_MPAlgorithm_00024Version__1identicon
|
||||||
|
(JNIEnv *, jobject, jstring, jbyteArray);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Class: com_lyndir_masterpassword_MPAlgorithm_Version
|
||||||
|
* Method: _toID
|
||||||
|
* Signature: ([B)Ljava/lang/String;
|
||||||
|
*/
|
||||||
|
JNIEXPORT jstring JNICALL Java_com_lyndir_masterpassword_MPAlgorithm_00024Version__1toID
|
||||||
|
(JNIEnv *, jobject, jbyteArray);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -20,23 +20,23 @@ bool mpw_log_sink_jni(const MPLogEvent *record) {
|
|||||||
|
|
||||||
if (logger && (*env)->PushLocalFrame( env, 16 ) == OK) {
|
if (logger && (*env)->PushLocalFrame( env, 16 ) == OK) {
|
||||||
jmethodID method = NULL;
|
jmethodID method = NULL;
|
||||||
jclass Logger = (*env)->GetObjectClass( env, logger );
|
jclass cLogger = (*env)->GetObjectClass( env, logger );
|
||||||
switch (record->level) {
|
switch (record->level) {
|
||||||
case LogLevelTrace:
|
case LogLevelTrace:
|
||||||
method = (*env)->GetMethodID( env, Logger, "trace", "(Ljava/lang/String;)V" );
|
method = (*env)->GetMethodID( env, cLogger, "trace", "(Ljava/lang/String;)V" );
|
||||||
break;
|
break;
|
||||||
case LogLevelDebug:
|
case LogLevelDebug:
|
||||||
method = (*env)->GetMethodID( env, Logger, "debug", "(Ljava/lang/String;)V" );
|
method = (*env)->GetMethodID( env, cLogger, "debug", "(Ljava/lang/String;)V" );
|
||||||
break;
|
break;
|
||||||
case LogLevelInfo:
|
case LogLevelInfo:
|
||||||
method = (*env)->GetMethodID( env, Logger, "info", "(Ljava/lang/String;)V" );
|
method = (*env)->GetMethodID( env, cLogger, "info", "(Ljava/lang/String;)V" );
|
||||||
break;
|
break;
|
||||||
case LogLevelWarning:
|
case LogLevelWarning:
|
||||||
method = (*env)->GetMethodID( env, Logger, "warn", "(Ljava/lang/String;)V" );
|
method = (*env)->GetMethodID( env, cLogger, "warn", "(Ljava/lang/String;)V" );
|
||||||
break;
|
break;
|
||||||
case LogLevelError:
|
case LogLevelError:
|
||||||
case LogLevelFatal:
|
case LogLevelFatal:
|
||||||
method = (*env)->GetMethodID( env, Logger, "error", "(Ljava/lang/String;)V" );
|
method = (*env)->GetMethodID( env, cLogger, "error", "(Ljava/lang/String;)V" );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,30 +57,40 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
|
|||||||
if ((*vm)->GetEnv( _vm = vm, (void **)&env, JNI_VERSION_1_6 ) != JNI_OK)
|
if ((*vm)->GetEnv( _vm = vm, (void **)&env, JNI_VERSION_1_6 ) != JNI_OK)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
jclass LoggerFactory = (*env)->FindClass( env, "org/slf4j/LoggerFactory" );
|
do {
|
||||||
jmethodID method = (*env)->GetStaticMethodID( env, LoggerFactory, "getLogger", "(Ljava/lang/String;)Lorg/slf4j/Logger;" );
|
jclass cLoggerFactory = (*env)->FindClass( env, "org/slf4j/LoggerFactory" );
|
||||||
jstring name = (*env)->NewStringUTF( env, "com.lyndir.masterpassword.algorithm" );
|
if (!cLoggerFactory)
|
||||||
if (LoggerFactory && method && name)
|
break;
|
||||||
logger = (*env)->NewGlobalRef( env, (*env)->CallStaticObjectMethod( env, LoggerFactory, method, name ) );
|
jmethodID method = (*env)->GetStaticMethodID( env, cLoggerFactory, "getLogger", "(Ljava/lang/String;)Lorg/slf4j/Logger;" );
|
||||||
else
|
if (!method)
|
||||||
|
break;
|
||||||
|
jstring name = (*env)->NewStringUTF( env, "com.lyndir.masterpassword.algorithm" );
|
||||||
|
if (!name)
|
||||||
|
break;
|
||||||
|
logger = (*env)->NewGlobalRef( env, (*env)->CallStaticObjectMethod( env, cLoggerFactory, method, name ) );
|
||||||
|
if (!logger)
|
||||||
|
break;
|
||||||
|
|
||||||
|
jclass cLogger = (*env)->GetObjectClass( env, logger );
|
||||||
|
if ((*env)->CallBooleanMethod( env, logger, (*env)->GetMethodID( env, cLogger, "isTraceEnabled", "()Z" ) ))
|
||||||
|
mpw_verbosity = LogLevelTrace;
|
||||||
|
else if ((*env)->CallBooleanMethod( env, logger, (*env)->GetMethodID( env, cLogger, "isDebugEnabled", "()Z" ) ))
|
||||||
|
mpw_verbosity = LogLevelDebug;
|
||||||
|
else if ((*env)->CallBooleanMethod( env, logger, (*env)->GetMethodID( env, cLogger, "isInfoEnabled", "()Z" ) ))
|
||||||
|
mpw_verbosity = LogLevelInfo;
|
||||||
|
else if ((*env)->CallBooleanMethod( env, logger, (*env)->GetMethodID( env, cLogger, "isWarnEnabled", "()Z" ) ))
|
||||||
|
mpw_verbosity = LogLevelWarning;
|
||||||
|
else if ((*env)->CallBooleanMethod( env, logger, (*env)->GetMethodID( env, cLogger, "isErrorEnabled", "()Z" ) ))
|
||||||
|
mpw_verbosity = LogLevelError;
|
||||||
|
else
|
||||||
|
mpw_verbosity = LogLevelFatal;
|
||||||
|
|
||||||
|
mpw_log_sink_register( &mpw_log_sink_jni );
|
||||||
|
} while (false);
|
||||||
|
|
||||||
|
if (!logger)
|
||||||
wrn( "Couldn't initialize JNI logger." );
|
wrn( "Couldn't initialize JNI logger." );
|
||||||
|
|
||||||
jclass Logger = (*env)->GetObjectClass( env, logger );
|
|
||||||
if ((*env)->CallBooleanMethod( env, logger, (*env)->GetMethodID( env, Logger, "isTraceEnabled", "()Z" ) ))
|
|
||||||
mpw_verbosity = LogLevelTrace;
|
|
||||||
else if ((*env)->CallBooleanMethod( env, logger, (*env)->GetMethodID( env, Logger, "isDebugEnabled", "()Z" ) ))
|
|
||||||
mpw_verbosity = LogLevelDebug;
|
|
||||||
else if ((*env)->CallBooleanMethod( env, logger, (*env)->GetMethodID( env, Logger, "isInfoEnabled", "()Z" ) ))
|
|
||||||
mpw_verbosity = LogLevelInfo;
|
|
||||||
else if ((*env)->CallBooleanMethod( env, logger, (*env)->GetMethodID( env, Logger, "isWarnEnabled", "()Z" ) ))
|
|
||||||
mpw_verbosity = LogLevelWarning;
|
|
||||||
else if ((*env)->CallBooleanMethod( env, logger, (*env)->GetMethodID( env, Logger, "isErrorEnabled", "()Z" ) ))
|
|
||||||
mpw_verbosity = LogLevelError;
|
|
||||||
else
|
|
||||||
mpw_verbosity = LogLevelFatal;
|
|
||||||
|
|
||||||
mpw_log_sink_register( &mpw_log_sink_jni );
|
|
||||||
|
|
||||||
return JNI_VERSION_1_6;
|
return JNI_VERSION_1_6;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,3 +216,51 @@ JNIEXPORT jstring JNICALL Java_com_lyndir_masterpassword_MPAlgorithm_00024Versio
|
|||||||
|
|
||||||
return siteState;
|
return siteState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* native MPIdenticon _identicon(final String fullName, final byte[] masterPassword) */
|
||||||
|
JNIEXPORT jobject JNICALL Java_com_lyndir_masterpassword_MPAlgorithm_00024Version__1identicon(JNIEnv *env, jobject obj,
|
||||||
|
jstring fullName, jbyteArray masterPassword) {
|
||||||
|
|
||||||
|
if (!fullName || !masterPassword)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
const char *fullNameString = (*env)->GetStringUTFChars( env, fullName, NULL );
|
||||||
|
jbyte *masterPasswordString = (*env)->GetByteArrayElements( env, masterPassword, NULL );
|
||||||
|
|
||||||
|
MPIdenticon identicon = mpw_identicon( fullNameString, (char *)masterPasswordString );
|
||||||
|
(*env)->ReleaseStringUTFChars( env, fullName, fullNameString );
|
||||||
|
(*env)->ReleaseByteArrayElements( env, masterPassword, masterPasswordString, JNI_ABORT );
|
||||||
|
if (identicon.color == MPIdenticonColorUnset)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
jclass cMPIdenticonColor = (*env)->FindClass( env, "com/lyndir/masterpassword/MPIdenticon$Color" );
|
||||||
|
if (!cMPIdenticonColor)
|
||||||
|
return NULL;
|
||||||
|
jmethodID method = (*env)->GetStaticMethodID( env, cMPIdenticonColor, "values", "()[Lcom/lyndir/masterpassword/MPIdenticon$Color;" );
|
||||||
|
if (!method)
|
||||||
|
return NULL;
|
||||||
|
jobject values = (*env)->CallStaticObjectMethod( env, cMPIdenticonColor, method );
|
||||||
|
if (!values)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
jclass cMPIdenticon = (*env)->FindClass( env, "com/lyndir/masterpassword/MPIdenticon" );
|
||||||
|
if (!cMPIdenticon)
|
||||||
|
return NULL;
|
||||||
|
jmethodID init = (*env)->GetMethodID( env, cMPIdenticon, "<init>", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/lyndir/masterpassword/MPIdenticon$Color;)V" );
|
||||||
|
if (!init)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return (*env)->NewObject( env, cMPIdenticon, init, fullName,
|
||||||
|
(*env)->NewStringUTF( env, identicon.leftArm ),
|
||||||
|
(*env)->NewStringUTF( env, identicon.body ),
|
||||||
|
(*env)->NewStringUTF( env, identicon.rightArm ),
|
||||||
|
(*env)->NewStringUTF( env, identicon.accessory ),
|
||||||
|
(*env)->GetObjectArrayElement( env, values, identicon.color ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/* native String _toID(final byte[] buffer) */
|
||||||
|
JNIEXPORT jstring JNICALL Java_com_lyndir_masterpassword_MPAlgorithm_00024Version__1toID(JNIEnv *env, jobject obj,
|
||||||
|
jbyteArray buffer) {
|
||||||
|
|
||||||
|
return (*env)->NewStringUTF( env, mpw_id_buf( (*env)->GetByteArrayElements( env, buffer, NULL ), (*env)->GetArrayLength( env, buffer ) ) );
|
||||||
|
}
|
||||||
|
@ -120,18 +120,17 @@ typedef mpw_enum ( uint32_t, MPCounterValue ) {
|
|||||||
|
|
||||||
/** These colours are compatible with the original ANSI SGR. */
|
/** These colours are compatible with the original ANSI SGR. */
|
||||||
typedef mpw_enum( uint8_t, MPIdenticonColor ) {
|
typedef mpw_enum( uint8_t, MPIdenticonColor ) {
|
||||||
MPIdenticonColorBlack,
|
MPIdenticonColorUnset,
|
||||||
MPIdenticonColorRed,
|
MPIdenticonColorRed,
|
||||||
MPIdenticonColorGreen,
|
MPIdenticonColorGreen,
|
||||||
MPIdenticonColorYellow,
|
MPIdenticonColorYellow,
|
||||||
MPIdenticonColorBlue,
|
MPIdenticonColorBlue,
|
||||||
MPIdenticonColorMagenta,
|
MPIdenticonColorMagenta,
|
||||||
MPIdenticonColorCyan,
|
MPIdenticonColorCyan,
|
||||||
MPIdenticonColorWhite,
|
MPIdenticonColorMono,
|
||||||
|
|
||||||
MPIdenticonColorUnset = MPIdenticonColorBlack,
|
|
||||||
MPIdenticonColorFirst = MPIdenticonColorRed,
|
MPIdenticonColorFirst = MPIdenticonColorRed,
|
||||||
MPIdenticonColorLast = MPIdenticonColorWhite,
|
MPIdenticonColorLast = MPIdenticonColorMono,
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -87,6 +87,29 @@ public interface MPAlgorithm {
|
|||||||
MPKeyPurpose keyPurpose, @Nullable String keyContext,
|
MPKeyPurpose keyPurpose, @Nullable String keyContext,
|
||||||
MPResultType resultType, String resultParam);
|
MPResultType resultType, String resultParam);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derive an identicon that represents the user's identity in a visually recognizable way.
|
||||||
|
*
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
|
MPIdenticon identicon(final String fullName, final char[] masterPassword);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a fingerprint for a message.
|
||||||
|
*/
|
||||||
|
String toID(final String string);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a fingerprint for a char buffer.
|
||||||
|
*/
|
||||||
|
String toID(final char[] message);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a fingerprint for a byte buffer.
|
||||||
|
*/
|
||||||
|
String toID(final byte[] buffer);
|
||||||
|
|
||||||
// Configuration
|
// Configuration
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -125,69 +148,6 @@ public interface MPAlgorithm {
|
|||||||
@Nonnull
|
@Nonnull
|
||||||
Charset mpw_charset();
|
Charset mpw_charset();
|
||||||
|
|
||||||
/**
|
|
||||||
* mpw: Platform-agnostic byte order.
|
|
||||||
*/
|
|
||||||
@Nonnull
|
|
||||||
ByteOrder mpw_byteOrder();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* mpw: Key ID hash.
|
|
||||||
*/
|
|
||||||
@Nonnull
|
|
||||||
MessageDigests mpw_hash();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* mpw: Site digest.
|
|
||||||
*/
|
|
||||||
@Nonnull
|
|
||||||
MessageAuthenticationDigests mpw_digest();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* mpw: Master key size (byte).
|
|
||||||
*/
|
|
||||||
int mpw_dkLen();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* mpw: Minimum size for derived keys (bit).
|
|
||||||
*/
|
|
||||||
int mpw_keySize_min();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* mpw: Maximum size for derived keys (bit).
|
|
||||||
*/
|
|
||||||
int mpw_keySize_max();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* mpw: validity for the time-based rolling counter (s).
|
|
||||||
*/
|
|
||||||
long mpw_otp_window();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* scrypt: CPU cost parameter.
|
|
||||||
*/
|
|
||||||
int scrypt_N();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* scrypt: Memory cost parameter.
|
|
||||||
*/
|
|
||||||
int scrypt_r();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* scrypt: Parallelization parameter.
|
|
||||||
*/
|
|
||||||
int scrypt_p();
|
|
||||||
|
|
||||||
// Utilities
|
|
||||||
|
|
||||||
byte[] toBytes(final int number);
|
|
||||||
|
|
||||||
byte[] toBytes(final UnsignedInteger number);
|
|
||||||
|
|
||||||
byte[] toBytes(final char[] characters);
|
|
||||||
|
|
||||||
byte[] toID(final byte[] bytes);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The algorithm iterations.
|
* The algorithm iterations.
|
||||||
*/
|
*/
|
||||||
@ -222,10 +182,6 @@ public interface MPAlgorithm {
|
|||||||
|
|
||||||
public static final Version CURRENT = V3;
|
public static final Version CURRENT = V3;
|
||||||
|
|
||||||
@SuppressWarnings("HardcodedFileSeparator")
|
|
||||||
private static final String AES_TRANSFORMATION = "AES/CBC/PKCS5Padding";
|
|
||||||
private static final int AES_BLOCKSIZE = 128 /* bit */;
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
if (!Native.load( MPAlgorithm.class, "mpw" ))
|
if (!Native.load( MPAlgorithm.class, "mpw" ))
|
||||||
Logger.get( MPAlgorithm.class ).err( "Native mpw library unavailable." );
|
Logger.get( MPAlgorithm.class ).err( "Native mpw library unavailable." );
|
||||||
@ -321,6 +277,70 @@ public interface MPAlgorithm {
|
|||||||
final int keyPurpose, @Nullable final String keyContext,
|
final int keyPurpose, @Nullable final String keyContext,
|
||||||
final int resultType, final String resultParam, final int algorithmVersion);
|
final int resultType, final String resultParam, final int algorithmVersion);
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public MPIdenticon identicon(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 _identicon( fullName, masterPasswordBytes );
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
Arrays.fill( masterPasswordBytes, (byte) 0 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
protected native MPIdenticon _identicon(final String fullName, final byte[] masterPassword);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toID(final String message) {
|
||||||
|
return toID( message.toCharArray() );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toID(final char[] message) {
|
||||||
|
// Create a memory-safe NUL-terminated UTF-8 C-string byte array variant of masterPassword.
|
||||||
|
CharsetEncoder encoder = mpw_charset().newEncoder();
|
||||||
|
byte[] messageBytes = new byte[(int) (message.length * (double) encoder.maxBytesPerChar()) + 1];
|
||||||
|
try {
|
||||||
|
Arrays.fill( messageBytes, (byte) 0 );
|
||||||
|
ByteBuffer messageBuffer = ByteBuffer.wrap( messageBytes );
|
||||||
|
|
||||||
|
CoderResult result = encoder.encode( CharBuffer.wrap( message ), messageBuffer, true );
|
||||||
|
if (result.isError())
|
||||||
|
throw new IllegalStateException( result.toString() );
|
||||||
|
result = encoder.flush( messageBuffer );
|
||||||
|
if (result.isError())
|
||||||
|
throw new IllegalStateException( result.toString() );
|
||||||
|
|
||||||
|
return toID( messageBytes );
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
Arrays.fill( messageBytes, (byte) 0 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toID(final byte[] buffer) {
|
||||||
|
return _toID( buffer );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
protected native String _toID(final byte[] buffer);
|
||||||
|
|
||||||
// Configuration
|
// Configuration
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@ -358,93 +378,5 @@ public interface MPAlgorithm {
|
|||||||
public Charset mpw_charset() {
|
public Charset mpw_charset() {
|
||||||
return Charsets.UTF_8;
|
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 );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,14 +20,8 @@ package com.lyndir.masterpassword;
|
|||||||
|
|
||||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
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 com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||||
import java.nio.*;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
|
||||||
@ -39,43 +33,23 @@ public class MPIdenticon {
|
|||||||
@SuppressWarnings("UnusedDeclaration")
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
private static final Logger logger = Logger.get( MPIdenticon.class );
|
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 String fullName;
|
||||||
|
private final String leftArm;
|
||||||
|
private final String body;
|
||||||
|
private final String rightArm;
|
||||||
|
private final String accessory;
|
||||||
private final Color color;
|
private final Color color;
|
||||||
private final String text;
|
|
||||||
|
|
||||||
public MPIdenticon(final String fullName, final String masterPassword) {
|
|
||||||
this( fullName, masterPassword.toCharArray() );
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressFBWarnings("CLI_CONSTANT_LIST_INDEX")
|
@SuppressFBWarnings("CLI_CONSTANT_LIST_INDEX")
|
||||||
@SuppressWarnings("MethodCanBeVariableArityMethod")
|
@SuppressWarnings("MethodCanBeVariableArityMethod")
|
||||||
public MPIdenticon(final String fullName, final char[] masterPassword) {
|
public MPIdenticon(final String fullName, final String leftArm, final String body, final String rightArm, final String accessory,
|
||||||
|
final Color color) {
|
||||||
this.fullName = fullName;
|
this.fullName = fullName;
|
||||||
|
this.leftArm = leftArm;
|
||||||
byte[] masterPasswordBytes = charset.encode( CharBuffer.wrap( masterPassword ) ).array();
|
this.body = body;
|
||||||
ByteBuffer identiconSeedBytes = ByteBuffer.wrap(
|
this.rightArm = rightArm;
|
||||||
MessageAuthenticationDigests.HmacSHA256.of( masterPasswordBytes, fullName.getBytes( charset ) ) );
|
this.accessory = accessory;
|
||||||
Arrays.fill( masterPasswordBytes, (byte) 0 );
|
this.color = color;
|
||||||
|
|
||||||
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() {
|
public String getFullName() {
|
||||||
@ -83,11 +57,11 @@ public class MPIdenticon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getText() {
|
public String getText() {
|
||||||
return text;
|
return strf( "%s%s%s%s", this.leftArm, this.body, this.rightArm, this.accessory );
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getHTML() {
|
public String getHTML() {
|
||||||
return strf( "<span style='color: %s'>%s</span>", color.getCSS(), text );
|
return strf( "<span style='color: %s'>%s</span>", color.getCSS(), getText() );
|
||||||
}
|
}
|
||||||
|
|
||||||
public Color getColor() {
|
public Color getColor() {
|
||||||
@ -95,6 +69,12 @@ public class MPIdenticon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public enum Color {
|
public enum Color {
|
||||||
|
UNSET {
|
||||||
|
@Override
|
||||||
|
public String getCSS() {
|
||||||
|
return "inherit";
|
||||||
|
}
|
||||||
|
},
|
||||||
RED,
|
RED,
|
||||||
GREEN,
|
GREEN,
|
||||||
YELLOW,
|
YELLOW,
|
||||||
|
@ -55,6 +55,18 @@ public class MPMasterKey {
|
|||||||
Arrays.fill( masterPassword, (char) 0 );
|
Arrays.fill( masterPassword, (char) 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void finalize()
|
||||||
|
throws Throwable {
|
||||||
|
|
||||||
|
if (isValid()) {
|
||||||
|
logger.wrn( "A master key for %s was abandoned without being invalidated.", getFullName() );
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
super.finalize();
|
||||||
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public String getFullName() {
|
public String getFullName() {
|
||||||
|
|
||||||
@ -67,7 +79,7 @@ public class MPMasterKey {
|
|||||||
* @throws MPKeyUnavailableException {@link #invalidate()} has been called on this object.
|
* @throws MPKeyUnavailableException {@link #invalidate()} has been called on this object.
|
||||||
*/
|
*/
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public byte[] getKeyID(final MPAlgorithm algorithm)
|
public String getKeyID(final MPAlgorithm algorithm)
|
||||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||||
|
|
||||||
return algorithm.toID( masterKey( algorithm ) );
|
return algorithm.toID( masterKey( algorithm ) );
|
||||||
@ -98,11 +110,6 @@ public class MPMasterKey {
|
|||||||
|
|
||||||
byte[] masterKey = keyByVersion.get( algorithm.version() );
|
byte[] masterKey = keyByVersion.get( algorithm.version() );
|
||||||
if (masterKey == null) {
|
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 ) );
|
keyByVersion.put( algorithm.version(), masterKey = algorithm.masterKey( fullName, masterPassword ) );
|
||||||
}
|
}
|
||||||
if (masterKey == null)
|
if (masterKey == null)
|
||||||
@ -118,13 +125,6 @@ public class MPMasterKey {
|
|||||||
Preconditions.checkArgument( !siteName.isEmpty() );
|
Preconditions.checkArgument( !siteName.isEmpty() );
|
||||||
|
|
||||||
byte[] masterKey = masterKey( algorithm );
|
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 );
|
byte[] siteKey = algorithm.siteKey( masterKey, siteName, siteCounter, keyPurpose, keyContext );
|
||||||
if (siteKey == null)
|
if (siteKey == null)
|
||||||
throw new MPAlgorithmException( "Could not derive site key." );
|
throw new MPAlgorithmException( "Could not derive site key." );
|
||||||
@ -161,10 +161,6 @@ public class MPMasterKey {
|
|||||||
byte[] masterKey = masterKey( algorithm );
|
byte[] masterKey = masterKey( algorithm );
|
||||||
byte[] siteKey = siteKey( siteName, algorithm, siteCounter, keyPurpose, keyContext );
|
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(
|
String siteResult = algorithm.siteResult(
|
||||||
masterKey, siteKey, siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam );
|
masterKey, siteKey, siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam );
|
||||||
if (siteResult == null)
|
if (siteResult == null)
|
||||||
@ -199,10 +195,6 @@ public class MPMasterKey {
|
|||||||
byte[] masterKey = masterKey( algorithm );
|
byte[] masterKey = masterKey( algorithm );
|
||||||
byte[] siteKey = siteKey( siteName, algorithm, siteCounter, keyPurpose, keyContext );
|
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(
|
String siteState = algorithm.siteState(
|
||||||
masterKey, siteKey, siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam );
|
masterKey, siteKey, siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam );
|
||||||
if (siteState == null)
|
if (siteState == null)
|
||||||
|
@ -35,7 +35,7 @@ public class MPIncognitoUser extends MPBasicUser<MPIncognitoSite> {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public byte[] getKeyID() {
|
public String getKeyID() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ import javax.swing.border.Border;
|
|||||||
import javax.swing.border.CompoundBorder;
|
import javax.swing.border.CompoundBorder;
|
||||||
import javax.swing.event.HyperlinkEvent;
|
import javax.swing.event.HyperlinkEvent;
|
||||||
import javax.swing.text.*;
|
import javax.swing.text.*;
|
||||||
|
import javax.swing.undo.UndoableEdit;
|
||||||
import org.jetbrains.annotations.NonNls;
|
import org.jetbrains.annotations.NonNls;
|
||||||
|
|
||||||
|
|
||||||
@ -207,7 +208,13 @@ public abstract class Components {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static JPasswordField passwordField() {
|
public static JPasswordField passwordField() {
|
||||||
return new JPasswordField() {
|
return new JPasswordField( new PlainDocument( new GapContent() {
|
||||||
|
@Override
|
||||||
|
public String getString(final int where, final int len)
|
||||||
|
throws BadLocationException {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
} ), null, 0 ) {
|
||||||
{
|
{
|
||||||
setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
|
setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
|
||||||
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) );
|
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) );
|
||||||
|
@ -182,7 +182,7 @@ public abstract class Res {
|
|||||||
|
|
||||||
public static final class Fonts {
|
public static final class Fonts {
|
||||||
|
|
||||||
public Font emoticonsFont(final int size) {
|
public Font identiconFont(final int size) {
|
||||||
return MPFont.emoticonsRegular.get( size );
|
return MPFont.emoticonsRegular.get( size );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,7 +317,7 @@ public class UserContentPanel extends JPanel implements State.Listener, MPUser.L
|
|||||||
add( Components.strut() );
|
add( Components.strut() );
|
||||||
|
|
||||||
add( identiconLabel = Components.label( SwingConstants.CENTER ) );
|
add( identiconLabel = Components.label( SwingConstants.CENTER ) );
|
||||||
identiconLabel.setFont( Res.fonts().emoticonsFont( Components.TEXT_SIZE_CONTROL ) );
|
identiconLabel.setFont( Res.fonts().identiconFont( Components.TEXT_SIZE_CONTROL ) );
|
||||||
add( Box.createGlue() );
|
add( Box.createGlue() );
|
||||||
|
|
||||||
add( Components.label( "Master Password:" ) );
|
add( Components.label( "Master Password:" ) );
|
||||||
@ -330,6 +330,15 @@ public class UserContentPanel extends JPanel implements State.Listener, MPUser.L
|
|||||||
add( Box.createGlue() );
|
add( Box.createGlue() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeNotify() {
|
||||||
|
char[] password = masterPasswordField.getPassword();
|
||||||
|
Arrays.fill( password, (char) 0 );
|
||||||
|
masterPasswordField.setText( new String( password ) );
|
||||||
|
|
||||||
|
super.removeNotify();
|
||||||
|
}
|
||||||
|
|
||||||
private void exportUser() {
|
private void exportUser() {
|
||||||
MPFileUser fileUser = (user instanceof MPFileUser)? (MPFileUser) user: null;
|
MPFileUser fileUser = (user instanceof MPFileUser)? (MPFileUser) user: null;
|
||||||
if (fileUser == null)
|
if (fileUser == null)
|
||||||
@ -449,7 +458,8 @@ public class UserContentPanel extends JPanel implements State.Listener, MPUser.L
|
|||||||
private void updateIdenticon() {
|
private void updateIdenticon() {
|
||||||
char[] masterPassword = masterPasswordField.getPassword();
|
char[] masterPassword = masterPasswordField.getPassword();
|
||||||
MPIdenticon identicon = ((masterPassword != null) && (masterPassword.length > 0))?
|
MPIdenticon identicon = ((masterPassword != null) && (masterPassword.length > 0))?
|
||||||
new MPIdenticon( user.getFullName(), masterPassword ): null;
|
user.getAlgorithm().identicon( user.getFullName(), masterPassword ): null;
|
||||||
|
Arrays.fill( masterPassword, (char) 0 );
|
||||||
|
|
||||||
Res.ui( () -> {
|
Res.ui( () -> {
|
||||||
if (identicon != null) {
|
if (identicon != null) {
|
||||||
@ -779,7 +789,7 @@ public class UserContentPanel extends JPanel implements State.Listener, MPUser.L
|
|||||||
typeModel.selection( MPResultType.DeriveKey, t -> {
|
typeModel.selection( MPResultType.DeriveKey, t -> {
|
||||||
switch (t) {
|
switch (t) {
|
||||||
case DeriveKey:
|
case DeriveKey:
|
||||||
stateModel.setText( Integer.toString( site.getAlgorithm().mpw_keySize_min() ) );
|
stateModel.setText( "128" );
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -49,10 +49,7 @@ public interface MPUser<S extends MPSite<?>> extends Comparable<MPUser<?>> {
|
|||||||
void setAlgorithm(MPAlgorithm algorithm);
|
void setAlgorithm(MPAlgorithm algorithm);
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
byte[] getKeyID();
|
String getKeyID();
|
||||||
|
|
||||||
@Nullable
|
|
||||||
String exportKeyID();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs an authentication attempt against the keyID for this user.
|
* Performs an authentication attempt against the keyID for this user.
|
||||||
|
@ -100,7 +100,7 @@ public abstract class MPBasicUser<S extends MPBasicSite<?, ?>> extends Changeabl
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public byte[] getKeyID() {
|
public String getKeyID() {
|
||||||
try {
|
try {
|
||||||
if (isMasterKeyAvailable())
|
if (isMasterKeyAvailable())
|
||||||
return getMasterKey().getKeyID( getAlgorithm() );
|
return getMasterKey().getKeyID( getAlgorithm() );
|
||||||
@ -112,12 +112,6 @@ public abstract class MPBasicUser<S extends MPBasicSite<?, ?>> extends Changeabl
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public String exportKeyID() {
|
|
||||||
return CodeUtils.encodeHex( getKeyID() );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void authenticate(final char[] masterPassword)
|
public void authenticate(final char[] masterPassword)
|
||||||
throws MPIncorrectMasterPasswordException, MPAlgorithmException {
|
throws MPIncorrectMasterPasswordException, MPAlgorithmException {
|
||||||
@ -136,8 +130,8 @@ public abstract class MPBasicUser<S extends MPBasicSite<?, ?>> extends Changeabl
|
|||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"Master key (for " + masterKey.getFullName() + ") is not for this user (" + getFullName() + ")." );
|
"Master key (for " + masterKey.getFullName() + ") is not for this user (" + getFullName() + ")." );
|
||||||
|
|
||||||
byte[] keyID = getKeyID();
|
String keyID = getKeyID();
|
||||||
if ((keyID != null) && !Arrays.equals( masterKey.getKeyID( getAlgorithm() ), keyID ))
|
if (keyID != null && !keyID.equalsIgnoreCase( masterKey.getKeyID( getAlgorithm() ) ))
|
||||||
throw new MPIncorrectMasterPasswordException( this );
|
throw new MPIncorrectMasterPasswordException( this );
|
||||||
|
|
||||||
this.masterKey = masterKey;
|
this.masterKey = masterKey;
|
||||||
|
@ -39,7 +39,7 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
|||||||
private static final Logger logger = Logger.get( MPFileUser.class );
|
private static final Logger logger = Logger.get( MPFileUser.class );
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private byte[] keyID;
|
private String keyID;
|
||||||
private File file;
|
private File file;
|
||||||
private MPMarshalFormat format;
|
private MPMarshalFormat format;
|
||||||
private MPMarshaller.ContentMode contentMode;
|
private MPMarshaller.ContentMode contentMode;
|
||||||
@ -62,18 +62,18 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
|||||||
this( fullName, null, MPAlgorithm.Version.CURRENT, location );
|
this( fullName, null, MPAlgorithm.Version.CURRENT, location );
|
||||||
}
|
}
|
||||||
|
|
||||||
public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPAlgorithm algorithm, final File location) {
|
public MPFileUser(final String fullName, @Nullable final String keyID, final MPAlgorithm algorithm, final File location) {
|
||||||
this( fullName, keyID, algorithm, 0, null, new Instant(), false,
|
this( fullName, keyID, algorithm, 0, null, new Instant(), false,
|
||||||
MPMarshaller.ContentMode.PROTECTED, MPMarshalFormat.DEFAULT, location );
|
MPMarshaller.ContentMode.PROTECTED, MPMarshalFormat.DEFAULT, location );
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressFBWarnings("PATH_TRAVERSAL_IN")
|
@SuppressFBWarnings("PATH_TRAVERSAL_IN")
|
||||||
public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPAlgorithm algorithm, final int avatar,
|
public MPFileUser(final String fullName, @Nullable final String keyID, final MPAlgorithm algorithm, final int avatar,
|
||||||
@Nullable final MPResultType defaultType, final ReadableInstant lastUsed, final boolean hidePasswords,
|
@Nullable final MPResultType defaultType, final ReadableInstant lastUsed, final boolean hidePasswords,
|
||||||
final MPMarshaller.ContentMode contentMode, final MPMarshalFormat format, final File location) {
|
final MPMarshaller.ContentMode contentMode, final MPMarshalFormat format, final File location) {
|
||||||
super( avatar, fullName, algorithm );
|
super( avatar, fullName, algorithm );
|
||||||
|
|
||||||
this.keyID = (keyID != null)? keyID.clone(): null;
|
this.keyID = keyID;
|
||||||
this.lastUsed = lastUsed;
|
this.lastUsed = lastUsed;
|
||||||
this.preferences = new MPFileUserPreferences( this, defaultType, hidePasswords );
|
this.preferences = new MPFileUserPreferences( this, defaultType, hidePasswords );
|
||||||
this.format = format;
|
this.format = format;
|
||||||
@ -87,8 +87,8 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public byte[] getKeyID() {
|
public String getKeyID() {
|
||||||
return (keyID == null)? null: keyID.clone();
|
return keyID;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
|
@ -54,7 +54,7 @@ public class MPFlatMarshaller implements MPMarshaller {
|
|||||||
content.append( "# User Name: " ).append( user.getFullName() ).append( '\n' );
|
content.append( "# User Name: " ).append( user.getFullName() ).append( '\n' );
|
||||||
content.append( "# Full 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( "# Avatar: " ).append( user.getAvatar() ).append( '\n' );
|
||||||
content.append( "# Key ID: " ).append( user.exportKeyID() ).append( '\n' );
|
content.append( "# Key ID: " ).append( user.getKeyID() ).append( '\n' );
|
||||||
content.append( "# Algorithm: " ).append( user.getAlgorithm().version().toInt() ).append( '\n' );
|
content.append( "# Algorithm: " ).append( user.getAlgorithm().version().toInt() ).append( '\n' );
|
||||||
content.append( "# Default Type: " ).append( user.getPreferences().getDefaultType().getType() ).append( '\n' );
|
content.append( "# Default Type: " ).append( user.getPreferences().getDefaultType().getType() ).append( '\n' );
|
||||||
content.append( "# Passwords: " ).append( user.getContentMode().name() ).append( '\n' );
|
content.append( "# Passwords: " ).append( user.getContentMode().name() ).append( '\n' );
|
||||||
|
@ -51,7 +51,7 @@ public class MPFlatUnmarshaller implements MPUnmarshaller {
|
|||||||
public MPFileUser readUser(@Nonnull final File file)
|
public MPFileUser readUser(@Nonnull final File file)
|
||||||
throws IOException, MPMarshalException {
|
throws IOException, MPMarshalException {
|
||||||
try (Reader reader = new InputStreamReader( new FileInputStream( file ), Charsets.UTF_8 )) {
|
try (Reader reader = new InputStreamReader( new FileInputStream( file ), Charsets.UTF_8 )) {
|
||||||
byte[] keyID = null;
|
String keyID = null;
|
||||||
String fullName = null;
|
String fullName = null;
|
||||||
int mpVersion = 0, avatar = 0;
|
int mpVersion = 0, avatar = 0;
|
||||||
boolean clearContent = false, headerStarted = false;
|
boolean clearContent = false, headerStarted = false;
|
||||||
@ -84,7 +84,7 @@ public class MPFlatUnmarshaller implements MPUnmarshaller {
|
|||||||
if ("Full Name".equalsIgnoreCase( name ) || "User Name".equalsIgnoreCase( name ))
|
if ("Full Name".equalsIgnoreCase( name ) || "User Name".equalsIgnoreCase( name ))
|
||||||
fullName = value;
|
fullName = value;
|
||||||
else if ("Key ID".equalsIgnoreCase( name ))
|
else if ("Key ID".equalsIgnoreCase( name ))
|
||||||
keyID = CodeUtils.decodeHex( value );
|
keyID = value;
|
||||||
else if ("Algorithm".equalsIgnoreCase( name ))
|
else if ("Algorithm".equalsIgnoreCase( name ))
|
||||||
mpVersion = ConversionUtils.toIntegerNN( value );
|
mpVersion = ConversionUtils.toIntegerNN( value );
|
||||||
else if ("Avatar".equalsIgnoreCase( name ))
|
else if ("Avatar".equalsIgnoreCase( name ))
|
||||||
|
@ -61,7 +61,7 @@ public class MPJSONFile extends MPJSONAnyObject {
|
|||||||
user.avatar = modelUser.getAvatar();
|
user.avatar = modelUser.getAvatar();
|
||||||
user.full_name = modelUser.getFullName();
|
user.full_name = modelUser.getFullName();
|
||||||
user.last_used = MPModelConstants.dateTimeFormatter.print( modelUser.getLastUsed() );
|
user.last_used = MPModelConstants.dateTimeFormatter.print( modelUser.getLastUsed() );
|
||||||
user.key_id = modelUser.exportKeyID();
|
user.key_id = modelUser.getKeyID();
|
||||||
user.algorithm = modelUser.getAlgorithm().version();
|
user.algorithm = modelUser.getAlgorithm().version();
|
||||||
user._ext_mpw = new User.Ext() {
|
user._ext_mpw = new User.Ext() {
|
||||||
{
|
{
|
||||||
@ -131,7 +131,7 @@ public class MPJSONFile extends MPJSONAnyObject {
|
|||||||
MPAlgorithm algorithm = ifNotNullElse( user.algorithm, MPAlgorithm.Version.CURRENT );
|
MPAlgorithm algorithm = ifNotNullElse( user.algorithm, MPAlgorithm.Version.CURRENT );
|
||||||
|
|
||||||
return new MPFileUser(
|
return new MPFileUser(
|
||||||
user.full_name, CodeUtils.decodeHex( user.key_id ), algorithm, user.avatar,
|
user.full_name, user.key_id, algorithm, user.avatar,
|
||||||
(user._ext_mpw != null)? user._ext_mpw.default_type: null,
|
(user._ext_mpw != null)? user._ext_mpw.default_type: null,
|
||||||
(user.last_used != null)? MPModelConstants.dateTimeFormatter.parseDateTime( user.last_used ): new Instant(),
|
(user.last_used != null)? MPModelConstants.dateTimeFormatter.parseDateTime( user.last_used ): new Instant(),
|
||||||
(user._ext_mpw != null) && user._ext_mpw.hide_passwords,
|
(user._ext_mpw != null) && user._ext_mpw.hide_passwords,
|
||||||
|
@ -53,9 +53,8 @@ public class MPMasterKeyTest {
|
|||||||
MPMasterKey masterKey = new MPMasterKey( testCase.getFullName(), masterPassword );
|
MPMasterKey masterKey = new MPMasterKey( testCase.getFullName(), masterPassword );
|
||||||
|
|
||||||
// Test key
|
// Test key
|
||||||
assertEquals(
|
assertTrue(
|
||||||
CodeUtils.encodeHex( masterKey.getKeyID( testCase.getAlgorithm() ) ),
|
testCase.getKeyID().equalsIgnoreCase( masterKey.getKeyID( testCase.getAlgorithm() ) ),
|
||||||
testCase.getKeyID(),
|
|
||||||
"[testMasterKey] keyID mismatch for test case: " + testCase );
|
"[testMasterKey] keyID mismatch for test case: " + testCase );
|
||||||
|
|
||||||
// Test invalidation
|
// Test invalidation
|
||||||
|
Loading…
Reference in New Issue
Block a user