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:
		@@ -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
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user