Remove plist dependency, fix length bug, import ciphers.plist.
[REMOVED] Java code no longer depends on ciphers.plist and net.sf.plist. [ADDED] Java code now explicitly defines the algorithm's templates. [FIXED] Java code now properly counts the site name and user name's byte length. [FIXED] Java code now explicitly uses 32-bit integers.
This commit is contained in:
parent
ae08cb62c5
commit
a82ce7310d
@ -33,17 +33,24 @@
|
||||
</dependency>
|
||||
|
||||
<!-- EXTERNAL DEPENDENCIES -->
|
||||
<dependency>
|
||||
<groupId>net.sf.plist</groupId>
|
||||
<artifactId>property-list</artifactId>
|
||||
<version>2.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.lambdaworks</groupId>
|
||||
<artifactId>scrypt</artifactId>
|
||||
<version>1.4.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- TESTING -->
|
||||
<dependency>
|
||||
<groupId>org.testng</groupId>
|
||||
<artifactId>testng</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
@ -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.<MPTemplate>of(), MPElementFeature.ExportContent ),
|
||||
|
||||
StoredDevicePrivate( "AES-encrypted, not exported.", //
|
||||
ImmutableList.of( "device" ), MPElementTypeClass.Stored, //
|
||||
ImmutableList.<MPTemplate>of(), MPElementFeature.DevicePrivate );
|
||||
|
||||
static final Logger logger = Logger.get( MPElementType.class );
|
||||
|
||||
private final MPElementTypeClass typeClass;
|
||||
private final Set<MPElementFeature> typeFeatures;
|
||||
private final String name;
|
||||
private final String shortName;
|
||||
private final String description;
|
||||
private final List<String> options;
|
||||
private final MPElementTypeClass typeClass;
|
||||
private final List<MPTemplate> templates;
|
||||
private final Set<MPElementFeature> typeFeatures;
|
||||
|
||||
MPElementType(final String name, final String shortName, final String description, final MPElementTypeClass typeClass,
|
||||
final MPElementFeature... typeFeatures) {
|
||||
MPElementType(final String description, final List<String> options, final MPElementTypeClass typeClass,
|
||||
final List<MPTemplate> 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<MPElementFeature> 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<String> 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() );
|
||||
}
|
||||
}
|
||||
|
@ -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<String> options;
|
||||
private final String scope;
|
||||
|
||||
MPElementVariant(final String description, final String contextDescription, final List<String> 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<String> 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 );
|
||||
}
|
||||
}
|
@ -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<MPTemplateCharacterClass> template;
|
||||
|
||||
public MPTemplate(final String template, final Map<Character, MPTemplateCharacterClass> characterClasses) {
|
||||
MPTemplate(final String template) {
|
||||
|
||||
ImmutableList.Builder<MPTemplateCharacterClass> builder = ImmutableList.<MPTemplateCharacterClass>builder();
|
||||
ImmutableList.Builder<MPTemplateCharacterClass> 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<MPTemplateCharacterClass> template) {
|
||||
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
public MPTemplateCharacterClass getCharacterClassAtIndex(final int index) {
|
||||
|
||||
return template.get( index );
|
||||
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
||||
/**
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
public class MPTemplates extends MetaObject {
|
||||
|
||||
static final Logger logger = Logger.get( MPTemplates.class );
|
||||
|
||||
private final Map<MPElementType, List<MPTemplate>> templates;
|
||||
|
||||
public MPTemplates(final Map<MPElementType, List<MPTemplate>> 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<Character, MPTemplateCharacterClass> characterClassesBuilder = ImmutableMap.builder();
|
||||
for (final Map.Entry<String, NSObject> 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<Character, MPTemplateCharacterClass> characterClasses = characterClassesBuilder.build();
|
||||
|
||||
ImmutableMap.Builder<MPElementType, List<MPTemplate>> templatesBuilder = ImmutableMap.builder();
|
||||
for (final Map.Entry<String, NSObject> template : templatesDict.entrySet()) {
|
||||
String key = template.getKey();
|
||||
NSObject value = template.getValue();
|
||||
Preconditions.checkState( NSArray.class.isAssignableFrom( value.getClass() ) );
|
||||
|
||||
MPElementType type = MPElementType.forName( key );
|
||||
List<NSObject> templateStrings = ((NSArray) value).getValue();
|
||||
|
||||
ImmutableList.Builder<MPTemplate> typeTemplatesBuilder = ImmutableList.<MPTemplate>builder();
|
||||
for (final NSObject templateString : templateStrings)
|
||||
typeTemplatesBuilder.add( new MPTemplate( ((NSString) templateString).getValue(), characterClasses ) );
|
||||
|
||||
templatesBuilder.put( type, typeTemplatesBuilder.build() );
|
||||
}
|
||||
ImmutableMap<MPElementType, List<MPTemplate>> 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<MPTemplate> typeTemplates = templates.get( type );
|
||||
|
||||
return typeTemplates.get( templateIndex % typeTemplates.size() );
|
||||
}
|
||||
|
||||
public static void main(final String... arguments) {
|
||||
|
||||
load();
|
||||
}
|
||||
}
|
@ -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() );
|
||||
|
@ -1 +0,0 @@
|
||||
../../../../../../MasterPassword/Resources/Data/ciphers.plist
|
@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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<String, MPElementType> 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<String, MPElementType> 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<String, MPElementVariant> 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<String, MPElementVariant> 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<String, MPElementVariant> 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,7 +173,6 @@ public class CLI {
|
||||
userName = lineReader.readLine();
|
||||
}
|
||||
|
||||
if (masterPassword == null) {
|
||||
if (console != null)
|
||||
masterPassword = new String( console.readPassword( "%s's master password: ", userName ) );
|
||||
|
||||
@ -135,9 +181,8 @@ public class CLI {
|
||||
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 ) );
|
||||
}
|
||||
}
|
||||
|
@ -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 );
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user