From cecaf1b5ccbbe9af861395c0d4724e0892fcf6f5 Mon Sep 17 00:00:00 2001 From: Maarten Billemont Date: Sat, 23 Sep 2017 19:11:06 -0400 Subject: [PATCH] Log fixes, test improvements and some refactoring. --- core/java/algorithm/build.gradle | 4 +- core/java/algorithm/pom.xml | 4 +- .../lyndir/masterpassword/MPAlgorithm.java | 6 +- .../lyndir/masterpassword/MPAlgorithmV0.java | 56 ++++-------- .../lyndir/masterpassword/MPAlgorithmV1.java | 8 +- .../lyndir/masterpassword/MPAlgorithmV2.java | 11 +-- .../lyndir/masterpassword/MPAlgorithmV3.java | 20 ++--- .../lyndir/masterpassword/MPMasterKey.java | 86 ++++++++++++++---- .../lyndir/masterpassword/MPResultType.java | 34 +++----- .../com/lyndir/masterpassword/MPUtils.java | 12 +++ .../lyndir/masterpassword/MPTestSuite.java | 2 +- .../com/lyndir/masterpassword/MPTests.java | 4 +- .../masterpassword/MPMasterKeyTest.java | 87 +++++++++++++------ .../java/tests/src/test/resources/logback.xml | 4 +- gradle/.idea/runConfigurations/GUI.xml | 4 +- gradle/.idea/runConfigurations/Tests.xml | 6 +- 16 files changed, 201 insertions(+), 147 deletions(-) diff --git a/core/java/algorithm/build.gradle b/core/java/algorithm/build.gradle index d39c8889..59da7457 100644 --- a/core/java/algorithm/build.gradle +++ b/core/java/algorithm/build.gradle @@ -5,10 +5,10 @@ plugins { description = 'Master Password Algorithm Implementation' dependencies { - compile (group: 'com.lyndir.lhunath.opal', name: 'opal-system', version: '1.6-p10') { + compile (group: 'com.lyndir.lhunath.opal', name: 'opal-system', version: '1.6-p11') { exclude( module: 'joda-time' ) } - compile group: 'com.lyndir.lhunath.opal', name: 'opal-crypto', version: '1.6-p10' + compile group: 'com.lyndir.lhunath.opal', name: 'opal-crypto', version: '1.6-p11' compile group: 'com.lambdaworks', name: 'scrypt', version: '1.4.0' compile group: 'org.jetbrains', name: 'annotations', version: '13.0' diff --git a/core/java/algorithm/pom.xml b/core/java/algorithm/pom.xml index 3da84269..b4c41ae2 100644 --- a/core/java/algorithm/pom.xml +++ b/core/java/algorithm/pom.xml @@ -23,7 +23,7 @@ com.lyndir.lhunath.opal opal-system - 1.6-p9 + 1.6-p11 joda-time @@ -34,7 +34,7 @@ com.lyndir.lhunath.opal opal-crypto - 1.6-p9 + 1.6-p11 diff --git a/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPAlgorithm.java b/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPAlgorithm.java index 0442139e..340c7c86 100644 --- a/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPAlgorithm.java +++ b/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPAlgorithm.java @@ -30,12 +30,12 @@ public interface MPAlgorithm extends Serializable { MPMasterKey.Version getAlgorithmVersion(); - byte[] deriveKey(String fullName, char[] masterPassword); + byte[] masterKey(String fullName, char[] masterPassword); byte[] siteKey(byte[] masterKey, String siteName, UnsignedInteger siteCounter, MPKeyPurpose keyPurpose, @Nullable String keyContext); - String siteResult(byte[] masterKey, String siteName, UnsignedInteger siteCounter, MPKeyPurpose keyPurpose, + String siteResult(byte[] masterKey, final byte[] siteKey, String siteName, UnsignedInteger siteCounter, MPKeyPurpose keyPurpose, @Nullable String keyContext, MPResultType resultType, @Nullable String resultParam); String sitePasswordFromTemplate(byte[] masterKey, byte[] siteKey, MPResultType resultType, @Nullable String resultParam); @@ -44,6 +44,6 @@ public interface MPAlgorithm extends Serializable { String sitePasswordFromDerive(byte[] masterKey, byte[] siteKey, MPResultType resultType, @Nullable String resultParam); - String siteState(byte[] masterKey, String siteName, UnsignedInteger siteCounter, MPKeyPurpose keyPurpose, + String siteState(byte[] masterKey, final byte[] siteKey, String siteName, UnsignedInteger siteCounter, MPKeyPurpose keyPurpose, @Nullable String keyContext, MPResultType resultType, @Nullable String resultParam); } diff --git a/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPAlgorithmV0.java b/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPAlgorithmV0.java index c50456e7..21b57fbd 100644 --- a/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPAlgorithmV0.java +++ b/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPAlgorithmV0.java @@ -90,16 +90,10 @@ public class MPAlgorithmV0 implements MPAlgorithm { } @Override - public byte[] deriveKey(final String fullName, final char[] masterPassword) { - Preconditions.checkArgument( masterPassword.length > 0 ); + public byte[] masterKey(final String fullName, final char[] masterPassword) { byte[] fullNameBytes = fullName.getBytes( mpw_charset ); byte[] fullNameLengthBytes = bytesForInt( fullName.length() ); - ByteBuffer mpBytesBuf = mpw_charset.encode( CharBuffer.wrap( masterPassword ) ); - - logger.trc( "-- mpw_masterKey (algorithm: %u)", getAlgorithmVersion().toInt() ); - logger.trc( "fullName: %s", fullName ); - logger.trc( "masterPassword.id: %s", (Object) idForBytes( mpBytesBuf.array() ) ); String keyScope = MPKeyPurpose.Authentication.getScope(); logger.trc( "keyScope: %s", keyScope ); @@ -111,15 +105,13 @@ public class MPAlgorithmV0 implements MPAlgorithm { logger.trc( " => masterKeySalt.id: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) ); // Calculate the master key. - logger.trc( "masterKey: scrypt( masterPassword, masterKeySalt, N=%lu, r=%u, p=%u )", + logger.trc( "masterKey: scrypt( masterPassword, masterKeySalt, N=%d, r=%d, p=%d )", scrypt_N, scrypt_r, scrypt_p ); - byte[] mpBytes = new byte[mpBytesBuf.remaining()]; - mpBytesBuf.get( mpBytes, 0, mpBytes.length ); - Arrays.fill( mpBytesBuf.array(), (byte) 0 ); - byte[] masterKey = scrypt( masterKeySalt, mpBytes ); // TODO: Why not mpBytesBuf.array()? + byte[] masterPasswordBytes = bytesForChars( masterPassword ); + byte[] masterKey = scrypt( masterKeySalt, masterPasswordBytes ); Arrays.fill( masterKeySalt, (byte) 0 ); - Arrays.fill( mpBytes, (byte) 0 ); - logger.trc( " => masterKey.id: %s", (Object) idForBytes( masterKey ) ); + Arrays.fill( masterPasswordBytes, (byte) 0 ); + logger.trc( " => masterKey.id: %s", CodeUtils.encodeHex( idForBytes( masterKey ) ) ); return masterKey; } @@ -139,13 +131,6 @@ public class MPAlgorithmV0 implements MPAlgorithm { @Override public byte[] siteKey(final byte[] masterKey, final String siteName, UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose, @Nullable final String keyContext) { - Preconditions.checkArgument( !siteName.isEmpty() ); - - logger.trc( "-- mpw_siteKey (algorithm: %u)", getAlgorithmVersion().toInt() ); - logger.trc( "siteName: %s", siteName ); - logger.trc( "siteCounter: %d", siteCounter ); - logger.trc( "keyPurpose: %d (%s)", keyPurpose.toInt(), keyPurpose.getShortName() ); - logger.trc( "keyContext: %s", keyContext ); String keyScope = keyPurpose.getScope(); logger.trc( "keyScope: %s", keyScope ); @@ -169,23 +154,18 @@ public class MPAlgorithmV0 implements MPAlgorithm { sitePasswordInfo = Bytes.concat( sitePasswordInfo, keyContextLengthBytes, keyContextBytes ); logger.trc( " => siteSalt.id: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) ); - logger.trc( "siteKey: hmac-sha256( masterKey.id=%s, siteSalt )", (Object) idForBytes( masterKey ) ); + logger.trc( "siteKey: hmac-sha256( masterKey.id=%s, siteSalt )", CodeUtils.encodeHex( idForBytes( masterKey ) ) ); byte[] sitePasswordSeedBytes = mpw_digest.of( masterKey, sitePasswordInfo ); - logger.trc( " => siteKey.id: %s", (Object) idForBytes( sitePasswordSeedBytes ) ); + logger.trc( " => siteKey.id: %s", CodeUtils.encodeHex( idForBytes( sitePasswordSeedBytes ) ) ); return sitePasswordSeedBytes; } @Override - public String siteResult(final byte[] masterKey, final String siteName, final UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose, + public String siteResult(final byte[] masterKey, final byte[] siteKey, final String siteName, final UnsignedInteger siteCounter, + final MPKeyPurpose keyPurpose, @Nullable final String keyContext, final MPResultType resultType, @Nullable final String resultParam) { - byte[] siteKey = siteKey( masterKey, siteName, siteCounter, keyPurpose, keyContext ); - - logger.trc( "-- mpw_siteResult (algorithm: %u)", getAlgorithmVersion().toInt() ); - logger.trc( "resultType: %d (%s)", resultType.toInt(), resultType.getShortName() ); - logger.trc( "resultParam: %s", resultParam ); - switch (resultType.getTypeClass()) { case Template: return sitePasswordFromTemplate( masterKey, siteKey, resultType, resultParam ); @@ -214,7 +194,7 @@ public class MPAlgorithmV0 implements MPAlgorithm { Preconditions.checkState( _siteKey.length > 0 ); int templateIndex = _siteKey[0]; MPTemplate template = resultType.getTemplateAtRollingIndex( templateIndex ); - logger.trc( "template: %u => %s", templateIndex, template.getTemplateString() ); + logger.trc( "template: %d => %s", templateIndex, template.getTemplateString() ); // Encode the password from the seed using the template. StringBuilder password = new StringBuilder( template.length() ); @@ -222,7 +202,7 @@ public class MPAlgorithmV0 implements MPAlgorithm { int characterIndex = _siteKey[i + 1]; MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i ); char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex ); - logger.trc( " - class: %c, index: %5u (0x%02hX) => character: %c", + logger.trc( " - class: %c, index: %5d (0x%2H) => character: %c", characterClass.getIdentifier(), characterIndex, _siteKey[i + 1], passwordCharacter ); password.append( passwordCharacter ); @@ -241,7 +221,7 @@ public class MPAlgorithmV0 implements MPAlgorithm { try { // Base64-decode byte[] cipherBuf = CryptUtils.decodeBase64( resultParam ); - logger.trc( "b64 decoded: %zu bytes = %s", cipherBuf.length, CodeUtils.encodeHex( cipherBuf ) ); + logger.trc( "b64 decoded: %d bytes = %s", cipherBuf.length, CodeUtils.encodeHex( cipherBuf ) ); // Decrypt byte[] plainBuf = CryptUtils.decrypt( cipherBuf, masterKey, true ); @@ -266,7 +246,7 @@ public class MPAlgorithmV0 implements MPAlgorithm { if ((resultParamInt < 128) || (resultParamInt > 512) || ((resultParamInt % 8) != 0)) throw logger.bug( "Parameter is not a valid key size (should be 128 - 512): %s", resultParam ); int keySize = resultParamInt / 8; - logger.trc( "keySize: %u", keySize ); + logger.trc( "keySize: %d", keySize ); // Derive key byte[] resultKey = null; // TODO: mpw_kdf_blake2b( keySize, siteKey, MPSiteKeySize, NULL, 0, 0, NULL ); @@ -285,7 +265,8 @@ public class MPAlgorithmV0 implements MPAlgorithm { } @Override - public String siteState(final byte[] masterKey, final String siteName, final UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose, + public String siteState(final byte[] masterKey, final byte[] siteKey, final String siteName, final UnsignedInteger siteCounter, + final MPKeyPurpose keyPurpose, @Nullable final String keyContext, final MPResultType resultType, @Nullable final String resultParam) { Preconditions.checkNotNull( resultParam ); @@ -293,9 +274,8 @@ public class MPAlgorithmV0 implements MPAlgorithm { try { // Encrypt - ByteBuffer plainText = mpw_charset.encode( CharBuffer.wrap( resultParam ) ); - byte[] cipherBuf = CryptUtils.encrypt( plainText.array(), masterKey, true ); - logger.trc( "cipherBuf: %zu bytes = %s", cipherBuf.length, CodeUtils.encodeHex( cipherBuf ) ); + byte[] cipherBuf = CryptUtils.encrypt( resultParam.getBytes( mpw_charset ), masterKey, true ); + logger.trc( "cipherBuf: %d bytes = %s", cipherBuf.length, CodeUtils.encodeHex( cipherBuf ) ); // Base64-encode String cipherText = Verify.verifyNotNull( CryptUtils.encodeBase64( cipherBuf ) ); diff --git a/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPAlgorithmV1.java b/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPAlgorithmV1.java index e1ecc4de..7d1088a0 100644 --- a/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPAlgorithmV1.java +++ b/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPAlgorithmV1.java @@ -38,15 +38,11 @@ public class MPAlgorithmV1 extends MPAlgorithmV0 { @Override public String sitePasswordFromTemplate(final byte[] masterKey, final byte[] siteKey, final MPResultType resultType, @Nullable final String resultParam) { - logger.trc( "-- mpw_siteResult (algorithm: %u)", getAlgorithmVersion().toInt() ); - logger.trc( "resultType: %d (%s)", resultType.toInt(), resultType.getShortName() ); - logger.trc( "resultParam: %s", resultParam ); - // Determine the template. Preconditions.checkState( siteKey.length > 0 ); int templateIndex = siteKey[0] & 0xFF; // Convert to unsigned int. MPTemplate template = resultType.getTemplateAtRollingIndex( templateIndex ); - logger.trc( "template: %u => %s", templateIndex, template.getTemplateString() ); + logger.trc( "template: %d => %s", templateIndex, template.getTemplateString() ); // Encode the password from the seed using the template. StringBuilder password = new StringBuilder( template.length() ); @@ -54,7 +50,7 @@ public class MPAlgorithmV1 extends MPAlgorithmV0 { int characterIndex = siteKey[i + 1] & 0xFF; // Convert to unsigned int. MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i ); char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex ); - logger.trc( " - class: %c, index: %3u (0x%02hhX) => character: %c", + logger.trc( " - class: %c, index: %3d (0x%2H) => character: %c", characterClass.getIdentifier(), characterIndex, siteKey[i + 1], passwordCharacter ); password.append( passwordCharacter ); diff --git a/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPAlgorithmV2.java b/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPAlgorithmV2.java index ef76c083..d8835a34 100644 --- a/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPAlgorithmV2.java +++ b/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPAlgorithmV2.java @@ -43,13 +43,6 @@ public class MPAlgorithmV2 extends MPAlgorithmV1 { @Override public byte[] siteKey(final byte[] masterKey, final String siteName, UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose, @Nullable final String keyContext) { - Preconditions.checkArgument( !siteName.isEmpty() ); - - logger.trc( "-- mpw_siteKey (algorithm: %u)", getAlgorithmVersion().toInt() ); - logger.trc( "siteName: %s", siteName ); - logger.trc( "siteCounter: %d", siteCounter ); - logger.trc( "keyPurpose: %d (%s)", keyPurpose.toInt(), keyPurpose.getShortName() ); - logger.trc( "keyContext: %s", keyContext ); String keyScope = keyPurpose.getScope(); logger.trc( "keyScope: %s", keyScope ); @@ -73,9 +66,9 @@ public class MPAlgorithmV2 extends MPAlgorithmV1 { sitePasswordInfo = Bytes.concat( sitePasswordInfo, keyContextLengthBytes, keyContextBytes ); logger.trc( " => siteSalt.id: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) ); - logger.trc( "siteKey: hmac-sha256( masterKey.id=%s, siteSalt )", (Object) idForBytes( masterKey ) ); + logger.trc( "siteKey: hmac-sha256( masterKey.id=%s, siteSalt )", CodeUtils.encodeHex( idForBytes( masterKey ) ) ); byte[] sitePasswordSeedBytes = MPAlgorithmV0.mpw_digest.of( masterKey, sitePasswordInfo ); - logger.trc( " => siteKey.id: %s", (Object) idForBytes( sitePasswordSeedBytes ) ); + logger.trc( " => siteKey.id: %s", CodeUtils.encodeHex( idForBytes( sitePasswordSeedBytes ) ) ); return sitePasswordSeedBytes; } diff --git a/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPAlgorithmV3.java b/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPAlgorithmV3.java index 497c858d..f7d6db41 100644 --- a/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPAlgorithmV3.java +++ b/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPAlgorithmV3.java @@ -18,7 +18,7 @@ package com.lyndir.masterpassword; -import static com.lyndir.masterpassword.MPUtils.idForBytes; +import static com.lyndir.masterpassword.MPUtils.*; import com.google.common.base.Preconditions; import com.google.common.primitives.Bytes; @@ -42,16 +42,10 @@ public class MPAlgorithmV3 extends MPAlgorithmV2 { } @Override - public byte[] deriveKey(final String fullName, final char[] masterPassword) { - Preconditions.checkArgument( masterPassword.length > 0 ); + public byte[] masterKey(final String fullName, final char[] masterPassword) { byte[] fullNameBytes = fullName.getBytes( MPAlgorithmV0.mpw_charset ); byte[] fullNameLengthBytes = MPUtils.bytesForInt( fullNameBytes.length ); - ByteBuffer mpBytesBuf = MPAlgorithmV0.mpw_charset.encode( CharBuffer.wrap( masterPassword ) ); - - logger.trc( "-- mpw_masterKey (algorithm: %u)", getAlgorithmVersion().toInt() ); - logger.trc( "fullName: %s", fullName ); - logger.trc( "masterPassword.id: %s", (Object) idForBytes( mpBytesBuf.array() ) ); String keyScope = MPKeyPurpose.Authentication.getScope(); logger.trc( "keyScope: %s", keyScope ); @@ -63,15 +57,13 @@ public class MPAlgorithmV3 extends MPAlgorithmV2 { logger.trc( " => masterKeySalt.id: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) ); // Calculate the master key. - logger.trc( "masterKey: scrypt( masterPassword, masterKeySalt, N=%lu, r=%u, p=%u )", + logger.trc( "masterKey: scrypt( masterPassword, masterKeySalt, N=%d, r=%d, p=%d )", MPAlgorithmV0.scrypt_N, MPAlgorithmV0.scrypt_r, MPAlgorithmV0.scrypt_p ); - byte[] mpBytes = new byte[mpBytesBuf.remaining()]; - mpBytesBuf.get( mpBytes, 0, mpBytes.length ); - Arrays.fill( mpBytesBuf.array(), (byte) 0 ); - byte[] masterKey = scrypt( masterKeySalt, mpBytes ); // TODO: Why not mpBytesBuf.array()? + byte[] mpBytes = bytesForChars( masterPassword ); + byte[] masterKey = scrypt( masterKeySalt, mpBytes ); Arrays.fill( masterKeySalt, (byte) 0 ); Arrays.fill( mpBytes, (byte) 0 ); - logger.trc( " => masterKey.id: %s", (Object) idForBytes( masterKey ) ); + logger.trc( " => masterKey.id: %s", CodeUtils.encodeHex( idForBytes( masterKey ) ) ); return masterKey; } diff --git a/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPMasterKey.java b/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPMasterKey.java index e2f23589..5a386367 100644 --- a/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPMasterKey.java +++ b/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPMasterKey.java @@ -18,9 +18,11 @@ package com.lyndir.masterpassword; -import static com.lyndir.masterpassword.MPUtils.idForBytes; +import static com.lyndir.masterpassword.MPUtils.*; +import com.google.common.base.Preconditions; import com.google.common.primitives.UnsignedInteger; +import com.lyndir.lhunath.opal.system.CodeUtils; import com.lyndir.lhunath.opal.system.logging.Logger; import java.util.Arrays; import java.util.EnumMap; @@ -53,6 +55,51 @@ public class MPMasterKey { this.masterPassword = masterPassword; } + /** + * Derive the master key for a user based on their name and master password. + * + * @throws MPInvalidatedException {@link #invalidate()} has been called on this object. + */ + private byte[] masterKey(final Version algorithmVersion) + throws MPInvalidatedException { + Preconditions.checkArgument( masterPassword.length > 0 ); + + if (invalidated) + throw new MPInvalidatedException(); + + byte[] key = keyByVersion.get( algorithmVersion ); + if (key == null) { + logger.trc( "-- mpw_masterKey (algorithm: %d)", algorithmVersion.toInt() ); + logger.trc( "fullName: %s", fullName ); + logger.trc( "masterPassword.id: %s", CodeUtils.encodeHex( idForBytes( bytesForChars( masterPassword ) ) ) ); + + keyByVersion.put( algorithmVersion, key = algorithmVersion.getAlgorithm().masterKey( fullName, masterPassword ) ); + } + + return key; + } + + /** + * Derive the master key for a user based on their name and master password. + * + * @throws MPInvalidatedException {@link #invalidate()} has been called on this object. + */ + private byte[] siteKey(final String siteName, final UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose, + @Nullable final String keyContext, final Version algorithmVersion) + throws MPInvalidatedException { + Preconditions.checkArgument( !siteName.isEmpty() ); + + byte[] masterKey = masterKey( algorithmVersion ); + + logger.trc( "-- mpw_siteKey (algorithm: %d)", algorithmVersion.toInt() ); + logger.trc( "siteName: %s", siteName ); + logger.trc( "siteCounter: %s", siteCounter ); + logger.trc( "keyPurpose: %d (%s)", keyPurpose.toInt(), keyPurpose.getShortName() ); + logger.trc( "keyContext: %s", keyContext ); + + return algorithmVersion.getAlgorithm().siteKey( masterKey, siteName, siteCounter, keyPurpose, keyContext ); + } + /** * Generate a site result token. * @@ -70,8 +117,16 @@ public class MPMasterKey { @Nullable final String keyContext, final MPResultType resultType, @Nullable final String resultParam, final Version algorithmVersion) throws MPInvalidatedException { + + byte[] masterKey = masterKey( algorithmVersion ); + byte[] siteKey = siteKey( siteName, siteCounter, keyPurpose, keyContext, algorithmVersion ); + + logger.trc( "-- mpw_siteResult (algorithm: %d)", algorithmVersion.toInt() ); + logger.trc( "resultType: %d (%s)", resultType.getType(), resultType.getShortName() ); + logger.trc( "resultParam: %s", resultParam ); + return algorithmVersion.getAlgorithm().siteResult( - getKey( algorithmVersion ), siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam ); + masterKey, siteKey, siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam ); } /** @@ -91,8 +146,19 @@ public class MPMasterKey { @Nullable final String keyContext, final MPResultType resultType, @Nullable final String resultParam, final Version algorithmVersion) throws MPInvalidatedException { + + Preconditions.checkNotNull( resultParam ); + Preconditions.checkArgument( !resultParam.isEmpty() ); + + byte[] masterKey = masterKey( algorithmVersion ); + byte[] siteKey = siteKey( siteName, siteCounter, keyPurpose, keyContext, algorithmVersion ); + + logger.trc( "-- mpw_siteState (algorithm: %d)", algorithmVersion.toInt() ); + logger.trc( "resultType: %d (%s)", resultType.getType(), resultType.getShortName() ); + logger.trc( "resultParam: %s", resultParam ); + return algorithmVersion.getAlgorithm().siteState( - getKey( algorithmVersion ), siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam ); + masterKey, siteKey, siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam ); } @Nonnull @@ -109,7 +175,7 @@ public class MPMasterKey { public byte[] getKeyID(final Version algorithmVersion) throws MPInvalidatedException { - return idForBytes( getKey( algorithmVersion ) ); + return idForBytes( masterKey( algorithmVersion ) ); } /** @@ -123,18 +189,6 @@ public class MPMasterKey { Arrays.fill( masterPassword, (char) 0 ); } - private byte[] getKey(final Version algorithmVersion) - throws MPInvalidatedException { - if (invalidated) - throw new MPInvalidatedException(); - - byte[] key = keyByVersion.get( algorithmVersion ); - if (key == null) - keyByVersion.put( algorithmVersion, key = algorithmVersion.getAlgorithm().deriveKey( fullName, masterPassword ) ); - - return key; - } - /** * The algorithm iterations. */ diff --git a/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPResultType.java b/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPResultType.java index bf28ca96..1a3ce5eb 100644 --- a/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPResultType.java +++ b/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPResultType.java @@ -37,7 +37,7 @@ public enum MPResultType { /** * pg^VMAUBk5x3p%HP%i4= */ - GeneratedMaximum( "Maximum", "20 characters, contains symbols.", // + GeneratedMaximum( "maximum", "20 characters, contains symbols.", // ImmutableList.of( new MPTemplate( "anoxxxxxxxxxxxxxxxxx" ), new MPTemplate( "axxxxxxxxxxxxxxxxxno" ) ), // MPResultTypeClass.Template, 0x0 ), @@ -45,7 +45,7 @@ public enum MPResultType { /** * BiroYena8:Kixa */ - GeneratedLong( "Long", "Copy-friendly, 14 characters, contains symbols.", // + GeneratedLong( "long", "Copy-friendly, 14 characters, contains symbols.", // ImmutableList.of( new MPTemplate( "CvcvnoCvcvCvcv" ), new MPTemplate( "CvcvCvcvnoCvcv" ), new MPTemplate( "CvcvCvcvCvcvno" ), new MPTemplate( "CvccnoCvcvCvcv" ), new MPTemplate( "CvccCvcvnoCvcv" ), new MPTemplate( "CvccCvcvCvcvno" ), @@ -62,7 +62,7 @@ public enum MPResultType { /** * BirSuj0- */ - GeneratedMedium( "Medium", "Copy-friendly, 8 characters, contains symbols.", // + GeneratedMedium( "medium", "Copy-friendly, 8 characters, contains symbols.", // ImmutableList.of( new MPTemplate( "CvcnoCvc" ), new MPTemplate( "CvcCvcno" ) ), // MPResultTypeClass.Template, 0x2 ), @@ -70,7 +70,7 @@ public enum MPResultType { /** * pO98MoD0 */ - GeneratedBasic( "Basic", "8 characters, no symbols.", // + GeneratedBasic( "basic", "8 characters, no symbols.", // ImmutableList.of( new MPTemplate( "aaanaaan" ), new MPTemplate( "aannaaan" ), new MPTemplate( "aaannaaa" ) ), // @@ -79,28 +79,28 @@ public enum MPResultType { /** * Bir8 */ - GeneratedShort( "Short", "Copy-friendly, 4 characters, no symbols.", // + GeneratedShort( "short", "Copy-friendly, 4 characters, no symbols.", // ImmutableList.of( new MPTemplate( "Cvcn" ) ), // MPResultTypeClass.Template, 0x4 ), /** * 2798 */ - GeneratedPIN( "PIN", "4 numbers.", // + GeneratedPIN( "pin", "4 numbers.", // ImmutableList.of( new MPTemplate( "nnnn" ) ), // MPResultTypeClass.Template, 0x5 ), /** * birsujano */ - GeneratedName( "Name", "9 letter name.", // + GeneratedName( "name", "9 letter name.", // ImmutableList.of( new MPTemplate( "cvccvcvcv" ) ), // MPResultTypeClass.Template, 0xE ), /** * bir yennoquce fefi */ - GeneratedPhrase( "Phrase", "20 character sentence.", // + GeneratedPhrase( "phrase", "20 character sentence.", // ImmutableList.of( new MPTemplate( "cvcc cvc cvccvcv cvc" ), new MPTemplate( "cvc cvccvcvcv cvcv" ), new MPTemplate( "cv cvccv cvc cvcvccv" ) ), // @@ -109,25 +109,25 @@ public enum MPResultType { /** * Custom saved password. */ - StoredPersonal( "Personal", "AES-encrypted, exportable.", // + StoredPersonal( "personal", "AES-encrypted, exportable.", // ImmutableList.of(), // MPResultTypeClass.Stateful, 0x0, MPSiteFeature.ExportContent ), /** * Custom saved password that should not be exported from the device. */ - StoredDevicePrivate( "Device", "AES-encrypted, not exported.", // + StoredDevicePrivate( "device", "AES-encrypted, not exported.", // ImmutableList.of(), // MPResultTypeClass.Stateful, 0x1, MPSiteFeature.DevicePrivate ), /** * Derive a unique binary key. */ - DeriveKey( "Key", "Encryption key.", // + DeriveKey( "key", "Encryption key.", // ImmutableList.of(), // MPResultTypeClass.Derive, 0x0, MPSiteFeature.Alternative ); - public static MPResultType DEFAULT = GeneratedLong; + public static final MPResultType DEFAULT = GeneratedLong; static final Logger logger = Logger.get( MPResultType.class ); @@ -246,16 +246,6 @@ public enum MPResultType { return types.build(); } - public static MPResultType forInt(final int resultType) { - - return values()[resultType]; - } - - public int toInt() { - - return ordinal(); - } - public MPTemplate getTemplateAtRollingIndex(final int templateIndex) { return templates.get( templateIndex % templates.size() ); } diff --git a/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPUtils.java b/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPUtils.java index 6b50f0da..eb263abd 100644 --- a/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPUtils.java +++ b/core/java/algorithm/src/main/java/com/lyndir/masterpassword/MPUtils.java @@ -20,6 +20,8 @@ package com.lyndir.masterpassword; import com.google.common.primitives.UnsignedInteger; import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.util.Arrays; /** @@ -35,6 +37,16 @@ public final class MPUtils { return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( MPAlgorithmV0.mpw_byteOrder ).putInt( number.intValue() ).array(); } + public static byte[] bytesForChars(final char[] characters) { + ByteBuffer byteBuffer = MPAlgorithmV0.mpw_charset.encode( CharBuffer.wrap( characters ) ); + + byte[] bytes = new byte[byteBuffer.remaining()]; + byteBuffer.get( bytes ); + + Arrays.fill( byteBuffer.array(), (byte) 0 ); + return bytes; + } + public static byte[] idForBytes(final byte[] bytes) { return MPAlgorithmV0.mpw_hash.of( bytes ); } diff --git a/core/java/tests/src/main/java/com/lyndir/masterpassword/MPTestSuite.java b/core/java/tests/src/main/java/com/lyndir/masterpassword/MPTestSuite.java index 9db579d9..63f5209d 100644 --- a/core/java/tests/src/main/java/com/lyndir/masterpassword/MPTestSuite.java +++ b/core/java/tests/src/main/java/com/lyndir/masterpassword/MPTestSuite.java @@ -171,7 +171,7 @@ public class MPTestSuite implements Callable { @Override public boolean run(final MPTests.Case testCase) throws Exception { - MPMasterKey masterKey = new MPMasterKey( testCase.getFullName(), testCase.getMasterPassword() ); + MPMasterKey masterKey = new MPMasterKey( testCase.getFullName(), testCase.getMasterPassword().toCharArray() ); String sitePassword = masterKey.siteResult( testCase.getSiteName(), testCase.getSiteCounter(), testCase.getKeyPurpose(), testCase.getKeyContext(), testCase.getResultType(), null, testCase.getAlgorithm() ); diff --git a/core/java/tests/src/main/java/com/lyndir/masterpassword/MPTests.java b/core/java/tests/src/main/java/com/lyndir/masterpassword/MPTests.java index c2e5dc9e..54670904 100644 --- a/core/java/tests/src/main/java/com/lyndir/masterpassword/MPTests.java +++ b/core/java/tests/src/main/java/com/lyndir/masterpassword/MPTests.java @@ -181,8 +181,8 @@ public class MPTests { } @Nonnull - public char[] getMasterPassword() { - return checkNotNull( masterPassword ).toCharArray(); + public String getMasterPassword() { + return checkNotNull( masterPassword ); } @Nonnull diff --git a/core/java/tests/src/test/java/com/lyndir/masterpassword/MPMasterKeyTest.java b/core/java/tests/src/test/java/com/lyndir/masterpassword/MPMasterKeyTest.java index a9b476ce..66e8d3b2 100644 --- a/core/java/tests/src/test/java/com/lyndir/masterpassword/MPMasterKeyTest.java +++ b/core/java/tests/src/test/java/com/lyndir/masterpassword/MPMasterKeyTest.java @@ -22,7 +22,7 @@ import static org.testng.Assert.*; import com.lyndir.lhunath.opal.system.CodeUtils; import com.lyndir.lhunath.opal.system.logging.Logger; -import org.jetbrains.annotations.NonNls; +import java.util.Random; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -32,7 +32,6 @@ public class MPMasterKeyTest { @SuppressWarnings("UnusedDeclaration") private static final Logger logger = Logger.get( MPMasterKeyTest.class ); - @NonNls private MPTestSuite testSuite; @BeforeMethod @@ -43,20 +42,58 @@ public class MPMasterKeyTest { } @Test - public void testEncode() + public void testMasterKey() throws Exception { - testSuite.forEach( "testEncode", new MPTestSuite.TestCase() { + testSuite.forEach( "testMasterKey", new MPTestSuite.TestCase() { @Override public boolean run(final MPTests.Case testCase) throws Exception { - MPMasterKey masterKey = new MPMasterKey( testCase.getFullName(), testCase.getMasterPassword() ); + char[] masterPassword = testCase.getMasterPassword().toCharArray(); + MPMasterKey masterKey = new MPMasterKey( testCase.getFullName(), masterPassword ); + // Test key + assertEquals( + CodeUtils.encodeHex( masterKey.getKeyID( testCase.getAlgorithm() ) ), + testCase.getKeyID(), + "[testMasterKey] keyID mismatch: " + testCase ); + + // Test invalidation + masterKey.invalidate(); + try { + masterKey.getKeyID( testCase.getAlgorithm() ); + fail( "[testMasterKey] invalidate ineffective: " + testCase ); + } + catch (final MPInvalidatedException ignored) { + } + assertNotEquals( + masterPassword, + testCase.getMasterPassword().toCharArray(), + "[testMasterKey] masterPassword not wiped: " + testCase ); + + return true; + } + } ); + } + + @Test + public void testSiteResult() + throws Exception { + + testSuite.forEach( "testSiteResult", new MPTestSuite.TestCase() { + @Override + public boolean run(final MPTests.Case testCase) + throws Exception { + char[] masterPassword = testCase.getMasterPassword().toCharArray(); + MPMasterKey masterKey = new MPMasterKey( testCase.getFullName(), masterPassword ); + + // Test site result assertEquals( masterKey.siteResult( testCase.getSiteName(), testCase.getSiteCounter(), testCase.getKeyPurpose(), testCase.getKeyContext(), testCase.getResultType(), null, testCase.getAlgorithm() ), - testCase.getResult(), "[testEncode] Failed test case: " + testCase ); + testCase.getResult(), + "[testSiteResult] result mismatch: " + testCase ); return true; } @@ -64,30 +101,30 @@ public class MPMasterKeyTest { } @Test - public void testGetUserName() + public void testSiteState() throws Exception { - MPTests.Case defaultCase = testSuite.getTests().getDefaultCase(); + MPTests.Case testCase = testSuite.getTests().getDefaultCase(); + char[] masterPassword = testCase.getMasterPassword().toCharArray(); + MPMasterKey masterKey = new MPMasterKey( testCase.getFullName(), masterPassword ); - assertEquals( new MPMasterKey( defaultCase.getFullName(), defaultCase.getMasterPassword() ).getFullName(), - defaultCase.getFullName(), "[testGetUserName] Failed test case: " + defaultCase ); - } + Random random = new Random(); + StringBuilder password = new StringBuilder(); + for (int p = 0; p < 8; ++p) + password.append( (char) (random.nextInt( Character.MAX_CODE_POINT - Character.MIN_CODE_POINT ) + Character.MIN_CODE_POINT) ); - @Test - public void testGetKeyID() - throws Exception { + for (final MPMasterKey.Version version : MPMasterKey.Version.values()) { + MPResultType resultType = MPResultType.StoredPersonal; - testSuite.forEach( "testGetKeyID", new MPTestSuite.TestCase() { - @Override - public boolean run(final MPTests.Case testCase) - throws Exception { - MPMasterKey masterKey = new MPMasterKey( testCase.getFullName(), testCase.getMasterPassword() ); + // Test site state + String state = masterKey.siteState( testCase.getSiteName(), testCase.getSiteCounter(), testCase.getKeyPurpose(), + testCase.getKeyContext(), resultType, password.toString(), version ); - assertEquals( CodeUtils.encodeHex( masterKey.getKeyID( testCase.getAlgorithm() ) ), - testCase.getKeyID(), "[testGetKeyID] Failed test case: " + testCase ); - - return true; - } - } ); + assertEquals( + masterKey.siteResult( testCase.getSiteName(), testCase.getSiteCounter(), testCase.getKeyPurpose(), + testCase.getKeyContext(), resultType, state, version ), + password.toString(), + "[testSiteState] state mismatch: " + testCase ); + } } } diff --git a/core/java/tests/src/test/resources/logback.xml b/core/java/tests/src/test/resources/logback.xml index 42815ef6..65d96765 100644 --- a/core/java/tests/src/test/resources/logback.xml +++ b/core/java/tests/src/test/resources/logback.xml @@ -6,9 +6,9 @@ - + - + diff --git a/gradle/.idea/runConfigurations/GUI.xml b/gradle/.idea/runConfigurations/GUI.xml index aac22091..7c0766a3 100644 --- a/gradle/.idea/runConfigurations/GUI.xml +++ b/gradle/.idea/runConfigurations/GUI.xml @@ -1,5 +1,5 @@ - + diff --git a/gradle/.idea/runConfigurations/Tests.xml b/gradle/.idea/runConfigurations/Tests.xml index cf544362..ee3efb8f 100644 --- a/gradle/.idea/runConfigurations/Tests.xml +++ b/gradle/.idea/runConfigurations/Tests.xml @@ -1,6 +1,6 @@ - - + +