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
 | 
			
		||||
extern "C" {
 | 
			
		||||
#endif
 | 
			
		||||
#undef com_lyndir_masterpassword_MPAlgorithm_Version_AES_BLOCKSIZE
 | 
			
		||||
#define com_lyndir_masterpassword_MPAlgorithm_Version_AES_BLOCKSIZE 128L
 | 
			
		||||
/*
 | 
			
		||||
 * Class:     com_lyndir_masterpassword_MPAlgorithm_Version
 | 
			
		||||
 * Method:    _masterKey
 | 
			
		||||
@@ -41,6 +39,22 @@ JNIEXPORT jstring JNICALL Java_com_lyndir_masterpassword_MPAlgorithm_00024Versio
 | 
			
		||||
JNIEXPORT jstring JNICALL Java_com_lyndir_masterpassword_MPAlgorithm_00024Version__1siteState
 | 
			
		||||
  (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
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -20,23 +20,23 @@ bool mpw_log_sink_jni(const MPLogEvent *record) {
 | 
			
		||||
 | 
			
		||||
    if (logger && (*env)->PushLocalFrame( env, 16 ) == OK) {
 | 
			
		||||
        jmethodID method = NULL;
 | 
			
		||||
        jclass Logger = (*env)->GetObjectClass( env, logger );
 | 
			
		||||
        jclass cLogger = (*env)->GetObjectClass( env, logger );
 | 
			
		||||
        switch (record->level) {
 | 
			
		||||
            case LogLevelTrace:
 | 
			
		||||
                method = (*env)->GetMethodID( env, Logger, "trace", "(Ljava/lang/String;)V" );
 | 
			
		||||
                method = (*env)->GetMethodID( env, cLogger, "trace", "(Ljava/lang/String;)V" );
 | 
			
		||||
                break;
 | 
			
		||||
            case LogLevelDebug:
 | 
			
		||||
                method = (*env)->GetMethodID( env, Logger, "debug", "(Ljava/lang/String;)V" );
 | 
			
		||||
                method = (*env)->GetMethodID( env, cLogger, "debug", "(Ljava/lang/String;)V" );
 | 
			
		||||
                break;
 | 
			
		||||
            case LogLevelInfo:
 | 
			
		||||
                method = (*env)->GetMethodID( env, Logger, "info", "(Ljava/lang/String;)V" );
 | 
			
		||||
                method = (*env)->GetMethodID( env, cLogger, "info", "(Ljava/lang/String;)V" );
 | 
			
		||||
                break;
 | 
			
		||||
            case LogLevelWarning:
 | 
			
		||||
                method = (*env)->GetMethodID( env, Logger, "warn", "(Ljava/lang/String;)V" );
 | 
			
		||||
                method = (*env)->GetMethodID( env, cLogger, "warn", "(Ljava/lang/String;)V" );
 | 
			
		||||
                break;
 | 
			
		||||
            case LogLevelError:
 | 
			
		||||
            case LogLevelFatal:
 | 
			
		||||
                method = (*env)->GetMethodID( env, Logger, "error", "(Ljava/lang/String;)V" );
 | 
			
		||||
                method = (*env)->GetMethodID( env, cLogger, "error", "(Ljava/lang/String;)V" );
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -57,29 +57,39 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
 | 
			
		||||
    if ((*vm)->GetEnv( _vm = vm, (void **)&env, JNI_VERSION_1_6 ) != JNI_OK)
 | 
			
		||||
        return -1;
 | 
			
		||||
 | 
			
		||||
    jclass LoggerFactory = (*env)->FindClass( env, "org/slf4j/LoggerFactory" );
 | 
			
		||||
    jmethodID method = (*env)->GetStaticMethodID( env, LoggerFactory, "getLogger", "(Ljava/lang/String;)Lorg/slf4j/Logger;" );
 | 
			
		||||
    do {
 | 
			
		||||
        jclass cLoggerFactory = (*env)->FindClass( env, "org/slf4j/LoggerFactory" );
 | 
			
		||||
        if (!cLoggerFactory)
 | 
			
		||||
            break;
 | 
			
		||||
        jmethodID method = (*env)->GetStaticMethodID( env, cLoggerFactory, "getLogger", "(Ljava/lang/String;)Lorg/slf4j/Logger;" );
 | 
			
		||||
        if (!method)
 | 
			
		||||
            break;
 | 
			
		||||
        jstring name = (*env)->NewStringUTF( env, "com.lyndir.masterpassword.algorithm" );
 | 
			
		||||
    if (LoggerFactory && method && name)
 | 
			
		||||
        logger = (*env)->NewGlobalRef( env, (*env)->CallStaticObjectMethod( env, LoggerFactory, method, name ) );
 | 
			
		||||
    else
 | 
			
		||||
        wrn( "Couldn't initialize JNI logger." );
 | 
			
		||||
        if (!name)
 | 
			
		||||
            break;
 | 
			
		||||
        logger = (*env)->NewGlobalRef( env, (*env)->CallStaticObjectMethod( env, cLoggerFactory, method, name ) );
 | 
			
		||||
        if (!logger)
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
    jclass Logger = (*env)->GetObjectClass( env, logger );
 | 
			
		||||
    if ((*env)->CallBooleanMethod( env, logger, (*env)->GetMethodID( env, Logger, "isTraceEnabled", "()Z" ) ))
 | 
			
		||||
        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, Logger, "isDebugEnabled", "()Z" ) ))
 | 
			
		||||
        else if ((*env)->CallBooleanMethod( env, logger, (*env)->GetMethodID( env, cLogger, "isDebugEnabled", "()Z" ) ))
 | 
			
		||||
            mpw_verbosity = LogLevelDebug;
 | 
			
		||||
    else if ((*env)->CallBooleanMethod( env, logger, (*env)->GetMethodID( env, Logger, "isInfoEnabled", "()Z" ) ))
 | 
			
		||||
        else if ((*env)->CallBooleanMethod( env, logger, (*env)->GetMethodID( env, cLogger, "isInfoEnabled", "()Z" ) ))
 | 
			
		||||
            mpw_verbosity = LogLevelInfo;
 | 
			
		||||
    else if ((*env)->CallBooleanMethod( env, logger, (*env)->GetMethodID( env, Logger, "isWarnEnabled", "()Z" ) ))
 | 
			
		||||
        else if ((*env)->CallBooleanMethod( env, logger, (*env)->GetMethodID( env, cLogger, "isWarnEnabled", "()Z" ) ))
 | 
			
		||||
            mpw_verbosity = LogLevelWarning;
 | 
			
		||||
    else if ((*env)->CallBooleanMethod( env, logger, (*env)->GetMethodID( env, Logger, "isErrorEnabled", "()Z" ) ))
 | 
			
		||||
        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." );
 | 
			
		||||
 | 
			
		||||
    return JNI_VERSION_1_6;
 | 
			
		||||
}
 | 
			
		||||
@@ -206,3 +216,51 @@ JNIEXPORT jstring JNICALL Java_com_lyndir_masterpassword_MPAlgorithm_00024Versio
 | 
			
		||||
 | 
			
		||||
    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. */
 | 
			
		||||
typedef mpw_enum( uint8_t, MPIdenticonColor ) {
 | 
			
		||||
    MPIdenticonColorBlack,
 | 
			
		||||
    MPIdenticonColorUnset,
 | 
			
		||||
    MPIdenticonColorRed,
 | 
			
		||||
    MPIdenticonColorGreen,
 | 
			
		||||
    MPIdenticonColorYellow,
 | 
			
		||||
    MPIdenticonColorBlue,
 | 
			
		||||
    MPIdenticonColorMagenta,
 | 
			
		||||
    MPIdenticonColorCyan,
 | 
			
		||||
    MPIdenticonColorWhite,
 | 
			
		||||
    MPIdenticonColorMono,
 | 
			
		||||
 | 
			
		||||
    MPIdenticonColorUnset = MPIdenticonColorBlack,
 | 
			
		||||
    MPIdenticonColorFirst = MPIdenticonColorRed,
 | 
			
		||||
    MPIdenticonColorLast = MPIdenticonColorWhite,
 | 
			
		||||
    MPIdenticonColorLast = MPIdenticonColorMono,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
 
 | 
			
		||||
@@ -87,6 +87,29 @@ public interface MPAlgorithm {
 | 
			
		||||
                     MPKeyPurpose keyPurpose, @Nullable String keyContext,
 | 
			
		||||
                     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
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -125,69 +148,6 @@ public interface MPAlgorithm {
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    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.
 | 
			
		||||
     */
 | 
			
		||||
@@ -222,10 +182,6 @@ public interface MPAlgorithm {
 | 
			
		||||
 | 
			
		||||
        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 {
 | 
			
		||||
            if (!Native.load( MPAlgorithm.class, "mpw" ))
 | 
			
		||||
                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 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
 | 
			
		||||
 | 
			
		||||
        @Nonnull
 | 
			
		||||
@@ -358,93 +378,5 @@ public interface MPAlgorithm {
 | 
			
		||||
        public Charset mpw_charset() {
 | 
			
		||||
            return Charsets.UTF_8;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Nonnull
 | 
			
		||||
        @Override
 | 
			
		||||
        public ByteOrder mpw_byteOrder() {
 | 
			
		||||
            return ByteOrder.BIG_ENDIAN;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Nonnull
 | 
			
		||||
        @Override
 | 
			
		||||
        public MessageDigests mpw_hash() {
 | 
			
		||||
            return MessageDigests.SHA256;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Nonnull
 | 
			
		||||
        @Override
 | 
			
		||||
        public MessageAuthenticationDigests mpw_digest() {
 | 
			
		||||
            return MessageAuthenticationDigests.HmacSHA256;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        @SuppressWarnings("MagicNumber")
 | 
			
		||||
        public int mpw_dkLen() {
 | 
			
		||||
            return 64;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        @SuppressWarnings("MagicNumber")
 | 
			
		||||
        public int mpw_keySize_min() {
 | 
			
		||||
            return 128;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        @SuppressWarnings("MagicNumber")
 | 
			
		||||
        public int mpw_keySize_max() {
 | 
			
		||||
            return 512;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        @SuppressWarnings("MagicNumber")
 | 
			
		||||
        public long mpw_otp_window() {
 | 
			
		||||
            return 5 * 60 /* s */;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        @SuppressWarnings("MagicNumber")
 | 
			
		||||
        public int scrypt_N() {
 | 
			
		||||
            return 32768;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        @SuppressWarnings("MagicNumber")
 | 
			
		||||
        public int scrypt_r() {
 | 
			
		||||
            return 8;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        @SuppressWarnings("MagicNumber")
 | 
			
		||||
        public int scrypt_p() {
 | 
			
		||||
            return 2;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Utilities
 | 
			
		||||
 | 
			
		||||
        @Nonnull
 | 
			
		||||
        public byte[] toBytes(final int number) {
 | 
			
		||||
            return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( mpw_byteOrder() ).putInt( number ).array();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Nonnull
 | 
			
		||||
        public byte[] toBytes(final UnsignedInteger number) {
 | 
			
		||||
            return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( mpw_byteOrder() ).putInt( number.intValue() ).array();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Nonnull
 | 
			
		||||
        public byte[] toBytes(final char[] characters) {
 | 
			
		||||
            ByteBuffer byteBuffer = mpw_charset().encode( CharBuffer.wrap( characters ) );
 | 
			
		||||
 | 
			
		||||
            byte[] bytes = new byte[byteBuffer.remaining()];
 | 
			
		||||
            byteBuffer.get( bytes );
 | 
			
		||||
 | 
			
		||||
            Arrays.fill( byteBuffer.array(), (byte) 0 );
 | 
			
		||||
            return bytes;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Nonnull
 | 
			
		||||
        public byte[] toID(final byte[] bytes) {
 | 
			
		||||
            return mpw_hash().of( bytes );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,14 +20,8 @@ package com.lyndir.masterpassword;
 | 
			
		||||
 | 
			
		||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
 | 
			
		||||
 | 
			
		||||
import com.google.common.base.Charsets;
 | 
			
		||||
import com.google.common.primitives.UnsignedBytes;
 | 
			
		||||
import com.lyndir.lhunath.opal.system.MessageAuthenticationDigests;
 | 
			
		||||
import com.lyndir.lhunath.opal.system.logging.Logger;
 | 
			
		||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 | 
			
		||||
import java.nio.*;
 | 
			
		||||
import java.nio.charset.Charset;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.Locale;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -39,43 +33,23 @@ public class MPIdenticon {
 | 
			
		||||
    @SuppressWarnings("UnusedDeclaration")
 | 
			
		||||
    private static final Logger logger = Logger.get( MPIdenticon.class );
 | 
			
		||||
 | 
			
		||||
    private static final Charset charset   = Charsets.UTF_8;
 | 
			
		||||
    private static final Color[] colors    = {
 | 
			
		||||
            Color.RED, Color.GREEN, Color.YELLOW, Color.BLUE, Color.MAGENTA, Color.CYAN, Color.MONO };
 | 
			
		||||
    private static final char[]  leftArm   = { '╔', '╚', '╰', '═' };
 | 
			
		||||
    private static final char[]  rightArm  = { '╗', '╝', '╯', '═' };
 | 
			
		||||
    private static final char[]  body      = { '█', '░', '▒', '▓', '☺', '☻' };
 | 
			
		||||
    private static final char[]  accessory = {
 | 
			
		||||
            '◈', '◎', '◐', '◑', '◒', '◓', '☀', '☁', '☂', '☃', '☄', '★', '☆', '☎', '☏', '⎈', '⌂', '☘', '☢', '☣', '☕', '⌚', '⌛', '⏰', '⚡',
 | 
			
		||||
            '⛄', '⛅', '☔', '♔', '♕', '♖', '♗', '♘', '♙', '♚', '♛', '♜', '♝', '♞', '♟', '♨', '♩', '♪', '♫', '⚐', '⚑', '⚔', '⚖', '⚙', '⚠',
 | 
			
		||||
            '⌘', '⏎', '✄', '✆', '✈', '✉', '✌' };
 | 
			
		||||
 | 
			
		||||
    private final String fullName;
 | 
			
		||||
    private final String leftArm;
 | 
			
		||||
    private final String body;
 | 
			
		||||
    private final String rightArm;
 | 
			
		||||
    private final String accessory;
 | 
			
		||||
    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")
 | 
			
		||||
    @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;
 | 
			
		||||
 | 
			
		||||
        byte[] masterPasswordBytes = charset.encode( CharBuffer.wrap( masterPassword ) ).array();
 | 
			
		||||
        ByteBuffer identiconSeedBytes = ByteBuffer.wrap(
 | 
			
		||||
                MessageAuthenticationDigests.HmacSHA256.of( masterPasswordBytes, fullName.getBytes( charset ) ) );
 | 
			
		||||
        Arrays.fill( masterPasswordBytes, (byte) 0 );
 | 
			
		||||
 | 
			
		||||
        IntBuffer identiconSeedBuffer = IntBuffer.allocate( identiconSeedBytes.capacity() );
 | 
			
		||||
        while (identiconSeedBytes.hasRemaining())
 | 
			
		||||
            identiconSeedBuffer.put( UnsignedBytes.toInt( identiconSeedBytes.get() ) );
 | 
			
		||||
        int[] identiconSeed = identiconSeedBuffer.array();
 | 
			
		||||
 | 
			
		||||
        color = colors[identiconSeed[4] % colors.length];
 | 
			
		||||
        text = strf( "%c%c%c%c", leftArm[identiconSeed[0] % leftArm.length], body[identiconSeed[1] % body.length],
 | 
			
		||||
                     rightArm[identiconSeed[2] % rightArm.length], accessory[identiconSeed[3] % accessory.length] );
 | 
			
		||||
        this.leftArm = leftArm;
 | 
			
		||||
        this.body = body;
 | 
			
		||||
        this.rightArm = rightArm;
 | 
			
		||||
        this.accessory = accessory;
 | 
			
		||||
        this.color = color;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getFullName() {
 | 
			
		||||
@@ -83,11 +57,11 @@ public class MPIdenticon {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getText() {
 | 
			
		||||
        return text;
 | 
			
		||||
        return strf( "%s%s%s%s", this.leftArm, this.body, this.rightArm, this.accessory );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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() {
 | 
			
		||||
@@ -95,6 +69,12 @@ public class MPIdenticon {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public enum Color {
 | 
			
		||||
        UNSET {
 | 
			
		||||
            @Override
 | 
			
		||||
            public String getCSS() {
 | 
			
		||||
                return "inherit";
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        RED,
 | 
			
		||||
        GREEN,
 | 
			
		||||
        YELLOW,
 | 
			
		||||
 
 | 
			
		||||
@@ -55,6 +55,18 @@ public class MPMasterKey {
 | 
			
		||||
        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
 | 
			
		||||
    public String getFullName() {
 | 
			
		||||
 | 
			
		||||
@@ -67,7 +79,7 @@ public class MPMasterKey {
 | 
			
		||||
     * @throws MPKeyUnavailableException {@link #invalidate()} has been called on this object.
 | 
			
		||||
     */
 | 
			
		||||
    @Nonnull
 | 
			
		||||
    public byte[] getKeyID(final MPAlgorithm algorithm)
 | 
			
		||||
    public String getKeyID(final MPAlgorithm algorithm)
 | 
			
		||||
            throws MPKeyUnavailableException, MPAlgorithmException {
 | 
			
		||||
 | 
			
		||||
        return algorithm.toID( masterKey( algorithm ) );
 | 
			
		||||
@@ -98,11 +110,6 @@ public class MPMasterKey {
 | 
			
		||||
 | 
			
		||||
        byte[] masterKey = keyByVersion.get( algorithm.version() );
 | 
			
		||||
        if (masterKey == null) {
 | 
			
		||||
            logger.trc( "-- mpw_masterKey (algorithm: %s)", algorithm );
 | 
			
		||||
            logger.trc( "fullName: %s", fullName );
 | 
			
		||||
            logger.trc( "masterPassword.id: %s", CodeUtils.encodeHex(
 | 
			
		||||
                    algorithm.toID( algorithm.toBytes( masterPassword ) ) ) );
 | 
			
		||||
 | 
			
		||||
            keyByVersion.put( algorithm.version(), masterKey = algorithm.masterKey( fullName, masterPassword ) );
 | 
			
		||||
        }
 | 
			
		||||
        if (masterKey == null)
 | 
			
		||||
@@ -118,13 +125,6 @@ public class MPMasterKey {
 | 
			
		||||
        Preconditions.checkArgument( !siteName.isEmpty() );
 | 
			
		||||
 | 
			
		||||
        byte[] masterKey = masterKey( algorithm );
 | 
			
		||||
 | 
			
		||||
        logger.trc( "-- mpw_siteKey (algorithm: %s)", algorithm );
 | 
			
		||||
        logger.trc( "siteName: %s", siteName );
 | 
			
		||||
        logger.trc( "siteCounter: %s", siteCounter );
 | 
			
		||||
        logger.trc( "keyPurpose: %d (%s)", keyPurpose.toInt(), keyPurpose.getShortName() );
 | 
			
		||||
        logger.trc( "keyContext: %s", keyContext );
 | 
			
		||||
 | 
			
		||||
        byte[] siteKey = algorithm.siteKey( masterKey, siteName, siteCounter, keyPurpose, keyContext );
 | 
			
		||||
        if (siteKey == null)
 | 
			
		||||
            throw new MPAlgorithmException( "Could not derive site key." );
 | 
			
		||||
@@ -161,10 +161,6 @@ public class MPMasterKey {
 | 
			
		||||
        byte[] masterKey = masterKey( algorithm );
 | 
			
		||||
        byte[] siteKey   = siteKey( siteName, algorithm, siteCounter, keyPurpose, keyContext );
 | 
			
		||||
 | 
			
		||||
        logger.trc( "-- mpw_siteResult (algorithm: %s)", algorithm );
 | 
			
		||||
        logger.trc( "resultType: %d (%s)", resultType.getType(), resultType.getShortName() );
 | 
			
		||||
        logger.trc( "resultParam: %s", resultParam );
 | 
			
		||||
 | 
			
		||||
        String siteResult = algorithm.siteResult(
 | 
			
		||||
                masterKey, siteKey, siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam );
 | 
			
		||||
        if (siteResult == null)
 | 
			
		||||
@@ -199,10 +195,6 @@ public class MPMasterKey {
 | 
			
		||||
        byte[] masterKey = masterKey( algorithm );
 | 
			
		||||
        byte[] siteKey   = siteKey( siteName, algorithm, siteCounter, keyPurpose, keyContext );
 | 
			
		||||
 | 
			
		||||
        logger.trc( "-- mpw_siteState (algorithm: %s)", algorithm );
 | 
			
		||||
        logger.trc( "resultType: %d (%s)", resultType.getType(), resultType.getShortName() );
 | 
			
		||||
        logger.trc( "resultParam: %d bytes = %s", resultParam.getBytes( algorithm.mpw_charset() ).length, resultParam );
 | 
			
		||||
 | 
			
		||||
        String siteState = algorithm.siteState(
 | 
			
		||||
                masterKey, siteKey, siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam );
 | 
			
		||||
        if (siteState == null)
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,7 @@ public class MPIncognitoUser extends MPBasicUser<MPIncognitoSite> {
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
    @Override
 | 
			
		||||
    public byte[] getKeyID() {
 | 
			
		||||
    public String getKeyID() {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -35,6 +35,7 @@ import javax.swing.border.Border;
 | 
			
		||||
import javax.swing.border.CompoundBorder;
 | 
			
		||||
import javax.swing.event.HyperlinkEvent;
 | 
			
		||||
import javax.swing.text.*;
 | 
			
		||||
import javax.swing.undo.UndoableEdit;
 | 
			
		||||
import org.jetbrains.annotations.NonNls;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -207,7 +208,13 @@ public abstract class Components {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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 ),
 | 
			
		||||
                                                               BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) );
 | 
			
		||||
 
 | 
			
		||||
@@ -182,7 +182,7 @@ public abstract class Res {
 | 
			
		||||
 | 
			
		||||
    public static final class Fonts {
 | 
			
		||||
 | 
			
		||||
        public Font emoticonsFont(final int size) {
 | 
			
		||||
        public Font identiconFont(final int size) {
 | 
			
		||||
            return MPFont.emoticonsRegular.get( size );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -317,7 +317,7 @@ public class UserContentPanel extends JPanel implements State.Listener, MPUser.L
 | 
			
		||||
            add( Components.strut() );
 | 
			
		||||
 | 
			
		||||
            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( Components.label( "Master Password:" ) );
 | 
			
		||||
@@ -330,6 +330,15 @@ public class UserContentPanel extends JPanel implements State.Listener, MPUser.L
 | 
			
		||||
            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() {
 | 
			
		||||
            MPFileUser fileUser = (user instanceof MPFileUser)? (MPFileUser) user: null;
 | 
			
		||||
            if (fileUser == null)
 | 
			
		||||
@@ -449,7 +458,8 @@ public class UserContentPanel extends JPanel implements State.Listener, MPUser.L
 | 
			
		||||
        private void updateIdenticon() {
 | 
			
		||||
            char[] masterPassword = masterPasswordField.getPassword();
 | 
			
		||||
            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( () -> {
 | 
			
		||||
                if (identicon != null) {
 | 
			
		||||
@@ -779,7 +789,7 @@ public class UserContentPanel extends JPanel implements State.Listener, MPUser.L
 | 
			
		||||
            typeModel.selection( MPResultType.DeriveKey, t -> {
 | 
			
		||||
                switch (t) {
 | 
			
		||||
                    case DeriveKey:
 | 
			
		||||
                        stateModel.setText( Integer.toString( site.getAlgorithm().mpw_keySize_min() ) );
 | 
			
		||||
                        stateModel.setText( "128" );
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    default:
 | 
			
		||||
 
 | 
			
		||||
@@ -49,10 +49,7 @@ public interface MPUser<S extends MPSite<?>> extends Comparable<MPUser<?>> {
 | 
			
		||||
    void setAlgorithm(MPAlgorithm algorithm);
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
    byte[] getKeyID();
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
    String exportKeyID();
 | 
			
		||||
    String getKeyID();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Performs an authentication attempt against the keyID for this user.
 | 
			
		||||
 
 | 
			
		||||
@@ -100,7 +100,7 @@ public abstract class MPBasicUser<S extends MPBasicSite<?, ?>> extends Changeabl
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
    @Override
 | 
			
		||||
    public byte[] getKeyID() {
 | 
			
		||||
    public String getKeyID() {
 | 
			
		||||
        try {
 | 
			
		||||
            if (isMasterKeyAvailable())
 | 
			
		||||
                return getMasterKey().getKeyID( getAlgorithm() );
 | 
			
		||||
@@ -112,12 +112,6 @@ public abstract class MPBasicUser<S extends MPBasicSite<?, ?>> extends Changeabl
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
    @Override
 | 
			
		||||
    public String exportKeyID() {
 | 
			
		||||
        return CodeUtils.encodeHex( getKeyID() );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void authenticate(final char[] masterPassword)
 | 
			
		||||
            throws MPIncorrectMasterPasswordException, MPAlgorithmException {
 | 
			
		||||
@@ -136,8 +130,8 @@ public abstract class MPBasicUser<S extends MPBasicSite<?, ?>> extends Changeabl
 | 
			
		||||
            throw new IllegalArgumentException(
 | 
			
		||||
                    "Master key (for " + masterKey.getFullName() + ") is not for this user (" + getFullName() + ")." );
 | 
			
		||||
 | 
			
		||||
        byte[] keyID = getKeyID();
 | 
			
		||||
        if ((keyID != null) && !Arrays.equals( masterKey.getKeyID( getAlgorithm() ), keyID ))
 | 
			
		||||
        String keyID = getKeyID();
 | 
			
		||||
        if (keyID != null && !keyID.equalsIgnoreCase( masterKey.getKeyID( getAlgorithm() ) ))
 | 
			
		||||
            throw new MPIncorrectMasterPasswordException( this );
 | 
			
		||||
 | 
			
		||||
        this.masterKey = masterKey;
 | 
			
		||||
 
 | 
			
		||||
@@ -39,7 +39,7 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
 | 
			
		||||
    private static final Logger logger = Logger.get( MPFileUser.class );
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
    private byte[]                   keyID;
 | 
			
		||||
    private String                   keyID;
 | 
			
		||||
    private File                     file;
 | 
			
		||||
    private MPMarshalFormat          format;
 | 
			
		||||
    private MPMarshaller.ContentMode contentMode;
 | 
			
		||||
@@ -62,18 +62,18 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
 | 
			
		||||
        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,
 | 
			
		||||
              MPMarshaller.ContentMode.PROTECTED, MPMarshalFormat.DEFAULT, location );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @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,
 | 
			
		||||
                      final MPMarshaller.ContentMode contentMode, final MPMarshalFormat format, final File location) {
 | 
			
		||||
        super( avatar, fullName, algorithm );
 | 
			
		||||
 | 
			
		||||
        this.keyID = (keyID != null)? keyID.clone(): null;
 | 
			
		||||
        this.keyID = keyID;
 | 
			
		||||
        this.lastUsed = lastUsed;
 | 
			
		||||
        this.preferences = new MPFileUserPreferences( this, defaultType, hidePasswords );
 | 
			
		||||
        this.format = format;
 | 
			
		||||
@@ -87,8 +87,8 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
    @Override
 | 
			
		||||
    public byte[] getKeyID() {
 | 
			
		||||
        return (keyID == null)? null: keyID.clone();
 | 
			
		||||
    public String getKeyID() {
 | 
			
		||||
        return keyID;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nonnull
 | 
			
		||||
 
 | 
			
		||||
@@ -54,7 +54,7 @@ public class MPFlatMarshaller implements MPMarshaller {
 | 
			
		||||
        content.append( "# User Name: " ).append( user.getFullName() ).append( '\n' );
 | 
			
		||||
        content.append( "# Full Name: " ).append( user.getFullName() ).append( '\n' );
 | 
			
		||||
        content.append( "# Avatar: " ).append( user.getAvatar() ).append( '\n' );
 | 
			
		||||
        content.append( "# Key ID: " ).append( user.exportKeyID() ).append( '\n' );
 | 
			
		||||
        content.append( "# Key ID: " ).append( user.getKeyID() ).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( "# Passwords: " ).append( user.getContentMode().name() ).append( '\n' );
 | 
			
		||||
 
 | 
			
		||||
@@ -51,7 +51,7 @@ public class MPFlatUnmarshaller implements MPUnmarshaller {
 | 
			
		||||
    public MPFileUser readUser(@Nonnull final File file)
 | 
			
		||||
            throws IOException, MPMarshalException {
 | 
			
		||||
        try (Reader reader = new InputStreamReader( new FileInputStream( file ), Charsets.UTF_8 )) {
 | 
			
		||||
            byte[]       keyID        = null;
 | 
			
		||||
            String       keyID        = null;
 | 
			
		||||
            String       fullName     = null;
 | 
			
		||||
            int          mpVersion    = 0, avatar = 0;
 | 
			
		||||
            boolean      clearContent = false, headerStarted = false;
 | 
			
		||||
@@ -84,7 +84,7 @@ public class MPFlatUnmarshaller implements MPUnmarshaller {
 | 
			
		||||
                            if ("Full Name".equalsIgnoreCase( name ) || "User Name".equalsIgnoreCase( name ))
 | 
			
		||||
                                fullName = value;
 | 
			
		||||
                            else if ("Key ID".equalsIgnoreCase( name ))
 | 
			
		||||
                                keyID = CodeUtils.decodeHex( value );
 | 
			
		||||
                                keyID = value;
 | 
			
		||||
                            else if ("Algorithm".equalsIgnoreCase( name ))
 | 
			
		||||
                                mpVersion = ConversionUtils.toIntegerNN( value );
 | 
			
		||||
                            else if ("Avatar".equalsIgnoreCase( name ))
 | 
			
		||||
 
 | 
			
		||||
@@ -61,7 +61,7 @@ public class MPJSONFile extends MPJSONAnyObject {
 | 
			
		||||
        user.avatar = modelUser.getAvatar();
 | 
			
		||||
        user.full_name = modelUser.getFullName();
 | 
			
		||||
        user.last_used = MPModelConstants.dateTimeFormatter.print( modelUser.getLastUsed() );
 | 
			
		||||
        user.key_id = modelUser.exportKeyID();
 | 
			
		||||
        user.key_id = modelUser.getKeyID();
 | 
			
		||||
        user.algorithm = modelUser.getAlgorithm().version();
 | 
			
		||||
        user._ext_mpw = new User.Ext() {
 | 
			
		||||
            {
 | 
			
		||||
@@ -131,7 +131,7 @@ public class MPJSONFile extends MPJSONAnyObject {
 | 
			
		||||
        MPAlgorithm algorithm = ifNotNullElse( user.algorithm, MPAlgorithm.Version.CURRENT );
 | 
			
		||||
 | 
			
		||||
        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.last_used != null)? MPModelConstants.dateTimeFormatter.parseDateTime( user.last_used ): new Instant(),
 | 
			
		||||
                (user._ext_mpw != null) && user._ext_mpw.hide_passwords,
 | 
			
		||||
 
 | 
			
		||||
@@ -53,9 +53,8 @@ public class MPMasterKeyTest {
 | 
			
		||||
            MPMasterKey masterKey      = new MPMasterKey( testCase.getFullName(), masterPassword );
 | 
			
		||||
 | 
			
		||||
            // Test key
 | 
			
		||||
            assertEquals(
 | 
			
		||||
                    CodeUtils.encodeHex( masterKey.getKeyID( testCase.getAlgorithm() ) ),
 | 
			
		||||
                    testCase.getKeyID(),
 | 
			
		||||
            assertTrue(
 | 
			
		||||
                    testCase.getKeyID().equalsIgnoreCase( masterKey.getKeyID( testCase.getAlgorithm() ) ),
 | 
			
		||||
                    "[testMasterKey] keyID mismatch for test case: " + testCase );
 | 
			
		||||
 | 
			
		||||
            // Test invalidation
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user