diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..afbdab3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..4d29f21 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +Master Password \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..217af47 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..e7bedf3 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..e206d70 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..736c7b5 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..b153e48 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..b08ae61 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/.idea/scopes/scope_settings.xml b/.idea/scopes/scope_settings.xml new file mode 100644 index 0000000..922003b --- /dev/null +++ b/.idea/scopes/scope_settings.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..c80f219 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/MasterPassword.iml b/MasterPassword.iml new file mode 100644 index 0000000..0bb6048 --- /dev/null +++ b/MasterPassword.iml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/app.iml b/app/app.iml new file mode 100644 index 0000000..deb5092 --- /dev/null +++ b/app/app.iml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..2a73136 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,47 @@ +apply plugin: 'com.android.application' +apply plugin: 'android-apt' + +android { + compileSdkVersion 20 + buildToolsVersion "20.0.0" + + packagingOptions { + exclude 'META-INF/services/javax.annotation.processing.Processor' + exclude 'LICENSE' + } + + defaultConfig { + applicationId "de.devland.masterpassword" + minSdkVersion 15 + targetSdkVersion 20 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + runProguard false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + + compile 'com.github.gabrielemariotti.cards:library:1.8.0' + compile 'com.jakewharton:butterknife:5.1.2' + compile 'com.github.satyan:sugar:1.3' + compile 'org.projectlombok:lombok:1.14.0' // TODO lombok-api + + apt 'org.projectlombok:lombok:1.14.0' + + // MasterPassword dependencies + compile('com.lambdaworks:scrypt:1.3.2') + compile('net.sf.plist:property-list:2.0.0') + compile('com.lyndir.lhunath.opal:opal-crypto:GIT-SNAPSHOT') { + exclude module: 'jsr305' // problems while dexing + } + compile('com.lyndir.lhunath.opal:opal-system:GIT-SNAPSHOT') { + exclude module: 'jsr305' // problems while dexing + } +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..d4a039a --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in C:\develop\androidstudio\sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/app/src/androidTest/java/de/devland/masterpassword/ApplicationTest.java b/app/src/androidTest/java/de/devland/masterpassword/ApplicationTest.java new file mode 100644 index 0000000..65ee1ed --- /dev/null +++ b/app/src/androidTest/java/de/devland/masterpassword/ApplicationTest.java @@ -0,0 +1,13 @@ +package de.devland.masterpassword; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..ec6c776 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,51 @@ + + + + + + // Database configuration + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/com/lyndir/lhunath/masterpassword/MPElementFeature.java b/app/src/main/java/com/lyndir/lhunath/masterpassword/MPElementFeature.java new file mode 100644 index 0000000..4261f91 --- /dev/null +++ b/app/src/main/java/com/lyndir/lhunath/masterpassword/MPElementFeature.java @@ -0,0 +1,14 @@ +package com.lyndir.lhunath.masterpassword; + +/** + * 07 04, 2012 + * + * @author lhunath + */ +public enum MPElementFeature { + + /** Export the key-protected content data. */ + ExportContent, + /** Never export content. */ + DevicePrivate, +} diff --git a/app/src/main/java/com/lyndir/lhunath/masterpassword/MPElementType.java b/app/src/main/java/com/lyndir/lhunath/masterpassword/MPElementType.java new file mode 100644 index 0000000..9612e29 --- /dev/null +++ b/app/src/main/java/com/lyndir/lhunath/masterpassword/MPElementType.java @@ -0,0 +1,108 @@ +package com.lyndir.lhunath.masterpassword; + +import com.google.common.collect.ImmutableSet; +import com.lyndir.lhunath.opal.system.logging.Logger; +import java.util.Set; + + +/** + * 07 04, 2012 + * + * @author lhunath + */ +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 ), + + StoredPersonal( "Personal Password", "Personal", "AES-encrypted, exportable.", MPElementTypeClass.Stored, + MPElementFeature.ExportContent ), + StoredDevicePrivate( "Device Private Password", "Private", "AES-encrypted, not exported.", MPElementTypeClass.Stored, + 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; + + MPElementType(final String name, final String shortName, final String description, final MPElementTypeClass typeClass, + final MPElementFeature... typeFeatures) { + + this.name = name; + this.shortName = shortName; + this.typeClass = typeClass; + this.description = description; + + ImmutableSet.Builder typeFeaturesBuilder = ImmutableSet.builder(); + for (final MPElementFeature typeFeature : typeFeatures) { + typeFeaturesBuilder.add( typeFeature ); + } + this.typeFeatures = typeFeaturesBuilder.build(); + } + + public MPElementTypeClass getTypeClass() { + + return typeClass; + } + + public Set getTypeFeatures() { + + 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. + * + * @return The type with the given name. + */ + public static MPElementType forName(final String name) { + + for (final MPElementType type : values()) { + if (type.getName().equalsIgnoreCase( name ) || type.getShortName().equalsIgnoreCase( name )) { + return type; + } + } + + throw logger.bug( "Element type not known: %s", name ); + } + + /** + * @param typeClass The class for which we look up types. + * + * @return All types that support the given class. + */ + public static ImmutableSet forClass(final MPElementTypeClass typeClass) { + + ImmutableSet.Builder types = ImmutableSet.builder(); + for (final MPElementType type : values()) { + if (type.getTypeClass() == typeClass) { + types.add( type ); + } + } + + return types.build(); + } + +} diff --git a/app/src/main/java/com/lyndir/lhunath/masterpassword/MPElementTypeClass.java b/app/src/main/java/com/lyndir/lhunath/masterpassword/MPElementTypeClass.java new file mode 100644 index 0000000..1fbebee --- /dev/null +++ b/app/src/main/java/com/lyndir/lhunath/masterpassword/MPElementTypeClass.java @@ -0,0 +1,27 @@ +package com.lyndir.lhunath.masterpassword; + +import com.lyndir.lhunath.masterpassword.entity.*; + + +/** + * 07 04, 2012 + * + * @author lhunath + */ +public enum MPElementTypeClass { + + Generated(MPElementGeneratedEntity.class), + Stored(MPElementStoredEntity.class); + + private final Class entityClass; + + MPElementTypeClass(final Class entityClass) { + + this.entityClass = entityClass; + } + + public Class getEntityClass() { + + return entityClass; + } +} diff --git a/app/src/main/java/com/lyndir/lhunath/masterpassword/MPTemplate.java b/app/src/main/java/com/lyndir/lhunath/masterpassword/MPTemplate.java new file mode 100644 index 0000000..2027c09 --- /dev/null +++ b/app/src/main/java/com/lyndir/lhunath/masterpassword/MPTemplate.java @@ -0,0 +1,41 @@ +package com.lyndir.lhunath.masterpassword; + +import com.google.common.collect.ImmutableList; +import com.lyndir.lhunath.opal.system.util.MetaObject; +import java.util.List; +import java.util.Map; + + +/** + * 07 04, 2012 + * + * @author lhunath + */ +public class MPTemplate extends MetaObject { + + private final List template; + + public MPTemplate(final String template, final Map characterClasses) { + + ImmutableList.Builder builder = ImmutableList.builder(); + for (int i = 0; i < template.length(); ++i) + builder.add( characterClasses.get( 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 ); + } + + public int length() { + + return template.size(); + } +} diff --git a/app/src/main/java/com/lyndir/lhunath/masterpassword/MPTemplateCharacterClass.java b/app/src/main/java/com/lyndir/lhunath/masterpassword/MPTemplateCharacterClass.java new file mode 100644 index 0000000..24545e3 --- /dev/null +++ b/app/src/main/java/com/lyndir/lhunath/masterpassword/MPTemplateCharacterClass.java @@ -0,0 +1,33 @@ +package com.lyndir.lhunath.masterpassword; + +import com.lyndir.lhunath.opal.system.util.MetaObject; +import com.lyndir.lhunath.opal.system.util.ObjectMeta; + + +/** + * 07 04, 2012 + * + * @author lhunath + */ +public class MPTemplateCharacterClass extends MetaObject { + + private final char identifier; + @ObjectMeta(useFor = { }) + private final char[] characters; + + public MPTemplateCharacterClass(final char identifier, final char[] characters) { + + this.identifier = identifier; + this.characters = characters; + } + + public char getIdentifier() { + + return identifier; + } + + public char getCharacterAtRollingIndex(final int index) { + + return characters[index % characters.length]; + } +} diff --git a/app/src/main/java/com/lyndir/lhunath/masterpassword/MPTemplates.java b/app/src/main/java/com/lyndir/lhunath/masterpassword/MPTemplates.java new file mode 100644 index 0000000..4a3d9b6 --- /dev/null +++ b/app/src/main/java/com/lyndir/lhunath/masterpassword/MPTemplates.java @@ -0,0 +1,109 @@ +package com.lyndir.lhunath.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/app/src/main/java/com/lyndir/lhunath/masterpassword/MasterPassword.java b/app/src/main/java/com/lyndir/lhunath/masterpassword/MasterPassword.java new file mode 100644 index 0000000..2e100b1 --- /dev/null +++ b/app/src/main/java/com/lyndir/lhunath/masterpassword/MasterPassword.java @@ -0,0 +1,128 @@ +package com.lyndir.lhunath.masterpassword; + +import com.google.common.base.Charsets; +import com.google.common.base.Preconditions; +import com.google.common.primitives.Bytes; +import com.lambdaworks.crypto.SCrypt; +import com.lyndir.lhunath.opal.crypto.CryptUtils; +import com.lyndir.lhunath.opal.system.*; +import com.lyndir.lhunath.opal.system.logging.Logger; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.Charset; +import java.security.GeneralSecurityException; + + +/** + * Implementation of the Master Password algorithm. + * + * 07 04, 2012 + * + * @author lhunath + */ +public abstract class MasterPassword { + + static final Logger logger = Logger.get( MasterPassword.class ); + private static final int MP_N = 32768; + private static final int MP_r = 8; + private static final int MP_p = 2; + private static final int MP_dkLen = 64; + 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(); + + public static byte[] keyForPassword(final String password, final String username) { + + long start = System.currentTimeMillis(); + byte[] nusernameLengthBytes = ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ) + .order( MP_byteOrder ) + .putInt( username.length() ) + .array(); + byte[] salt = Bytes.concat( "com.lyndir.masterpassword".getBytes( MP_charset ), // + nusernameLengthBytes, // + username.getBytes( MP_charset ) ); + + try { + byte[] key = SCrypt.scrypt( password.getBytes( MP_charset ), salt, MP_N, MP_r, MP_p, MP_dkLen ); + logger.trc( "User: %s, password: %s derives to key ID: %s (took %.2fs)", username, password, + CodeUtils.encodeHex( keyIDForKey( key ) ), (double) (System.currentTimeMillis() - start) / 1000 ); + + return key; + } + catch (GeneralSecurityException e) { + throw logger.bug( e ); + } + } + + public static byte[] subkeyForKey(final byte[] key, final int subkeyLength) { + + byte[] subkey = new byte[Math.min( subkeyLength, key.length )]; + System.arraycopy( key, 0, subkey, 0, subkey.length ); + + return subkey; + } + + public static byte[] keyIDForPassword(final String password, final String username) { + + return keyIDForKey( keyForPassword( password, username ) ); + } + + public static byte[] keyIDForKey(final byte[] key) { + + return MP_hash.of( key ); + } + + public static String generateContent(final MPElementType type, final String name, final byte[] key, int counter) { + + Preconditions.checkArgument( type.getTypeClass() == MPElementTypeClass.Generated ); + Preconditions.checkArgument( !name.isEmpty() ); + Preconditions.checkArgument( key.length > 0 ); + + 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 ), + CodeUtils.encodeHex( nameLengthBytes ), name, CodeUtils.encodeHex( counterBytes ) ); + byte[] seed = MP_mac.of( key, Bytes.concat( "com.lyndir.masterpassword".getBytes( MP_charset ), // + nameLengthBytes, // + name.getBytes( MP_charset ), // + 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 ); + logger.trc( "type: %s, template: %s", type, template ); + + StringBuilder password = new StringBuilder( template.length() ); + for (int i = 0; i < template.length(); ++i) { + int characterIndex = seed[i + 1] & 0xFF; // Mask the integer's sign. + MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i ); + char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex ); + logger.trc( "class: %s, index: %d, byte: 0x%02X, chosen password character: %s", characterClass, characterIndex, seed[i + 1], + passwordCharacter ); + + password.append( passwordCharacter ); + } + + return password.toString(); + } + + public static void main(final String... arguments) { + + String masterPassword = "test-mp"; + String username = "test-user"; + String siteName = "test-site"; + MPElementType siteType = MPElementType.GeneratedLong; + int siteCounter = 42; + + String sitePassword = generateContent( siteType, siteName, keyForPassword( masterPassword, username ), siteCounter ); + + logger.inf( "master password: %s, username: %s\nsite name: %s, site type: %s, site counter: %d\n => site password: %s", + masterPassword, username, siteName, siteType, siteCounter, sitePassword ); + } +} diff --git a/app/src/main/java/com/lyndir/lhunath/masterpassword/entity/MPElementEntity.java b/app/src/main/java/com/lyndir/lhunath/masterpassword/entity/MPElementEntity.java new file mode 100644 index 0000000..b047bc0 --- /dev/null +++ b/app/src/main/java/com/lyndir/lhunath/masterpassword/entity/MPElementEntity.java @@ -0,0 +1,10 @@ +package com.lyndir.lhunath.masterpassword.entity; + +/** + * 07 04, 2012 + * + * @author lhunath + */ +public class MPElementEntity { + +} diff --git a/app/src/main/java/com/lyndir/lhunath/masterpassword/entity/MPElementGeneratedEntity.java b/app/src/main/java/com/lyndir/lhunath/masterpassword/entity/MPElementGeneratedEntity.java new file mode 100644 index 0000000..99d4c43 --- /dev/null +++ b/app/src/main/java/com/lyndir/lhunath/masterpassword/entity/MPElementGeneratedEntity.java @@ -0,0 +1,10 @@ +package com.lyndir.lhunath.masterpassword.entity; + +/** + * 07 04, 2012 + * + * @author lhunath + */ +public class MPElementGeneratedEntity extends MPElementEntity { + +} diff --git a/app/src/main/java/com/lyndir/lhunath/masterpassword/entity/MPElementStoredEntity.java b/app/src/main/java/com/lyndir/lhunath/masterpassword/entity/MPElementStoredEntity.java new file mode 100644 index 0000000..0a1e787 --- /dev/null +++ b/app/src/main/java/com/lyndir/lhunath/masterpassword/entity/MPElementStoredEntity.java @@ -0,0 +1,10 @@ +package com.lyndir.lhunath.masterpassword.entity; + +/** + * 07 04, 2012 + * + * @author lhunath + */ +public class MPElementStoredEntity extends MPElementEntity { + +} diff --git a/app/src/main/java/de/devland/masterpassword/App.java b/app/src/main/java/de/devland/masterpassword/App.java new file mode 100644 index 0000000..b2d1cb4 --- /dev/null +++ b/app/src/main/java/de/devland/masterpassword/App.java @@ -0,0 +1,24 @@ +package de.devland.masterpassword; + +import android.app.Application; + +import com.orm.SugarApp; + + +/** + * Created by David Kunzler on 23.08.2014. + */ +public class App extends SugarApp { + + private static App instance; + + @Override + public void onCreate() { + super.onCreate(); + instance = this; + } + + public static App get() { + return instance; + } +} diff --git a/app/src/main/java/de/devland/masterpassword/MasterPasswordUtil.java b/app/src/main/java/de/devland/masterpassword/MasterPasswordUtil.java new file mode 100644 index 0000000..fb1c08f --- /dev/null +++ b/app/src/main/java/de/devland/masterpassword/MasterPasswordUtil.java @@ -0,0 +1,20 @@ +package de.devland.masterpassword; + +import lombok.Getter; +import lombok.Setter; + +/** + * Created by David Kunzler on 23.08.2014. + */ +public enum MasterPasswordUtil { + INSTANCE; + + @Getter + @Setter + private String masterPassword; + + public void clear() { + masterPassword = null; + } + +} diff --git a/app/src/main/java/de/devland/masterpassword/model/Site.java b/app/src/main/java/de/devland/masterpassword/model/Site.java new file mode 100644 index 0000000..3b4ad59 --- /dev/null +++ b/app/src/main/java/de/devland/masterpassword/model/Site.java @@ -0,0 +1,21 @@ +package de.devland.masterpassword.model; + +import com.lyndir.lhunath.masterpassword.MPElementType; +import com.orm.SugarRecord; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * Created by David Kunzler on 23.08.2014. + */ +@Getter +@Setter +@NoArgsConstructor +public class Site extends SugarRecord { + protected String siteName = ""; + protected String userName = ""; + protected int siteCounter = 0; + protected MPElementType passwordType = MPElementType.GeneratedMaximum; +} diff --git a/app/src/main/java/de/devland/masterpassword/ui/EditActivity.java b/app/src/main/java/de/devland/masterpassword/ui/EditActivity.java new file mode 100644 index 0000000..c61c5d6 --- /dev/null +++ b/app/src/main/java/de/devland/masterpassword/ui/EditActivity.java @@ -0,0 +1,46 @@ +package de.devland.masterpassword.ui; + +import android.app.Activity; +import android.app.ActionBar; +import android.app.Fragment; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.os.Build; + +import de.devland.masterpassword.R; + + +public class EditActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_edit); + if (savedInstanceState == null) { + getFragmentManager().beginTransaction() + .add(R.id.container, new EditFragment()) + .commit(); + } + } + + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.edit, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + return super.onOptionsItemSelected(item); + } +} diff --git a/app/src/main/java/de/devland/masterpassword/ui/EditFragment.java b/app/src/main/java/de/devland/masterpassword/ui/EditFragment.java new file mode 100644 index 0000000..0835d3f --- /dev/null +++ b/app/src/main/java/de/devland/masterpassword/ui/EditFragment.java @@ -0,0 +1,122 @@ +package de.devland.masterpassword.ui; + + + +import android.app.Activity; +import android.content.res.Resources; +import android.os.Bundle; +import android.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AutoCompleteTextView; +import android.widget.EditText; +import android.widget.NumberPicker; +import android.widget.Spinner; +import android.widget.TextView; + +import com.lyndir.lhunath.masterpassword.MPElementType; + +import java.util.Arrays; + +import butterknife.ButterKnife; +import butterknife.InjectView; +import de.devland.masterpassword.R; +import de.devland.masterpassword.model.Site; + +/** + * A simple {@link Fragment} subclass. + * + */ +public class EditFragment extends Fragment { + + public static final String ARG_SITE_ID = "de.devland.masterpassword.EditFragment.siteId"; + + @InjectView(R.id.editText_siteName) + protected EditText siteName; + @InjectView(R.id.editText_userName) + protected AutoCompleteTextView userName; + @InjectView(R.id.spinner_passwordType) + protected Spinner passwordType; + @InjectView(R.id.numberPicker_siteCounter) + protected NumberPicker siteCounter; + + private String[] passwordTypeValues; + private String[] passwordTypeKeys; + + private long siteId = 1; // TODO -1 + private Site site; + + public EditFragment() { + // Required empty public constructor + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Bundle arguments = getArguments(); + if (arguments != null) { + siteId = arguments.getInt(ARG_SITE_ID, -1); + } + site = Site.findById(Site.class, siteId); + if (site == null) { + site = new Site(); + site.save(); + siteId = site.getId(); + } + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + passwordTypeKeys = getResources().getStringArray(R.array.passwordTypeKeys); + passwordTypeValues = getResources().getStringArray(R.array.passwordTypeValues); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.fragment_edit, container, false); + ButterKnife.inject(this, rootView); + + siteCounter.setMinValue(0); + siteCounter.setMaxValue(Integer.MAX_VALUE); + siteCounter.setWrapSelectorWheel(false); + return rootView; + } + + @Override + public void onResume() { + super.onResume(); + readValues(); + } + + @Override + public void onPause() { + super.onPause(); + writeValues(); + } + + private void readValues() { + siteName.setText(site.getSiteName()); + userName.setText(site.getUserName()); + String passwordTypeName = site.getPasswordType().toString(); + for (int i = 0; i < passwordTypeKeys.length; i++) { + String passwordTypeKey = passwordTypeKeys[i]; + if (passwordTypeKey.equals(passwordTypeName)) { + passwordType.setSelection(i, true); + break; + } + } + siteCounter.setValue(site.getSiteCounter()); + } + + private void writeValues() { + site.setSiteName(siteName.getText().toString()); + site.setUserName(userName.getText().toString()); + int passwordTypeIndex = passwordType.getSelectedItemPosition(); + site.setPasswordType(MPElementType.valueOf(passwordTypeKeys[passwordTypeIndex])); + site.setSiteCounter(siteCounter.getValue()); + site.save(); + } +} diff --git a/app/src/main/java/de/devland/masterpassword/ui/LoginActivity.java b/app/src/main/java/de/devland/masterpassword/ui/LoginActivity.java new file mode 100644 index 0000000..d7db387 --- /dev/null +++ b/app/src/main/java/de/devland/masterpassword/ui/LoginActivity.java @@ -0,0 +1,47 @@ +package de.devland.masterpassword.ui; + +import android.app.Activity; +import android.app.ActionBar; +import android.app.Fragment; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.os.Build; + +import de.devland.masterpassword.R; + + +public class LoginActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_login); + if (savedInstanceState == null) { + getFragmentManager().beginTransaction() + .add(R.id.container, new LoginFragment()) + .commit(); + } + } + + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.login, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + return super.onOptionsItemSelected(item); + } + +} diff --git a/app/src/main/java/de/devland/masterpassword/ui/LoginFragment.java b/app/src/main/java/de/devland/masterpassword/ui/LoginFragment.java new file mode 100644 index 0000000..c33791d --- /dev/null +++ b/app/src/main/java/de/devland/masterpassword/ui/LoginFragment.java @@ -0,0 +1,52 @@ +package de.devland.masterpassword.ui; + + + +import android.content.Intent; +import android.os.Bundle; +import android.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; + +import butterknife.ButterKnife; +import butterknife.InjectView; +import butterknife.OnClick; +import de.devland.masterpassword.MasterPasswordUtil; +import de.devland.masterpassword.R; + +/** + * A simple {@link Fragment} subclass. + * + */ +public class LoginFragment extends Fragment { + + @InjectView(R.id.editText_masterPassword) + protected EditText masterPassword; + @InjectView(R.id.button_login) + protected Button loginButton; + + public LoginFragment() { + // Required empty public constructor + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.fragment_login, container, false); + ButterKnife.inject(this, rootView); + return rootView; + } + + + @OnClick(R.id.button_login) + public void onClick() { + MasterPasswordUtil.INSTANCE.setMasterPassword(masterPassword.getText().toString()); + Intent intent = new Intent(getActivity(), PasswordViewActivity.class); + getActivity().startActivity(intent); + getActivity().finish(); + } +} diff --git a/app/src/main/java/de/devland/masterpassword/ui/PasswordViewActivity.java b/app/src/main/java/de/devland/masterpassword/ui/PasswordViewActivity.java new file mode 100644 index 0000000..db335b4 --- /dev/null +++ b/app/src/main/java/de/devland/masterpassword/ui/PasswordViewActivity.java @@ -0,0 +1,48 @@ +package de.devland.masterpassword.ui; + +import android.app.Activity; +import android.app.ActionBar; +import android.app.Fragment; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.os.Build; + +import de.devland.masterpassword.R; +import it.gmariotti.cardslib.library.prototypes.SectionedCardAdapter; + + +public class PasswordViewActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_password_view); + if (savedInstanceState == null) { + getFragmentManager().beginTransaction() + .add(R.id.container, new PasswordViewFragment()) + .commit(); + } + } + + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.password_view, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + return super.onOptionsItemSelected(item); + } + +} diff --git a/app/src/main/java/de/devland/masterpassword/ui/PasswordViewFragment.java b/app/src/main/java/de/devland/masterpassword/ui/PasswordViewFragment.java new file mode 100644 index 0000000..570863f --- /dev/null +++ b/app/src/main/java/de/devland/masterpassword/ui/PasswordViewFragment.java @@ -0,0 +1,32 @@ +package de.devland.masterpassword.ui; + + +import android.app.Fragment; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import de.devland.masterpassword.R; + +/** + * A simple {@link Fragment} subclass. + * + */ +public class PasswordViewFragment extends Fragment { + + + public PasswordViewFragment() { + // Required empty public constructor + } + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.fragment_password_view, container, false); + return rootView; + } + + +} diff --git a/app/src/main/res/drawable-hdpi/ic_launcher.png b/app/src/main/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 0000000..96a442e Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_launcher.png b/app/src/main/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 0000000..359047d Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_launcher.png b/app/src/main/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 0000000..71c6d76 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/app/src/main/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..4df1894 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/layout/activity_edit.xml b/app/src/main/res/layout/activity_edit.xml new file mode 100644 index 0000000..783c390 --- /dev/null +++ b/app/src/main/res/layout/activity_edit.xml @@ -0,0 +1,7 @@ + diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml new file mode 100644 index 0000000..b9bdfa2 --- /dev/null +++ b/app/src/main/res/layout/activity_login.xml @@ -0,0 +1,7 @@ + diff --git a/app/src/main/res/layout/activity_password_view.xml b/app/src/main/res/layout/activity_password_view.xml new file mode 100644 index 0000000..dfa79b8 --- /dev/null +++ b/app/src/main/res/layout/activity_password_view.xml @@ -0,0 +1,7 @@ + diff --git a/app/src/main/res/layout/fragment_edit.xml b/app/src/main/res/layout/fragment_edit.xml new file mode 100644 index 0000000..d9e45ce --- /dev/null +++ b/app/src/main/res/layout/fragment_edit.xml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_login.xml b/app/src/main/res/layout/fragment_login.xml new file mode 100644 index 0000000..db9261e --- /dev/null +++ b/app/src/main/res/layout/fragment_login.xml @@ -0,0 +1,37 @@ + + + + + + + +