diff --git a/MasterPassword/Java/masterpassword-algorithm/pom.xml b/MasterPassword/Java/masterpassword-algorithm/pom.xml
index 5e0b7ddb..94054f96 100644
--- a/MasterPassword/Java/masterpassword-algorithm/pom.xml
+++ b/MasterPassword/Java/masterpassword-algorithm/pom.xml
@@ -33,17 +33,24 @@
-
- net.sf.plist
- property-list
- 2.0.0
-
com.lambdaworks
scrypt
1.4.0
+
+
+ org.testng
+ testng
+ test
+
+
+ ch.qos.logback
+ logback-classic
+ test
+
+
diff --git a/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MPElementType.java b/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MPElementType.java
index aa0cf4ac..b2cc8338 100644
--- a/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MPElementType.java
+++ b/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MPElementType.java
@@ -3,7 +3,9 @@ package com.lyndir.masterpassword;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.lyndir.lhunath.opal.system.logging.Logger;
+import java.util.List;
import java.util.Set;
+import javax.annotation.Generated;
/**
@@ -13,33 +15,72 @@ import java.util.Set;
*/
public enum MPElementType {
- GeneratedMaximum( "Maximum Security Password", "Maximum", "20 characters, contains symbols.", MPElementTypeClass.Generated ),
- GeneratedLong( "Long Password", "Long", "Copy-friendly, 14 characters, contains symbols.", MPElementTypeClass.Generated ),
- GeneratedMedium( "Medium Password", "Medium", "Copy-friendly, 8 characters, contains symbols.", MPElementTypeClass.Generated ),
- GeneratedBasic( "Basic Password", "Basic", "8 characters, no symbols.", MPElementTypeClass.Generated ),
- GeneratedShort( "Short Password", "Short", "Copy-friendly, 4 characters, no symbols.", MPElementTypeClass.Generated ),
- GeneratedPIN( "PIN", "PIN", "4 numbers.", MPElementTypeClass.Generated ),
+ GeneratedMaximum( "20 characters, contains symbols.", //
+ ImmutableList.of( "x", "max", "maximum" ), MPElementTypeClass.Generated, //
+ ImmutableList.of( new MPTemplate( "anoxxxxxxxxxxxxxxxxx" ), new MPTemplate( "axxxxxxxxxxxxxxxxxno" ) ) ),
- StoredPersonal( "Personal Password", "Personal", "AES-encrypted, exportable.", MPElementTypeClass.Stored,
- MPElementFeature.ExportContent ),
- StoredDevicePrivate( "Device Private Password", "Private", "AES-encrypted, not exported.", MPElementTypeClass.Stored,
- MPElementFeature.DevicePrivate );
+ GeneratedLong( "Copy-friendly, 14 characters, contains symbols.", //
+ ImmutableList.of( "l", "long" ), MPElementTypeClass.Generated, //
+ ImmutableList.of( new MPTemplate( "CvcvnoCvcvCvcv" ), new MPTemplate( "CvcvCvcvnoCvcv" ),
+ new MPTemplate( "CvcvCvcvCvcvno" ), new MPTemplate( "CvccnoCvcvCvcv" ),
+ new MPTemplate( "CvccCvcvnoCvcv" ), new MPTemplate( "CvccCvcvCvcvno" ),
+ new MPTemplate( "CvcvnoCvccCvcv" ), new MPTemplate( "CvcvCvccnoCvcv" ),
+ new MPTemplate( "CvcvCvccCvcvno" ), new MPTemplate( "CvcvnoCvcvCvcc" ),
+ new MPTemplate( "CvcvCvcvnoCvcc" ), new MPTemplate( "CvcvCvcvCvccno" ),
+ new MPTemplate( "CvccnoCvccCvcv" ), new MPTemplate( "CvccCvccnoCvcv" ),
+ new MPTemplate( "CvccCvccCvcvno" ), new MPTemplate( "CvcvnoCvccCvcc" ),
+ new MPTemplate( "CvcvCvccnoCvcc" ), new MPTemplate( "CvcvCvccCvccno" ),
+ new MPTemplate( "CvccnoCvcvCvcc" ), new MPTemplate( "CvccCvcvnoCvcc" ),
+ new MPTemplate( "CvccCvcvCvccno" ) ) ),
+
+ GeneratedMedium( "Copy-friendly, 8 characters, contains symbols.", //
+ ImmutableList.of( "m", "med", "medium" ), MPElementTypeClass.Generated, //
+ ImmutableList.of( new MPTemplate( "CvcnoCvc" ), new MPTemplate( "CvcCvcno" ) ) ),
+
+ GeneratedBasic( "8 characters, no symbols.", //
+ ImmutableList.of( "b", "basic" ), MPElementTypeClass.Generated, //
+ ImmutableList.of( new MPTemplate( "aaanaaan" ), new MPTemplate( "aannaaan" ), new MPTemplate( "aaannaaa" ) ) ),
+
+ GeneratedShort( "Copy-friendly, 4 characters, no symbols.", //
+ ImmutableList.of( "s", "short" ), MPElementTypeClass.Generated, //
+ ImmutableList.of( new MPTemplate( "Cvcn" ) ) ),
+
+ GeneratedPIN( "4 numbers.", //
+ ImmutableList.of( "i", "pin" ), MPElementTypeClass.Generated, //
+ ImmutableList.of( new MPTemplate( "nnnn" ) ) ),
+
+ GeneratedName( "9 letter name.", //
+ ImmutableList.of( "n", "name" ), MPElementTypeClass.Generated, //
+ ImmutableList.of( new MPTemplate( "cvccvcvcv" ) ) ),
+
+ GeneratedPhrase( "20 character sentence.", //
+ ImmutableList.of( "p", "phrase" ), MPElementTypeClass.Generated, //
+ ImmutableList.of( new MPTemplate( "cvcc cvc cvccvcv cvc" ), new MPTemplate( "cvc cvccvcvcv cvcv" ),
+ new MPTemplate( "cv cvccv cvc cvcvccv" ) ) ),
+
+ StoredPersonal( "AES-encrypted, exportable.", //
+ ImmutableList.of( "personal" ), MPElementTypeClass.Stored, //
+ ImmutableList.of(), MPElementFeature.ExportContent ),
+
+ StoredDevicePrivate( "AES-encrypted, not exported.", //
+ ImmutableList.of( "device" ), MPElementTypeClass.Stored, //
+ ImmutableList.of(), MPElementFeature.DevicePrivate );
static final Logger logger = Logger.get( MPElementType.class );
- private final MPElementTypeClass typeClass;
- private final Set typeFeatures;
- private final String name;
- private final String shortName;
private final String description;
+ private final List options;
+ private final MPElementTypeClass typeClass;
+ private final List templates;
+ private final Set typeFeatures;
- MPElementType(final String name, final String shortName, final String description, final MPElementTypeClass typeClass,
- final MPElementFeature... typeFeatures) {
+ MPElementType(final String description, final List options, final MPElementTypeClass typeClass,
+ final List templates, final MPElementFeature... typeFeatures) {
- this.name = name;
- this.shortName = shortName;
- this.typeClass = typeClass;
this.description = description;
+ this.options = options;
+ this.typeClass = typeClass;
+ this.templates = templates;
ImmutableSet.Builder typeFeaturesBuilder = ImmutableSet.builder();
for (final MPElementFeature typeFeature : typeFeatures) {
@@ -48,6 +89,15 @@ public enum MPElementType {
this.typeFeatures = typeFeaturesBuilder.build();
}
+ public String getDescription() {
+
+ return description;
+ }
+
+ public List getOptions() {
+ return options;
+ }
+
public MPElementTypeClass getTypeClass() {
return typeClass;
@@ -58,33 +108,18 @@ public enum MPElementType {
return typeFeatures;
}
- public String getName() {
-
- return name;
- }
-
- public String getShortName() {
-
- return shortName;
- }
-
- public String getDescription() {
-
- return description;
- }
-
/**
- * @param name The full or short name of the type we want to look up. It is matched case insensitively.
+ * @param option The option to select a type with. It is matched case insensitively.
*
- * @return The type with the given name.
+ * @return The type registered for the given option.
*/
- public static MPElementType forName(final String name) {
+ public static MPElementType forOption(final String option) {
for (final MPElementType type : values())
- if (type.getName().equalsIgnoreCase( name ) || type.getShortName().equalsIgnoreCase( name ))
+ if (type.getOptions().contains( option.toLowerCase() ))
return type;
- throw logger.bug( "Element type not known: %s", name );
+ throw logger.bug( "No type for option: %s", option );
}
/**
@@ -102,4 +137,7 @@ public enum MPElementType {
return types.build();
}
+ public MPTemplate getTemplateAtRollingIndex(final int templateIndex) {
+ return templates.get( templateIndex % templates.size() );
+ }
}
diff --git a/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MPElementVariant.java b/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MPElementVariant.java
new file mode 100644
index 00000000..c212bd36
--- /dev/null
+++ b/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MPElementVariant.java
@@ -0,0 +1,63 @@
+package com.lyndir.masterpassword;
+
+import com.google.common.collect.ImmutableList;
+import com.lyndir.lhunath.opal.system.logging.Logger;
+import java.util.List;
+
+
+/**
+ * @author lhunath, 14-12-02
+ */
+public enum MPElementVariant {
+ Password( "The password to log in with.", "Doesn't currently use a context.", //
+ ImmutableList.of( "p", "password" ), "com.lyndir.masterpassword" ),
+ Login( "The username to log in as.", "Doesn't currently use a context.", //
+ ImmutableList.of( "l", "login" ), "com.lyndir.masterpassword.login" ),
+ Answer( "The answer to a security question.", "Empty for a universal site answer or\nthe most significant word(s) of the question.", //
+ ImmutableList.of( "a", "answer" ), "com.lyndir.masterpassword.answer" );
+
+ static final Logger logger = Logger.get( MPElementType.class );
+
+ private final String description;
+ private final String contextDescription;
+ private final List options;
+ private final String scope;
+
+ MPElementVariant(final String description, final String contextDescription, final List options, final String scope) {
+ this.contextDescription = contextDescription;
+
+ this.options = options;
+ this.description = description;
+ this.scope = scope;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public String getContextDescription() {
+ return contextDescription;
+ }
+
+ public List getOptions() {
+ return options;
+ }
+
+ public String getScope() {
+ return scope;
+ }
+
+ /**
+ * @param option The option to select a variant with. It is matched case insensitively.
+ *
+ * @return The variant registered for the given option.
+ */
+ public static MPElementVariant forOption(final String option) {
+
+ for (final MPElementVariant variant : values())
+ if (variant.getOptions().contains( option.toLowerCase() ))
+ return variant;
+
+ throw logger.bug( "No variant for option: %s", option );
+ }
+}
diff --git a/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MPTemplate.java b/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MPTemplate.java
index be28a17b..2ada4543 100644
--- a/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MPTemplate.java
+++ b/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MPTemplate.java
@@ -1,6 +1,7 @@
package com.lyndir.masterpassword;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.lyndir.lhunath.opal.system.util.MetaObject;
import java.util.List;
import java.util.Map;
@@ -15,20 +16,15 @@ public class MPTemplate extends MetaObject {
private final List template;
- public MPTemplate(final String template, final Map characterClasses) {
+ MPTemplate(final String template) {
- ImmutableList.Builder builder = ImmutableList.builder();
+ ImmutableList.Builder builder = ImmutableList.builder();
for (int i = 0; i < template.length(); ++i)
- builder.add( characterClasses.get( template.charAt( i ) ) );
+ builder.add( MPTemplateCharacterClass.forIdentifier( template.charAt( i ) ) );
this.template = builder.build();
}
- public MPTemplate(final List template) {
-
- this.template = template;
- }
-
public MPTemplateCharacterClass getCharacterClassAtIndex(final int index) {
return template.get( index );
diff --git a/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MPTemplateCharacterClass.java b/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MPTemplateCharacterClass.java
index d5116c11..2838034f 100644
--- a/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MPTemplateCharacterClass.java
+++ b/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MPTemplateCharacterClass.java
@@ -1,5 +1,6 @@
package com.lyndir.masterpassword;
+import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.MetaObject;
import com.lyndir.lhunath.opal.system.util.ObjectMeta;
@@ -9,16 +10,29 @@ import com.lyndir.lhunath.opal.system.util.ObjectMeta;
*
* @author lhunath
*/
-public class MPTemplateCharacterClass extends MetaObject {
+public enum MPTemplateCharacterClass {
+
+ UpperVowel( 'V', "AEIOU" ),
+ UpperConsonant( 'C', "BCDFGHJKLMNPQRSTVWXYZ" ),
+ LowerVowel( 'v', "aeiou" ),
+ LowerConsonant( 'c', "bcdfghjklmnpqrstvwxyz" ),
+ UpperAlphanumeric( 'A', "AEIOUBCDFGHJKLMNPQRSTVWXYZ" ),
+ Alphanumeric( 'a', "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz" ),
+ Numeric( 'n', "0123456789" ),
+ Other( 'o', "@&%?,=[]_:-+*$#!'^~;()/." ),
+ Any( 'x', "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz0123456789!@#$%^&*()" ),
+ Space( ' ', " " );
+
+ @SuppressWarnings("UnusedDeclaration")
+ private static final Logger logger = Logger.get( MPTemplateCharacterClass.class );
private final char identifier;
- @ObjectMeta(useFor = { })
private final char[] characters;
- public MPTemplateCharacterClass(final char identifier, final char[] characters) {
+ MPTemplateCharacterClass(final char identifier, final String characters) {
this.identifier = identifier;
- this.characters = characters;
+ this.characters = characters.toCharArray();
}
public char getIdentifier() {
@@ -30,4 +44,12 @@ public class MPTemplateCharacterClass extends MetaObject {
return characters[index % characters.length];
}
+
+ public static MPTemplateCharacterClass forIdentifier(final char identifier) {
+ for (MPTemplateCharacterClass characterClass : values())
+ if (characterClass.getIdentifier() == identifier)
+ return characterClass;
+
+ throw logger.bug( "No character class defined for identifier: %s", identifier );
+ }
}
diff --git a/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MPTemplates.java b/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MPTemplates.java
deleted file mode 100644
index f3378bef..00000000
--- a/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MPTemplates.java
+++ /dev/null
@@ -1,109 +0,0 @@
-package com.lyndir.masterpassword;
-
-import com.google.common.base.Preconditions;
-import com.google.common.base.Throwables;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.io.Closeables;
-import com.lyndir.lhunath.opal.system.logging.Logger;
-import com.lyndir.lhunath.opal.system.util.MetaObject;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.List;
-import java.util.Map;
-import net.sf.plist.*;
-import net.sf.plist.io.PropertyListException;
-import net.sf.plist.io.PropertyListParser;
-
-
-/**
- * 07 04, 2012
- *
- * @author lhunath
- */
-public class MPTemplates extends MetaObject {
-
- static final Logger logger = Logger.get( MPTemplates.class );
-
- private final Map> templates;
-
- public MPTemplates(final Map> templates) {
-
- this.templates = templates;
- }
-
- public static MPTemplates load() {
-
- return loadFromPList( "ciphers.plist" );
- }
-
- public static MPTemplates loadFromPList(final String templateResource) {
-
- @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
- InputStream templateStream = Thread.currentThread().getContextClassLoader().getResourceAsStream( templateResource );
- Preconditions.checkNotNull( templateStream, "Not found: %s", templateResource );
- try {
- NSObject plistObject = PropertyListParser.parse( templateStream );
- Preconditions.checkState( NSDictionary.class.isAssignableFrom( plistObject.getClass() ) );
- NSDictionary plist = (NSDictionary) plistObject;
-
- NSDictionary characterClassesDict = (NSDictionary) plist.get( "MPCharacterClasses" );
- NSDictionary templatesDict = (NSDictionary) plist.get( "MPElementGeneratedEntity" );
-
- ImmutableMap.Builder characterClassesBuilder = ImmutableMap.builder();
- for (final Map.Entry characterClassEntry : characterClassesDict.entrySet()) {
- String key = characterClassEntry.getKey();
- NSObject value = characterClassEntry.getValue();
- Preconditions.checkState( key.length() == 1 );
- Preconditions.checkState( NSString.class.isAssignableFrom( value.getClass() ));
-
- char character = key.charAt( 0 );
- char[] characterClass = ((NSString)value).getValue().toCharArray();
- characterClassesBuilder.put( character, new MPTemplateCharacterClass( character, characterClass ) );
- }
- ImmutableMap characterClasses = characterClassesBuilder.build();
-
- ImmutableMap.Builder> templatesBuilder = ImmutableMap.builder();
- for (final Map.Entry template : templatesDict.entrySet()) {
- String key = template.getKey();
- NSObject value = template.getValue();
- Preconditions.checkState( NSArray.class.isAssignableFrom( value.getClass() ) );
-
- MPElementType type = MPElementType.forName( key );
- List templateStrings = ((NSArray) value).getValue();
-
- ImmutableList.Builder typeTemplatesBuilder = ImmutableList.builder();
- for (final NSObject templateString : templateStrings)
- typeTemplatesBuilder.add( new MPTemplate( ((NSString) templateString).getValue(), characterClasses ) );
-
- templatesBuilder.put( type, typeTemplatesBuilder.build() );
- }
- ImmutableMap> templates = templatesBuilder.build();
-
- return new MPTemplates( templates );
- }
- catch (PropertyListException e) {
- logger.err( e, "Could not parse templates from: %s", templateResource );
- throw Throwables.propagate( e );
- }
- catch (IOException e) {
- logger.err( e, "Could not read templates from: %s", templateResource );
- throw Throwables.propagate( e );
- }
- finally {
- Closeables.closeQuietly( templateStream );
- }
- }
-
- public MPTemplate getTemplateForTypeAtRollingIndex(final MPElementType type, final int templateIndex) {
-
- List typeTemplates = templates.get( type );
-
- return typeTemplates.get( templateIndex % typeTemplates.size() );
- }
-
- public static void main(final String... arguments) {
-
- load();
- }
-}
diff --git a/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MasterKey.java b/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MasterKey.java
index 500776ef..2dc259f2 100644
--- a/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MasterKey.java
+++ b/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MasterKey.java
@@ -2,8 +2,6 @@ package com.lyndir.masterpassword;
import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
-import com.google.common.io.CharSource;
-import com.google.common.io.CharStreams;
import com.google.common.primitives.Bytes;
import com.lambdaworks.crypto.SCrypt;
import com.lyndir.lhunath.opal.crypto.CryptUtils;
@@ -14,7 +12,6 @@ import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.util.Arrays;
-import javax.xml.stream.events.Characters;
/**
@@ -28,11 +25,11 @@ public class MasterKey {
private static final int MP_r = 8;
private static final int MP_p = 2;
private static final int MP_dkLen = 64;
+ private static final int MP_intLen = 32;
private static final Charset MP_charset = Charsets.UTF_8;
private static final ByteOrder MP_byteOrder = ByteOrder.BIG_ENDIAN;
private static final MessageDigests MP_hash = MessageDigests.SHA256;
private static final MessageAuthenticationDigests MP_mac = MessageAuthenticationDigests.HmacSHA256;
- private static final MPTemplates templates = MPTemplates.load();
private final String userName;
private final byte[] key;
@@ -44,12 +41,13 @@ public class MasterKey {
this.userName = userName;
long start = System.currentTimeMillis();
- byte[] userNameLengthBytes = ByteBuffer.allocate( Integer.SIZE / Byte.SIZE )
+ byte[] userNameBytes = userName.getBytes( MP_charset );
+ byte[] userNameLengthBytes = ByteBuffer.allocate( MP_intLen / Byte.SIZE )
.order( MP_byteOrder )
- .putInt( userName.length() )
+ .putInt( userNameBytes.length )
.array();
- byte[] salt = Bytes.concat( "com.lyndir.masterpassword".getBytes( MP_charset ), //
- userNameLengthBytes, userName.getBytes( MP_charset ) );
+ byte[] salt = Bytes.concat( MPElementVariant.Password.getScope().getBytes( MP_charset ), //
+ userNameLengthBytes, userNameBytes );
try {
key = SCrypt.scrypt( masterPassword.getBytes( MP_charset ), salt, MP_N, MP_r, MP_p, MP_dkLen );
@@ -83,7 +81,7 @@ public class MasterKey {
return subkey;
}
- public String encode(final String name, final MPElementType type, int counter) {
+ public String encode(final String name, final MPElementType type, int counter, final MPElementVariant variant, final String context) {
Preconditions.checkState( valid );
Preconditions.checkArgument( type.getTypeClass() == MPElementTypeClass.Generated );
@@ -92,19 +90,20 @@ public class MasterKey {
if (counter == 0)
counter = (int) (System.currentTimeMillis() / (300 * 1000)) * 300;
- byte[] nameLengthBytes = ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( MP_byteOrder ).putInt( name.length() ).array();
- byte[] counterBytes = ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( MP_byteOrder ).putInt( counter ).array();
- logger.trc( "seed from: hmac-sha256(%s, 'com.lyndir.masterpassword' | %s | %s | %s)", CryptUtils.encodeBase64( key ),
+ byte[] nameBytes = name.getBytes( MP_charset );
+ byte[] nameLengthBytes = ByteBuffer.allocate( MP_intLen / Byte.SIZE ).order( MP_byteOrder ).putInt( nameBytes.length ).array();
+ byte[] counterBytes = ByteBuffer.allocate( MP_intLen / Byte.SIZE ).order( MP_byteOrder ).putInt( counter ).array();
+ logger.trc( "seed from: hmac-sha256(%s, %s | %s | %s | %s)", variant.getScope(), CryptUtils.encodeBase64( key ),
CodeUtils.encodeHex( nameLengthBytes ), name, CodeUtils.encodeHex( counterBytes ) );
- byte[] seed = MP_mac.of( key, Bytes.concat( "com.lyndir.masterpassword".getBytes( MP_charset ), //
+ byte[] seed = MP_mac.of( key, Bytes.concat( variant.getScope().getBytes( MP_charset ), //
nameLengthBytes, //
- name.getBytes( MP_charset ), //
+ nameBytes, //
counterBytes ) );
logger.trc( "seed is: %s", CryptUtils.encodeBase64( seed ) );
Preconditions.checkState( seed.length > 0 );
int templateIndex = seed[0] & 0xFF; // Mask the integer's sign.
- MPTemplate template = templates.getTemplateForTypeAtRollingIndex( type, templateIndex );
+ MPTemplate template = type.getTemplateAtRollingIndex( templateIndex );
logger.trc( "type: %s, template: %s", type, template );
StringBuilder password = new StringBuilder( template.length() );
diff --git a/MasterPassword/Java/masterpassword-algorithm/src/main/resources/ciphers.plist b/MasterPassword/Java/masterpassword-algorithm/src/main/resources/ciphers.plist
deleted file mode 120000
index 5258d70a..00000000
--- a/MasterPassword/Java/masterpassword-algorithm/src/main/resources/ciphers.plist
+++ /dev/null
@@ -1 +0,0 @@
-../../../../../../MasterPassword/Resources/Data/ciphers.plist
\ No newline at end of file
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
new file mode 100644
index 00000000..5039e2ac
--- /dev/null
+++ b/MasterPassword/Java/masterpassword-algorithm/src/test/java/com/lyndir/masterpassword/MasterKeyTest.java
@@ -0,0 +1,79 @@
+package com.lyndir.masterpassword;
+
+import static org.testng.Assert.*;
+
+import org.testng.annotations.Test;
+
+
+public class MasterKeyTest {
+
+ private static final String FULL_NAME = "Robert Lee Mitchell";
+ private static final String MASTER_PASSWORD = "banana colored duckling";
+ private static final String SITE_NAME = "masterpasswordapp.com";
+
+ @Test
+ public void testEncode()
+ throws Exception {
+
+ MasterKey masterKey = new MasterKey( FULL_NAME, MASTER_PASSWORD );
+
+ assertEquals( masterKey.encode( SITE_NAME, MPElementType.GeneratedLong, 1, MPElementVariant.Password, null ), //
+ "Jejr5[RepuSosp" );
+
+ assertEquals( masterKey.encode( "\u26C4", MPElementType.GeneratedMaximum, 1, MPElementVariant.Password, null ), //
+ "b9]1#2g*suJ^E@OJXZTQ" );
+
+ assertEquals( masterKey.encode( "\u26C4", MPElementType.GeneratedLong, 1, MPElementVariant.Password, null ), //
+ "LiheCuwhSerz6)" );
+
+ assertEquals( masterKey.encode( "\u26C4", MPElementType.GeneratedMedium, 1, MPElementVariant.Password, null ), //
+ "Xep8'Cav" );
+
+ assertEquals( masterKey.encode( "\u26C4", MPElementType.GeneratedBasic, 1, MPElementVariant.Password, null ), //
+ "bpW62jmW" );
+
+ assertEquals( masterKey.encode( "\u26C4", MPElementType.GeneratedShort, 1, MPElementVariant.Password, null ), //
+ "Puw2" );
+
+ assertEquals( masterKey.encode( "\u26C4", MPElementType.GeneratedPIN, 1, MPElementVariant.Password, null ), //
+ "3258" );
+
+ assertEquals( masterKey.encode( "\u26C4", MPElementType.GeneratedName, 1, MPElementVariant.Password, null ), //
+ "cujtebona" );
+
+ assertEquals( masterKey.encode( "\u26C4", MPElementType.GeneratedPhrase, 1, MPElementVariant.Password, null ), //
+ "ri durzu jid kalebho" );
+
+ assertEquals( masterKey.encode( "\u26C4", MPElementType.GeneratedMaximum, (int) (1L << 32 - 1), MPElementVariant.Password, null ),
+ "y4=s&D6)ao(xcBS)AgBT" );
+ }
+
+ @Test
+ public void testGetUserName()
+ throws Exception {
+
+ assertEquals( new MasterKey( FULL_NAME, "banana colored duckling" ).getUserName(), FULL_NAME );
+ }
+
+ @Test
+ public void testGetKeyID()
+ throws Exception {
+
+ assertEquals( new MasterKey( FULL_NAME, "banana colored duckling" ).getKeyID(),
+ "98EEF4D1DF46D849574A82A03C3177056B15DFFCA29BB3899DE4628453675302" );
+ }
+
+ @Test
+ public void testInvalidate()
+ throws Exception {
+
+ try {
+ MasterKey masterKey = new MasterKey( FULL_NAME, MASTER_PASSWORD );
+ masterKey.invalidate();
+ masterKey.encode( SITE_NAME, MPElementType.GeneratedLong, 1, MPElementVariant.Password, null );
+ assertFalse( true, "Master key was not invalidated." );
+ }
+ catch (IllegalStateException ignored) {
+ }
+ }
+}
diff --git a/MasterPassword/Java/masterpassword-android/src/main/java/com/lyndir/masterpassword/EmergencyActivity.java b/MasterPassword/Java/masterpassword-android/src/main/java/com/lyndir/masterpassword/EmergencyActivity.java
index 8376489f..0030eaca 100644
--- a/MasterPassword/Java/masterpassword-android/src/main/java/com/lyndir/masterpassword/EmergencyActivity.java
+++ b/MasterPassword/Java/masterpassword-android/src/main/java/com/lyndir/masterpassword/EmergencyActivity.java
@@ -184,7 +184,7 @@ public class EmergencyActivity extends Activity {
@Override
public void run() {
try {
- final String sitePassword = masterKeyFuture.get().encode( siteName, type, counter );
+ final String sitePassword = masterKeyFuture.get().encode( siteName, type, counter, variant, context );
runOnUiThread( new Runnable() {
@Override
diff --git a/MasterPassword/Java/masterpassword-cli/src/main/java/com/lyndir/masterpassword/CLI.java b/MasterPassword/Java/masterpassword-cli/src/main/java/com/lyndir/masterpassword/CLI.java
index 351a5112..270bcfe0 100644
--- a/MasterPassword/Java/masterpassword-cli/src/main/java/com/lyndir/masterpassword/CLI.java
+++ b/MasterPassword/Java/masterpassword-cli/src/main/java/com/lyndir/masterpassword/CLI.java
@@ -18,12 +18,16 @@
package com.lyndir.masterpassword;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse;
+import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
+import com.google.common.base.Joiner;
+import com.google.common.collect.Maps;
import com.google.common.io.LineReader;
-import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
+import com.lyndir.lhunath.opal.system.util.StringUtils;
import java.io.*;
import java.util.Arrays;
+import java.util.Map;
/**
@@ -34,7 +38,6 @@ import java.util.Arrays;
public class CLI {
private static final String ENV_USERNAME = "MP_USERNAME";
- private static final String ENV_PASSWORD = "MP_PASSWORD";
private static final String ENV_SITETYPE = "MP_SITETYPE";
private static final String ENV_SITECOUNTER = "MP_SITECOUNTER";
@@ -42,71 +45,115 @@ public class CLI {
throws IOException {
// Read information from the environment.
- String siteName = null;
- String userName = System.getenv().get( ENV_USERNAME );
- String masterPassword = System.getenv().get( ENV_PASSWORD );
- String siteTypeName = ifNotNullElse( System.getenv().get( ENV_SITETYPE ), "" );
- MPElementType siteType = siteTypeName.isEmpty()? MPElementType.GeneratedLong: MPElementType.forName( siteTypeName );
- String siteCounterName = ifNotNullElse( System.getenv().get( ENV_SITECOUNTER ), "" );
+ String siteName = null, masterPassword, context = null;
+ String userName = System.getenv( ENV_USERNAME );
+ String siteTypeName = ifNotNullElse( System.getenv( ENV_SITETYPE ), "" );
+ MPElementType siteType = siteTypeName.isEmpty()? MPElementType.GeneratedLong: MPElementType.forOption( siteTypeName );
+ MPElementVariant variant = MPElementVariant.Password;
+ String siteCounterName = ifNotNullElse( System.getenv( ENV_SITECOUNTER ), "" );
int siteCounter = siteCounterName.isEmpty()? 1: Integer.parseInt( siteCounterName );
// Parse information from option arguments.
- boolean typeArg = false, counterArg = false, userNameArg = false;
+ boolean userNameArg = false, typeArg = false, counterArg = false, variantArg = false, contextArg = false;
for (final String arg : Arrays.asList( args ))
- if ("-t".equals( arg ) || "--type".equals( arg ))
- typeArg = true;
- else if (typeArg) {
- if ("list".equalsIgnoreCase( arg )) {
- System.out.format( "%30s | %s\n", "type", "description" );
- for (final MPElementType aType : MPElementType.values())
- System.out.format( "%30s | %s\n", aType.getName(), aType.getDescription() );
- System.exit( 0 );
- }
-
- siteType = MPElementType.forName( arg );
- typeArg = false;
- } else if ("-c".equals( arg ) || "--counter".equals( arg ))
- counterArg = true;
- else if (counterArg) {
- siteCounter = ConversionUtils.toIntegerNN( arg );
- counterArg = false;
- } else if ("-u".equals( arg ) || "--username".equals( arg ))
+ // Full Name
+ if ("-u".equals( arg ) || "--username".equals( arg ))
userNameArg = true;
else if (userNameArg) {
userName = arg;
userNameArg = false;
- } else if ("-h".equals( arg ) || "--help".equals( arg )) {
+ }
+
+ // Type
+ else if ("-t".equals( arg ) || "--type".equals( arg ))
+ typeArg = true;
+ else if (typeArg) {
+ siteType = MPElementType.forOption( arg );
+ typeArg = false;
+ }
+
+ // Counter
+ else if ("-c".equals( arg ) || "--counter".equals( arg ))
+ counterArg = true;
+ else if (counterArg) {
+ siteCounter = ConversionUtils.toIntegerNN( arg );
+ counterArg = false;
+ }
+
+ // Variant
+ else if ("-v".equals( arg ) || "--variant".equals( arg ))
+ variantArg = true;
+ else if (variantArg) {
+ variant = MPElementVariant.forOption( arg );
+ variantArg = false;
+ }
+
+ // Context
+ else if ("-C".equals( arg ) || "--context".equals( arg ))
+ contextArg = true;
+ else if (contextArg) {
+ context = arg;
+ contextArg = false;
+ }
+
+ // Help
+ else if ("-h".equals( arg ) || "--help".equals( arg )) {
System.out.println();
- System.out.println( "\tMaster Password CLI" );
- System.out.println( "\t\tLyndir" );
+ System.out.format( "Usage: mpw [-u name] [-t type] [-c counter] site\n\n" );
+ System.out.format( " -u name Specify the full name of the user.\n" );
+ System.out.format( " Defaults to %s in env.\n\n", ENV_USERNAME );
+ System.out.format( " -t type Specify the password's template.\n" );
+ System.out.format( " Defaults to %s in env or 'long' for password, 'name' for login.\n", ENV_SITETYPE );
- System.out.println( "[options] [site name]" );
+ int optionsLength = 0;
+ Map typeMap = Maps.newLinkedHashMap();
+ for (MPElementType elementType : MPElementType.values()) {
+ String options = Joiner.on( ", " ).join( elementType.getOptions() );
+ typeMap.put( options, elementType );
+ optionsLength = Math.max( optionsLength, options.length() );
+ }
+ for (Map.Entry entry : typeMap.entrySet()) {
+ String infoString = strf( " -v %" + optionsLength + "s | ", entry.getKey() );
+ String infoNewline = "\n" + StringUtils.repeat( " ", infoString.length() - 3 ) + " | ";
+ infoString += entry.getValue().getDescription().replaceAll( "\n", infoNewline );
+ System.out.println( infoString );
+ }
System.out.println();
- System.out.println( "Available options:" );
- System.out.println( "\t-t | --type [site password type]" );
- System.out.format( "\t\tDefault: %s. The password type to use for this site.\n", siteType.getName() );
- System.out.println( "\t\tUse 'list' to see the available types." );
+ System.out.format( " -c counter The value of the counter.\n" );
+ System.out.format( " Defaults to %s in env or '1'.\n\n", ENV_SITECOUNTER );
+ System.out.format( " -v variant The kind of content to generate.\n" );
+ System.out.format( " Defaults to 'password'.\n" );
+ optionsLength = 0;
+ Map variantMap = Maps.newLinkedHashMap();
+ for (MPElementVariant elementVariant : MPElementVariant.values()) {
+ String options = Joiner.on( ", " ).join( elementVariant.getOptions() );
+ variantMap.put( options, elementVariant );
+ optionsLength = Math.max( optionsLength, options.length() );
+ }
+ for (Map.Entry entry : variantMap.entrySet()) {
+ String infoString = strf( " -v %" + optionsLength + "s | ", entry.getKey() );
+ String infoNewline = "\n" + StringUtils.repeat( " ", infoString.length() - 3 ) + " | ";
+ infoString += entry.getValue().getDescription().replaceAll( "\n", infoNewline );
+ System.out.println( infoString );
+ }
System.out.println();
- System.out.println( "\t-c | --counter [site counter]" );
- System.out.format( "\t\tDefault: %d. The counter to use for this site.\n", siteCounter );
- System.out.println( "\t\tIncrement the counter if you need a new password." );
+ System.out.format( " -C context A variant-specific context.\n" );
+ System.out.format( " Defaults to empty.\n" );
+ for (Map.Entry entry : variantMap.entrySet()) {
+ String infoString = strf( " -v %" + optionsLength + "s | ", entry.getKey() );
+ String infoNewline = "\n" + StringUtils.repeat( " ", infoString.length() - 3 ) + " | ";
+ infoString += entry.getValue().getContextDescription().replaceAll( "\n", infoNewline );
+ System.out.println( infoString );
+ }
System.out.println();
- System.out.println( "\t-u | --username [user's name]" );
- System.out.println( "\t\tDefault: asked. The name of the user." );
- System.out.println();
- System.out.println( "Available environment variables:" );
-
- System.out.format( "\t%s\n", ENV_USERNAME );
- System.out.println( "\t\tThe name of the user." );
-
- System.out.format( "\t%s\n", ENV_PASSWORD );
- System.out.println( "\t\tThe master password of the user." );
-
- System.out.println();
+ System.out.format( " ENVIRONMENT\n\n" );
+ System.out.format( " MP_USERNAME | The full name of the user.\n" );
+ System.out.format( " MP_SITETYPE | The default password template.\n" );
+ System.out.format( " MP_SITECOUNTER | The default counter value.\n\n" );
return;
} else
siteName = arg;
@@ -126,18 +173,16 @@ public class CLI {
userName = lineReader.readLine();
}
- if (masterPassword == null) {
- if (console != null)
- masterPassword = new String( console.readPassword( "%s's master password: ", userName ) );
+ if (console != null)
+ masterPassword = new String( console.readPassword( "%s's master password: ", userName ) );
- else {
- System.err.format( "%s's master password: ", userName );
- masterPassword = lineReader.readLine();
- }
+ else {
+ System.err.format( "%s's master password: ", userName );
+ masterPassword = lineReader.readLine();
}
}
// Encode and write out the site password.
- System.out.println( new MasterKey( userName, masterPassword ).encode( siteName, siteType, siteCounter ) );
+ System.out.println( new MasterKey( userName, masterPassword ).encode( siteName, siteType, siteCounter, variant, context ) );
}
}
diff --git a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/PasswordFrame.java b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/PasswordFrame.java
index 65f6b85d..3dad0401 100644
--- a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/PasswordFrame.java
+++ b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/PasswordFrame.java
@@ -159,7 +159,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
Res.execute( new Runnable() {
@Override
public void run() {
- final String sitePassword = user.getKey().encode( siteName, siteType, siteCounter );
+ final String sitePassword = user.getKey().encode( siteName, siteType, siteCounter, MPElementVariant.Password, null );
if (callback != null)
callback.passwordGenerated( siteName, sitePassword );