From 1c3ea3826fce55e4b4209f5454212656bb13cd19 Mon Sep 17 00:00:00 2001 From: Maarten Billemont Date: Wed, 15 Apr 2020 19:09:02 -0400 Subject: [PATCH] Move identicon and toID to mpw native. Clean out all unused Java MPAlgorithm stuff. Fix master password entries stuck in memory. --- ...yndir_masterpassword_MPAlgorithm_Version.h | 18 +- platform-independent/c/core/src/mpw-jni.c | 114 +++++++-- platform-independent/c/core/src/mpw-types.h | 7 +- .../lyndir/masterpassword/MPAlgorithm.java | 242 +++++++----------- .../lyndir/masterpassword/MPIdenticon.java | 58 ++--- .../lyndir/masterpassword/MPMasterKey.java | 34 +-- .../gui/model/MPIncognitoUser.java | 2 +- .../masterpassword/gui/util/Components.java | 9 +- .../lyndir/masterpassword/gui/util/Res.java | 2 +- .../gui/view/UserContentPanel.java | 16 +- .../lyndir/masterpassword/model/MPUser.java | 5 +- .../model/impl/MPBasicUser.java | 12 +- .../masterpassword/model/impl/MPFileUser.java | 12 +- .../model/impl/MPFlatMarshaller.java | 2 +- .../model/impl/MPFlatUnmarshaller.java | 4 +- .../masterpassword/model/impl/MPJSONFile.java | 4 +- .../masterpassword/MPMasterKeyTest.java | 5 +- 17 files changed, 264 insertions(+), 282 deletions(-) diff --git a/platform-independent/c/core/src/java/com_lyndir_masterpassword_MPAlgorithm_Version.h b/platform-independent/c/core/src/java/com_lyndir_masterpassword_MPAlgorithm_Version.h index 7fb59ba7..b89ea3ba 100644 --- a/platform-independent/c/core/src/java/com_lyndir_masterpassword_MPAlgorithm_Version.h +++ b/platform-independent/c/core/src/java/com_lyndir_masterpassword_MPAlgorithm_Version.h @@ -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 diff --git a/platform-independent/c/core/src/mpw-jni.c b/platform-independent/c/core/src/mpw-jni.c index c714ba31..94bea253 100644 --- a/platform-independent/c/core/src/mpw-jni.c +++ b/platform-independent/c/core/src/mpw-jni.c @@ -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,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) return -1; - jclass LoggerFactory = (*env)->FindClass( env, "org/slf4j/LoggerFactory" ); - jmethodID method = (*env)->GetStaticMethodID( env, LoggerFactory, "getLogger", "(Ljava/lang/String;)Lorg/slf4j/Logger;" ); - jstring name = (*env)->NewStringUTF( env, "com.lyndir.masterpassword.algorithm" ); - if (LoggerFactory && method && name) - logger = (*env)->NewGlobalRef( env, (*env)->CallStaticObjectMethod( env, LoggerFactory, method, name ) ); - else + 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 (!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." ); - 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; } @@ -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, "", "(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 ) ) ); +} diff --git a/platform-independent/c/core/src/mpw-types.h b/platform-independent/c/core/src/mpw-types.h index 3d71f060..b342b58c 100644 --- a/platform-independent/c/core/src/mpw-types.h +++ b/platform-independent/c/core/src/mpw-types.h @@ -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 { diff --git a/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPAlgorithm.java b/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPAlgorithm.java index ef62f065..43b1f9eb 100644 --- a/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPAlgorithm.java +++ b/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPAlgorithm.java @@ -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 ); - } } } diff --git a/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPIdenticon.java b/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPIdenticon.java index 311a2e34..3bd01a47 100644 --- a/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPIdenticon.java +++ b/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPIdenticon.java @@ -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( "%s", color.getCSS(), text ); + return strf( "%s", 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, diff --git a/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPMasterKey.java b/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPMasterKey.java index 4465be88..48b78de8 100644 --- a/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPMasterKey.java +++ b/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPMasterKey.java @@ -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) diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/model/MPIncognitoUser.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/model/MPIncognitoUser.java index 305b2caa..4f930736 100755 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/model/MPIncognitoUser.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/model/MPIncognitoUser.java @@ -35,7 +35,7 @@ public class MPIncognitoUser extends MPBasicUser { @Nullable @Override - public byte[] getKeyID() { + public String getKeyID() { return null; } diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java index 0547b22f..218d109f 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java @@ -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 ) ) ); diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Res.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Res.java index 6ab70bc8..59ca34ef 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Res.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Res.java @@ -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 ); } diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserContentPanel.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserContentPanel.java index 6d948405..a9e0d693 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserContentPanel.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserContentPanel.java @@ -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: diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPUser.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPUser.java index 7f707f8c..587844af 100644 --- a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPUser.java +++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPUser.java @@ -49,10 +49,7 @@ public interface MPUser> extends Comparable> { void setAlgorithm(MPAlgorithm algorithm); @Nullable - byte[] getKeyID(); - - @Nullable - String exportKeyID(); + String getKeyID(); /** * Performs an authentication attempt against the keyID for this user. diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicUser.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicUser.java index 7df83c6e..14742f90 100755 --- a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicUser.java +++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicUser.java @@ -100,7 +100,7 @@ public abstract class MPBasicUser> 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> 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> 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; diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFileUser.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFileUser.java index 76d97548..3d33ff08 100755 --- a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFileUser.java +++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFileUser.java @@ -39,7 +39,7 @@ public class MPFileUser extends MPBasicUser { 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 { 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 { @Nullable @Override - public byte[] getKeyID() { - return (keyID == null)? null: keyID.clone(); + public String getKeyID() { + return keyID; } @Nonnull diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFlatMarshaller.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFlatMarshaller.java index b3cca47c..ecaf77e6 100644 --- a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFlatMarshaller.java +++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFlatMarshaller.java @@ -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' ); diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFlatUnmarshaller.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFlatUnmarshaller.java index 673e9307..d307d20d 100644 --- a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFlatUnmarshaller.java +++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFlatUnmarshaller.java @@ -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 )) diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPJSONFile.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPJSONFile.java index bbd38036..e73645f2 100644 --- a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPJSONFile.java +++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPJSONFile.java @@ -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, diff --git a/platform-independent/java/tests/src/test/java/com/lyndir/masterpassword/MPMasterKeyTest.java b/platform-independent/java/tests/src/test/java/com/lyndir/masterpassword/MPMasterKeyTest.java index 14d6bdd1..40270be2 100644 --- a/platform-independent/java/tests/src/test/java/com/lyndir/masterpassword/MPMasterKeyTest.java +++ b/platform-independent/java/tests/src/test/java/com/lyndir/masterpassword/MPMasterKeyTest.java @@ -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