diff --git a/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MPSiteType.java b/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MPSiteType.java index 10eb56bc..b6f31eae 100644 --- a/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MPSiteType.java +++ b/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MPSiteType.java @@ -6,6 +6,7 @@ import com.lyndir.lhunath.opal.system.logging.Logger; import java.util.List; import java.util.Set; import javax.annotation.Nullable; +import org.jetbrains.annotations.Contract; /** @@ -147,6 +148,7 @@ public enum MPSiteType { * * @return The type registered with the given name. */ + @Contract("!null -> !null, null -> null") public static MPSiteType forName(@Nullable final String name) { if (name == null) diff --git a/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MPSiteVariant.java b/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MPSiteVariant.java index 45213857..b6039f4b 100644 --- a/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MPSiteVariant.java +++ b/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MPSiteVariant.java @@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableList; import com.lyndir.lhunath.opal.system.logging.Logger; import java.util.List; import javax.annotation.Nullable; +import org.jetbrains.annotations.Contract; /** @@ -66,6 +67,7 @@ public enum MPSiteVariant { * * @return The variant registered with the given name. */ + @Contract("!null -> !null, null -> null") public static MPSiteVariant forName(@Nullable final String name) { if (name == null) diff --git a/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MasterKeyV0.java b/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MasterKeyV0.java index c5061be8..c5671911 100644 --- a/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MasterKeyV0.java +++ b/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MasterKeyV0.java @@ -89,29 +89,37 @@ public class MasterKeyV0 extends MasterKey { byte[] siteNameBytes = siteName.getBytes( MP_charset ); byte[] siteNameLengthBytes = bytesForInt( siteName.length() ); byte[] siteCounterBytes = bytesForInt( siteCounter ); - byte[] siteContextBytes = siteContext == null? null: siteContext.getBytes( MP_charset ); + byte[] siteContextBytes = siteContext == null || siteContext.isEmpty()? null: siteContext.getBytes( MP_charset ); byte[] siteContextLengthBytes = bytesForInt( siteContextBytes == null? 0: siteContextBytes.length ); - logger.trc( "site scope: %s, context: %s", siteScope, siteContext == null? "": siteContext ); + logger.trc( "site scope: %s, context: %s", siteScope, siteContextBytes == null? "": siteContext ); logger.trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)", siteScope, CodeUtils.encodeHex( siteNameLengthBytes ), siteName, CodeUtils.encodeHex( siteCounterBytes ), CodeUtils.encodeHex( siteContextLengthBytes ), - siteContext == null? "(null)": siteContext ); + siteContextBytes == null? "(null)": siteContext ); byte[] sitePasswordInfo = Bytes.concat( siteScope.getBytes( MP_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes ); if (siteContextBytes != null) sitePasswordInfo = Bytes.concat( sitePasswordInfo, siteContextLengthBytes, siteContextBytes ); logger.trc( "sitePasswordInfo ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) ); - byte[] sitePasswordSeed = MP_mac.of( getKey(), sitePasswordInfo ); - logger.trc( "sitePasswordSeed ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordSeed ) ) ); + byte[] sitePasswordSeedBytes = MP_mac.of( getKey(), sitePasswordInfo ); + int[] sitePasswordSeed = new int[sitePasswordSeedBytes.length]; + for (int i = 0; i < sitePasswordSeedBytes.length; ++i) { + ByteBuffer buf = ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( ByteOrder.BIG_ENDIAN ); + Arrays.fill( buf.array(), sitePasswordSeedBytes[i] > 0? (byte)0x00: (byte) 0xFF ); + buf.position( 2 ); + buf.put( sitePasswordSeedBytes[i] ).rewind(); + sitePasswordSeed[i] = buf.getInt() & 0xFFFF; + } + logger.trc( "sitePasswordSeed ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordSeedBytes ) ) ); Preconditions.checkState( sitePasswordSeed.length > 0 ); - int templateIndex = sitePasswordSeed[0] & 0xFFFF; + int templateIndex = sitePasswordSeed[0]; MPTemplate template = siteType.getTemplateAtRollingIndex( templateIndex ); logger.trc( "type %s, template: %s", siteType, template.getTemplateString() ); StringBuilder password = new StringBuilder( template.length() ); for (int i = 0; i < template.length(); ++i) { - int characterIndex = sitePasswordSeed[i + 1] & 0xFFFF; + int characterIndex = sitePasswordSeed[i + 1]; MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i ); char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex ); logger.trc( "class %c, index %d (0x%02X) -> character: %c", characterClass.getIdentifier(), characterIndex, diff --git a/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MasterKeyV1.java b/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MasterKeyV1.java index 2d1abb62..bcb9d58d 100644 --- a/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MasterKeyV1.java +++ b/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MasterKeyV1.java @@ -46,12 +46,12 @@ public class MasterKeyV1 extends MasterKeyV0 { byte[] siteNameBytes = siteName.getBytes( MP_charset ); byte[] siteNameLengthBytes = bytesForInt( siteName.length() ); byte[] siteCounterBytes = bytesForInt( siteCounter ); - byte[] siteContextBytes = siteContext == null? null: siteContext.getBytes( MP_charset ); + byte[] siteContextBytes = siteContext == null || siteContext.isEmpty()? null: siteContext.getBytes( MP_charset ); byte[] siteContextLengthBytes = bytesForInt( siteContextBytes == null? 0: siteContextBytes.length ); - logger.trc( "site scope: %s, context: %s", siteScope, siteContext == null? "": siteContext ); + logger.trc( "site scope: %s, context: %s", siteScope, siteContextBytes == null? "": siteContext ); logger.trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)", siteScope, CodeUtils.encodeHex( siteNameLengthBytes ), siteName, CodeUtils.encodeHex( siteCounterBytes ), CodeUtils.encodeHex( siteContextLengthBytes ), - siteContext == null? "(null)": siteContext ); + siteContextBytes == null? "(null)": siteContext ); byte[] sitePasswordInfo = Bytes.concat( siteScope.getBytes( MP_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes ); if (siteContextBytes != null) diff --git a/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MasterKeyV2.java b/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MasterKeyV2.java index 9b4945d9..7da2856a 100644 --- a/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MasterKeyV2.java +++ b/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MasterKeyV2.java @@ -45,12 +45,12 @@ public class MasterKeyV2 extends MasterKeyV1 { byte[] siteNameBytes = siteName.getBytes( MP_charset ); byte[] siteNameLengthBytes = bytesForInt( siteNameBytes.length ); byte[] siteCounterBytes = bytesForInt( siteCounter ); - byte[] siteContextBytes = siteContext == null? null: siteContext.getBytes( MP_charset ); + byte[] siteContextBytes = siteContext == null || siteContext.isEmpty()? null: siteContext.getBytes( MP_charset ); byte[] siteContextLengthBytes = bytesForInt( siteContextBytes == null? 0: siteContextBytes.length ); - logger.trc( "site scope: %s, context: %s", siteScope, siteContext == null? "": siteContext ); + logger.trc( "site scope: %s, context: %s", siteScope, siteContextBytes == null? "": siteContext ); logger.trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)", siteScope, CodeUtils.encodeHex( siteNameLengthBytes ), siteName, CodeUtils.encodeHex( siteCounterBytes ), CodeUtils.encodeHex( siteContextLengthBytes ), - siteContext == null? "(null)": siteContext ); + siteContextBytes == null? "(null)": siteContext ); byte[] sitePasswordInfo = Bytes.concat( siteScope.getBytes( MP_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes ); if (siteContextBytes != null) diff --git a/MasterPassword/Java/masterpassword-algorithm/src/test/java/com/lyndir/masterpassword/MPWTests.java b/MasterPassword/Java/masterpassword-algorithm/src/test/java/com/lyndir/masterpassword/MPWTests.java index db908d09..5673bda6 100644 --- a/MasterPassword/Java/masterpassword-algorithm/src/test/java/com/lyndir/masterpassword/MPWTests.java +++ b/MasterPassword/Java/masterpassword-algorithm/src/test/java/com/lyndir/masterpassword/MPWTests.java @@ -1,10 +1,10 @@ package com.lyndir.masterpassword; +import static com.google.common.base.Preconditions.checkNotNull; import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*; import com.lyndir.lhunath.opal.system.logging.Logger; -import com.lyndir.lhunath.opal.system.util.NNSupplier; -import com.lyndir.lhunath.opal.system.util.NSupplier; +import com.lyndir.lhunath.opal.system.util.*; import java.util.List; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -25,8 +25,9 @@ public class MPWTests { @XmlElement(name = "case") private List cases; + @Nonnull public List getCases() { - return cases; + return checkNotNull( cases ); } public Case getCase(String identifier) { @@ -45,6 +46,8 @@ public class MPWTests { @XmlAttribute private String parent; @XmlElement + private String algorithm; + @XmlElement private String fullName; @XmlElement private String masterPassword; @@ -65,76 +68,86 @@ public class MPWTests { private transient Case parentCase; - public void setTests(MPWTests tests) { + public void initializeParentHierarchy(MPWTests tests) { if (parent != null) { parentCase = tests.getCase( parent ); - fullName = ifNotNullElse( fullName, new NNSupplier() { - @Nonnull - @Override - public String get() { - return parentCase.getFullName(); - } - } ); - masterPassword = ifNotNullElse( masterPassword, new NNSupplier() { - @Nonnull - @Override - public String get() { - return new String( parentCase.getMasterPassword() ); - } - } ); - keyID = ifNotNullElse( keyID, new NNSupplier() { - @Nonnull - @Override - public String get() { - return parentCase.getKeyID(); - } - } ); - siteName = ifNotNullElse( siteName, new NNSupplier() { - @Nonnull - @Override - public String get() { - return parentCase.getSiteName(); - } - } ); - siteCounter = ifNotNullElse( siteCounter, new NNSupplier() { - @Nonnull - @Override - public Integer get() { - return parentCase.getSiteCounter(); - } - } ); - siteType = ifNotNullElse( siteType, new NNSupplier() { - @Nonnull - @Override - public String get() { - return parentCase.getSiteType().name(); - } - } ); - siteVariant = ifNotNullElse( siteVariant, new NNSupplier() { - @Nonnull - @Override - public String get() { - return parentCase.getSiteVariant().name(); - } - } ); - siteContext = ifNotNullElseNullable( siteContext, new NSupplier() { - @Nonnull - @Override - public String get() { - return parentCase.getSiteContext(); - } - } ); - result = ifNotNullElse( result, new NNSupplier() { - @Nonnull - @Override - public String get() { - return parentCase.getResult(); - } - } ); + parentCase.initializeParentHierarchy( tests ); } + + algorithm = ifNotNullElse( algorithm, new NNSupplier() { + @Nonnull + @Override + public String get() { + return checkNotNull( parentCase.algorithm ); + } + } ); + fullName = ifNotNullElse( fullName, new NNSupplier() { + @Nonnull + @Override + public String get() { + return checkNotNull( parentCase.fullName ); + } + } ); + masterPassword = ifNotNullElse( masterPassword, new NNSupplier() { + @Nonnull + @Override + public String get() { + return checkNotNull( parentCase.masterPassword ); + } + } ); + keyID = ifNotNullElse( keyID, new NNSupplier() { + @Nonnull + @Override + public String get() { + return checkNotNull( parentCase.keyID ); + } + } ); + siteName = ifNotNullElse( siteName, new NNSupplier() { + @Nonnull + @Override + public String get() { + return checkNotNull( parentCase.siteName ); + } + } ); + siteCounter = ifNotNullElse( siteCounter, new NNSupplier() { + @Nonnull + @Override + public Integer get() { + return checkNotNull( parentCase.siteCounter ); + } + } ); + siteType = ifNotNullElse( siteType, new NNSupplier() { + @Nonnull + @Override + public String get() { + return checkNotNull( parentCase.siteType ); + } + } ); + siteVariant = ifNotNullElse( siteVariant, new NNSupplier() { + @Nonnull + @Override + public String get() { + return checkNotNull( parentCase.siteVariant ); + } + } ); + siteContext = ifNotNullElse( siteContext, new NNSupplier() { + @Nonnull + @Override + public String get() { + return parentCase == null? "": checkNotNull( parentCase.siteContext ); + } + } ); + result = ifNotNullElse( result, new NNSupplier() { + @Nonnull + @Override + public String get() { + return parentCase == null? "": checkNotNull( parentCase.result ); + } + } ); } + @Nonnull public String getIdentifier() { return identifier; } @@ -144,40 +157,53 @@ public class MPWTests { return parentCase; } + @Nonnull + public MasterKey.Version getAlgorithm() { + return MasterKey.Version.fromInt( ConversionUtils.toIntegerNN( algorithm ) ); + } + + @Nonnull public String getFullName() { - return fullName; + return checkNotNull( fullName ); } + @Nonnull public char[] getMasterPassword() { - return masterPassword == null? null: masterPassword.toCharArray(); + return checkNotNull( masterPassword ).toCharArray(); } + @Nonnull public String getKeyID() { - return keyID; + return checkNotNull( keyID ); } + @Nonnull public String getSiteName() { - return siteName; + return checkNotNull( siteName ); } public int getSiteCounter() { return ifNotNullElse( siteCounter, 1 ); } + @Nonnull public MPSiteType getSiteType() { - return MPSiteType.forName( siteType ); + return MPSiteType.forName( checkNotNull( siteType ) ); } + @Nonnull public MPSiteVariant getSiteVariant() { - return MPSiteVariant.forName( siteVariant ); + return MPSiteVariant.forName( checkNotNull( siteVariant ) ); } + @Nonnull public String getSiteContext() { - return siteContext; + return checkNotNull( siteContext ); } + @Nonnull public String getResult() { - return result; + return checkNotNull( result ); } @Override diff --git a/MasterPassword/Java/masterpassword-algorithm/src/test/java/com/lyndir/masterpassword/MasterKeyTest.java b/MasterPassword/Java/masterpassword-algorithm/src/test/java/com/lyndir/masterpassword/MasterKeyTest.java index 6b04ac50..55978452 100644 --- a/MasterPassword/Java/masterpassword-algorithm/src/test/java/com/lyndir/masterpassword/MasterKeyTest.java +++ b/MasterPassword/Java/masterpassword-algorithm/src/test/java/com/lyndir/masterpassword/MasterKeyTest.java @@ -5,6 +5,7 @@ import static org.testng.Assert.*; import com.google.common.io.Resources; import com.lyndir.lhunath.opal.system.CodeUtils; import com.lyndir.lhunath.opal.system.logging.Logger; +import com.lyndir.lhunath.opal.system.util.StringUtils; import java.net.URL; import javax.xml.bind.JAXBContext; import org.testng.annotations.BeforeMethod; @@ -26,7 +27,7 @@ public class MasterKeyTest { URL testCasesResource = Resources.getResource( "mpw_tests.xml" ); tests = (MPWTests) JAXBContext.newInstance( MPWTests.class ).createUnmarshaller().unmarshal( testCasesResource ); for (MPWTests.Case testCase : tests.getCases()) - testCase.setTests( tests ); + testCase.initializeParentHierarchy( tests ); defaultCase = tests.getCase( MPWTests.ID_DEFAULT ); } @@ -35,10 +36,15 @@ public class MasterKeyTest { throws Exception { for (MPWTests.Case testCase : tests.getCases()) { - MasterKey masterKey = MasterKey.create( testCase.getFullName(), testCase.getMasterPassword() ); + if (testCase.getResult().isEmpty()) + continue; + + logger.inf( "Running test case: %s [testEncode]", testCase.getIdentifier() ); + MasterKey masterKey = MasterKey.create( testCase.getAlgorithm(), testCase.getFullName(), testCase.getMasterPassword() ); assertEquals( masterKey.encode( testCase.getSiteName(), testCase.getSiteType(), testCase.getSiteCounter(), testCase.getSiteVariant(), testCase.getSiteContext() ), testCase.getResult(), "Failed test case: " + testCase ); + logger.inf( "passed!" ); } } @@ -55,8 +61,13 @@ public class MasterKeyTest { throws Exception { for (MPWTests.Case testCase : tests.getCases()) { + if (testCase.getResult().isEmpty()) + continue; + + logger.inf( "Running test case: %s [testGetKeyID]", testCase.getIdentifier() ); MasterKey masterKey = MasterKey.create( testCase.getFullName(), testCase.getMasterPassword() ); assertEquals( CodeUtils.encodeHex( masterKey.getKeyID() ), testCase.getKeyID(), "Failed test case: " + testCase ); + logger.inf( "passed!" ); } } diff --git a/MasterPassword/Java/masterpassword-algorithm/src/test/resources/mpw_tests.xml b/MasterPassword/Java/masterpassword-algorithm/src/test/resources/mpw_tests.xml index 09ca5a21..86d24fc7 100644 --- a/MasterPassword/Java/masterpassword-algorithm/src/test/resources/mpw_tests.xml +++ b/MasterPassword/Java/masterpassword-algorithm/src/test/resources/mpw_tests.xml @@ -1,5 +1,7 @@ + + Robert Lee Mitchell banana colored duckling 98EEF4D1DF46D849574A82A03C3177056B15DFFCA29BB3899DE4628453675302 @@ -7,67 +9,271 @@ 1 GeneratedLong Password + + + + + + 3 Jejr5[RepuSosp - + 1717AA1F9BF5BA56CD0965CDA3D78E6D2E6A1EA8C067A8EA621F3DDAD4A87EB8 NopaDajh8=Fene - + 351432B8528A5ABECAB768CA95015097DE76FE14C41E10AF36C67DCFB8917E08 QesuHirv5-Xepl - + LiheCuwhSerz6) - + Login GeneratedName wohzaqage - + Answer GeneratedPhrase xin diyjiqoja hubu - + question xogx tem cegyiva jab - + GeneratedMaximum W6@692^B1#&@gVdSdLZ@ - + GeneratedMedium Jej2$Quv - + GeneratedBasic WAo2xIg6 - + GeneratedShort Jej2 - + GeneratedPIN 7662 - + GeneratedName jejraquvo - + GeneratedPhrase jejr quv cabsibu tam - + 4294967295 XambHoqo6[Peni + + + + 2 + Jejr5[RepuSosp + + + + 1717AA1F9BF5BA56CD0965CDA3D78E6D2E6A1EA8C067A8EA621F3DDAD4A87EB8 + WaqoGuho2[Xaxw + + + + 351432B8528A5ABECAB768CA95015097DE76FE14C41E10AF36C67DCFB8917E08 + QesuHirv5-Xepl + + + + LiheCuwhSerz6) + + + Login + GeneratedName + wohzaqage + + + Answer + GeneratedPhrase + xin diyjiqoja hubu + + + question + xogx tem cegyiva jab + + + GeneratedMaximum + W6@692^B1#&@gVdSdLZ@ + + + GeneratedMedium + Jej2$Quv + + + GeneratedBasic + WAo2xIg6 + + + GeneratedShort + Jej2 + + + GeneratedPIN + 7662 + + + GeneratedName + jejraquvo + + + GeneratedPhrase + jejr quv cabsibu tam + + + 4294967295 + XambHoqo6[Peni + + + + + 1 + Jejr5[RepuSosp + + + + 1717AA1F9BF5BA56CD0965CDA3D78E6D2E6A1EA8C067A8EA621F3DDAD4A87EB8 + WaqoGuho2[Xaxw + + + + 351432B8528A5ABECAB768CA95015097DE76FE14C41E10AF36C67DCFB8917E08 + QesuHirv5-Xepl + + + + WawiYarp2@Kodh + + + Login + GeneratedName + wohzaqage + + + Answer + GeneratedPhrase + xin diyjiqoja hubu + + + question + xogx tem cegyiva jab + + + GeneratedMaximum + W6@692^B1#&@gVdSdLZ@ + + + GeneratedMedium + Jej2$Quv + + + GeneratedBasic + WAo2xIg6 + + + GeneratedShort + Jej2 + + + GeneratedPIN + 7662 + + + GeneratedName + jejraquvo + + + GeneratedPhrase + jejr quv cabsibu tam + + + 4294967295 + XambHoqo6[Peni + + + + + 0 + Feji5@ReduWosh + + + + 1717AA1F9BF5BA56CD0965CDA3D78E6D2E6A1EA8C067A8EA621F3DDAD4A87EB8 + HajrYudo7@Mamh + + + + 351432B8528A5ABECAB768CA95015097DE76FE14C41E10AF36C67DCFB8917E08 + MewmDini0]Meho + + + + HahiVana2@Nole + + + Login + GeneratedName + lozwajave + + + Answer + GeneratedPhrase + miy lirfijoja dubu + + + question + movm bex gevrica jaf + + + GeneratedMaximum + w1!3bA3icmRAc)SS@lwl + + + GeneratedMedium + Fej7]Jug + + + GeneratedBasic + wvH7irC1 + + + GeneratedShort + Fej7 + + + GeneratedPIN + 2117 + + + GeneratedName + fejrajugo + + + GeneratedPhrase + fejr jug gabsibu bax + + + 4294967295 + QateDojh1@Hecn +