2
0

Compare commits

...

82 Commits

Author SHA1 Message Date
Maarten Billemont
7f5bb9e114 [maven-release-plugin] prepare release 2.3 2015-04-18 17:41:45 -04:00
Maarten Billemont
4c4aaac08b Update to remove the db4o repository. 2015-04-18 17:31:12 -04:00
Maarten Billemont
5320e079ce [maven-release-plugin] rollback changes from release preparation of 2.3 2015-04-18 16:24:34 -04:00
Maarten Billemont
636c337c78 [maven-release-plugin] prepare release 2.3 2015-04-18 16:24:34 -04:00
Maarten Billemont
c2b08678df Also release android. 2015-04-18 16:22:26 -04:00
Maarten Billemont
7117d82aa4 [maven-release-plugin] rollback changes from release preparation of 2.3-android 2015-04-18 16:02:40 -04:00
Maarten Billemont
4fa6436de2 [maven-release-plugin] prepare release 2.3-android 2015-04-18 16:02:40 -04:00
Maarten Billemont
3e30087856 Try to fix releasing just masterpassword-android. 2015-04-18 16:01:40 -04:00
Maarten Billemont
6bd2d70270 [maven-release-plugin] rollback changes from release preparation of 2.3 2015-04-18 15:35:26 -04:00
Maarten Billemont
cf11b01ed6 [maven-release-plugin] prepare release 2.3 2015-04-18 15:35:26 -04:00
Maarten Billemont
7e11f01331 Try to fix maven-release-plugin inside a subdir. 2015-04-18 15:30:31 -04:00
Maarten Billemont
aeca5e18fe [maven-release-plugin] rollback changes from release preparation of 2.3 2015-04-18 15:07:08 -04:00
Maarten Billemont
615e7c98a8 [maven-release-plugin] prepare release 2.3 2015-04-18 15:07:08 -04:00
Maarten Billemont
e713776123 Update to the correct Android SDK dependency. 2015-04-18 15:02:57 -04:00
Maarten Billemont
7d5b7e53d4 Explain what is necessary to build the Android app. 2015-04-18 14:59:11 -04:00
Maarten Billemont
950c68437a Prepare for 2.3 android release. 2015-04-18 14:49:26 -04:00
Maarten Billemont
300a04f5c7 Merge branch 'master' of github.com:Lyndir/MasterPassword 2015-04-18 14:48:35 -04:00
Maarten Billemont
8faf6b48dd Fixed UTF-8 issue, click on A4.4, add notification and expiry.
[FIXED]     A Java UTF-8 encoding issue.
[FIXED]     Android 4.4 wasn't triggering onClick on TextViews.
[ADDED]     A notification when the password is copied.
[ADDED]     Expire the password from the clipboard after 20 seconds.
[UPDATED]   -underscore variant of slf4j-android to make tags settable with setprops
2015-04-18 14:48:27 -04:00
Maarten Billemont
5f0367ad29 Support for deleting sites from the Java GUI. 2015-04-03 12:12:12 -04:00
Maarten Billemont
e126a55912 Update mpw-js. 2015-04-01 08:44:55 -04:00
Maarten Billemont
d05c5eedd8 Update site's masterpassword-gui.jar 2015-04-01 08:11:43 -04:00
Maarten Billemont
6819a2ace5 Delete users + window resize fix.
[ADDED]     Java: support for deleting users.
[FIXED]     Java: Password window resizing issue.
2015-04-01 08:09:48 -04:00
Maarten Billemont
645b6c5f54 Avoid trouble with s3cmd sync not noticing when remote is already up to date. 2015-03-29 21:10:10 -04:00
Maarten Billemont
d3c09fd979 Update masterpassword-gui to have identicon on the bottom. 2015-03-29 21:03:25 -04:00
Maarten Billemont
a41ae1814a Update site for new Java with emoticons. 2015-03-29 20:32:26 -04:00
Maarten Billemont
70f7fa1345 Java identicon support. 2015-03-29 20:30:57 -04:00
Maarten Billemont
ea9d8cc275 Update masterpassword-gui-2.2.jar for Windows L&F improvements. 2015-03-20 14:37:46 -04:00
Maarten Billemont
20d1811b5c Fixes to mpw.completion.bash when no mpsites. 2015-03-13 11:02:30 -04:00
Maarten Billemont
634ef062f3 Merge branch 'master' of github.com:Lyndir/MasterPassword 2015-03-13 09:57:35 -04:00
Maarten Billemont
a424531a8a Some more anchors on secutity.html. 2015-03-13 09:56:28 -04:00
Maarten Billemont
9cffe53993 A bash completion script for mpw. 2015-03-12 01:03:02 -04:00
Maarten Billemont
fd35fea8cf mpw C code is not thread-safe + bad performance long site queries. 2015-03-11 17:31:39 -04:00
Maarten Billemont
01c21e95bb Merge branch 'master' of github.com:Lyndir/MasterPassword 2015-03-10 16:40:42 -04:00
Maarten Billemont
5af3ffa178 Merge branch 'master' of github.com:Lyndir/MasterPassword
Conflicts:
	Site/2013-05/index.html
2015-03-10 14:39:05 -04:00
Maarten Billemont
651d07f982 mpw is now available via homebrew. 2015-03-10 14:37:47 -04:00
Maarten Billemont
a383d0eee7 Make new site creation on Mac same as iOS.
[FIXED]     Unable to create a site that is a substring of an existing site.
2015-03-05 17:28:04 -05:00
Maarten Billemont
ca8f14fd3e Site-specific support for keys of different algorithm versions.
[ADDED]     Ability to downgrade sites.
[ADDED]     A more explicit message that sites need to be upgraded.
2015-02-28 10:01:41 -05:00
Maarten Billemont
fd855bb025 Add link to Tom's test suite. 2015-02-28 09:53:00 -05:00
Maarten Billemont
af340806af Update mpw-js to include Tom's latest fixes. 2015-02-28 09:20:28 -05:00
Maarten Billemont
cdeee2576d More algorithm trace logging. 2015-02-27 08:49:04 -05:00
Maarten Billemont
779d2776a0 Fix V0 in Java and support for testing algorithms. 2015-02-27 08:35:10 -05:00
Maarten Billemont
563aab9a81 More verbose in TRC about what algorithm is used + fix mpw_charlen on some platforms. 2015-02-18 17:32:33 -05:00
Maarten Billemont
73de98c5e2 Ignore Windows Thumbs.db 2015-02-16 23:58:03 -05:00
Maarten Billemont
c330728ac3 UI Improvements for Windows and system L&F. 2015-02-16 23:56:58 -05:00
Maarten Billemont
2db601475f UI improvements for Windows and system look and feel. 2015-02-16 19:04:43 -05:00
Maarten Billemont
d898646097 Build fixes for mpw_color. 2015-02-09 20:51:12 +00:00
Maarten Billemont
afaa17948f A fix build fixes on other platforms. 2015-02-09 18:02:22 +00:00
Maarten Billemont
8514a58e64 Sizing and memory related C fixes.
[FIXED]     mpw_templatesForType now returns a fully allocated array, was relying on undefined behavior before.
[FIXED]     mpw_variantWithName was allocating a badly sized standard string.
2015-02-07 11:10:59 -05:00
Maarten Billemont
f80bbff46e Small C tweaks and add 2.2 to the site.
[UPDATED]   mpw-cli's usage: line.
[IMPROVED]  Safer code for standardizing of type name.
[FIXED]     Detection of missing Crashlytics API Key wasn't detecting the file being missing.
[ADDED]     2.2 desktop and android to the site.
2015-02-07 09:16:17 -05:00
Maarten Billemont
f7d595b0e7 Update site with new versions. 2015-02-05 21:02:06 -05:00
Maarten Billemont
422066ad4a [maven-release-plugin] rollback changes from release preparation of 2.2-android 2015-02-05 20:28:25 -05:00
Maarten Billemont
73372f507a [maven-release-plugin] prepare release 2.2-android 2015-02-05 20:28:25 -05:00
Maarten Billemont
feddd038e3 Also sign masterpassword-gui 2015-02-05 20:27:09 -05:00
Maarten Billemont
3f6773f3a9 Android release preparation. 2015-02-05 17:29:29 -05:00
Maarten Billemont
12b1610dc7 Remove unavailable resources + update graphics. 2015-02-05 16:34:04 -05:00
Maarten Billemont
e20b33a051 Prepare for making Android releases. 2015-02-05 13:43:57 -05:00
Maarten Billemont
b84ae532f2 Some generic code fix-ups throughout. 2015-02-05 13:14:17 -05:00
Maarten Billemont
145008406d Big overhaul for proper site-specific algorithm support and big Android UI update. 2015-02-05 00:56:24 -05:00
Maarten Billemont
a6ab9b9194 Moar UI work on the Java app + support for per-site algorithm versioning. 2015-02-04 19:51:38 -05:00
Maarten Billemont
78c593fc08 Many UI improvements to the Java GUI. 2015-02-04 11:25:18 -05:00
Maarten Billemont
5b08149ca6 Fix a crash on older OS X versions. 2015-02-02 14:48:12 -05:00
Maarten Billemont
58afc19c26 Make string constants C constants, not macros. 2015-02-02 14:47:16 -05:00
Maarten Billemont
b3109187e9 Some project cleanup. 2015-01-31 11:08:22 -05:00
Maarten Billemont
a6e7a749bf Android improvements.
[UPDATED]   Opal API
[ADDED]     Scrypt native binaries for more archs.
[IMPROVED]  Android activity secure.
[FIXED]     White background on buttons for some devices.
[IMPROVED]  Android layout.
[WIP]       Remember password on Android.
2015-01-31 10:55:08 -05:00
Maarten Billemont
ca5d83d40c Put LDFLAGS after .o files that use them for compatibility with the 11.10+ linker. 2015-01-29 14:37:15 -05:00
Maarten Billemont
285813324f mpw-js is now back in sync with Tom's repo. 2015-01-23 08:31:50 -05:00
Maarten Billemont
d4b772b467 Update to Tom's latest mpw-js to fix issues with maximum security passwords. 2015-01-23 08:14:13 -05:00
Maarten Billemont
f392ad4053 Merge branch 'master' of github.com:Lyndir/MasterPassword 2015-01-21 11:12:23 -05:00
Maarten Billemont
35990f3bdd Update Java GUI. 2015-01-21 09:11:14 -05:00
Maarten Billemont
3932857c21 Fix for not being able to copy the password from the password field. 2015-01-21 09:07:57 -05:00
Maarten Billemont
6f771a972b his -> their. 2015-01-20 10:37:43 -05:00
Maarten Billemont
806a07135a Update 2.1-cli4 again. 2015-01-19 23:32:26 -05:00
Maarten Billemont
f6b2287778 Build fixes for iOS and Mac. 2015-01-19 23:30:19 -05:00
Maarten Billemont
f4e90bb839 Update 2.1-cli4 2015-01-19 23:23:10 -05:00
Maarten Billemont
21630e919b Standardize includes for other POSIX platforms. 2015-01-19 23:21:10 -05:00
Maarten Billemont
ae74ab6906 Site references to mpw-cli C packages. 2015-01-19 23:07:49 -05:00
Maarten Billemont
caf361cd10 Rename mpw formula. 2015-01-19 21:54:50 -05:00
Maarten Billemont
aeedc1946e Some audit fixes to mpw.rb. 2015-01-19 21:53:41 -05:00
Maarten Billemont
93ae31f679 An initial Homebrew formula for installing mpw. 2015-01-19 21:48:44 -05:00
Maarten Billemont
d5ff215da2 Support for passing the master password non-interactively for testing. 2015-01-19 21:34:54 -05:00
Maarten Billemont
b34f7377da Handle dependencies unpacked by a package manager. 2015-01-19 20:58:53 -05:00
Maarten Billemont
0c2e182039 Release a new C CLI and update links. 2015-01-19 17:22:05 -05:00
219 changed files with 4072 additions and 1548 deletions

4
.gitignore vendored
View File

@@ -1,5 +1,6 @@
# OS-Specific junk. # OS-Specific junk.
.DS_Store .DS_Store
Thumbs.db
# IntelliJ # IntelliJ
/MasterPassword/Java/.idea /MasterPassword/Java/.idea
@@ -28,9 +29,6 @@ Press/MasterPassword_PressKit/MasterPassword_pressrelease_*.pdf
/sendipa/* /sendipa/*
!/sendipa/sendipa.conf !/sendipa/sendipa.conf
# Java
MasterPassword/Java/**/target
# C # C
MasterPassword/C/VERSION MasterPassword/C/VERSION
MasterPassword/C/*.o MasterPassword/C/*.o

2
.gitmodules vendored
View File

@@ -18,4 +18,4 @@
url = git://github.com/jonmarimba/jrswizzle.git url = git://github.com/jonmarimba/jrswizzle.git
[submodule "Site/mpw-js/js/mpw-js"] [submodule "Site/mpw-js/js/mpw-js"]
path = Site/mpw-js/js/mpw-js path = Site/mpw-js/js/mpw-js
url = https://github.com/Lyndir/mpw-js.git url = https://github.com/tmthrgd/mpw-js.git

8
.idea/encodings.xml generated Normal file → Executable file
View File

@@ -1,5 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" /> <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false">
<file url="file://$PROJECT_DIR$/MasterPassword/Java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/MasterPassword/Java/masterpassword-algorithm" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/MasterPassword/Java/masterpassword-cli" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/MasterPassword/Java/masterpassword-gui" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/MasterPassword/Java/masterpassword-model" charset="UTF-8" />
</component>
</project> </project>

2
External/Pearl vendored

View File

@@ -218,3 +218,8 @@ OBJC_EXTERN void CLSNSLogv(NSString *format, va_list args) NS_FORMAT_FUNCTION(1,
- (void)crashlytics:(Crashlytics *)crashlytics didDetectCrashDuringPreviousExecution:(id <CLSCrashReport>)crash; - (void)crashlytics:(Crashlytics *)crashlytics didDetectCrashDuringPreviousExecution:(id <CLSCrashReport>)crash;
@end @end
/**
* `CrashlyticsKit` can be used as a parameter to `[Fabric with:@[CrashlyticsKit]];` in Objective-C. In Swift, simply use `Crashlytics()`
*/
#define CrashlyticsKit [Crashlytics sharedInstance]

View File

@@ -15,13 +15,13 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>FMWK</string> <string>FMWK</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>2.2.5</string> <string>2.2.9</string>
<key>CFBundleSupportedPlatforms</key> <key>CFBundleSupportedPlatforms</key>
<array> <array>
<string>iPhoneOS</string> <string>iPhoneOS</string>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>40</string> <string>44</string>
<key>DTPlatformName</key> <key>DTPlatformName</key>
<string>iphoneos</string> <string>iphoneos</string>
<key>MinimumOSVersion</key> <key>MinimumOSVersion</key>

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,61 @@
#!/usr/bin/env bash
# FIXME
# partials are currently readline words, but these can't be reliably compared against literal data. We need to make them literal first.. in a safe way. Currently using xargs' quote parser as a hack.
# Process literal completion options in COMPREPLY
#
# 1. Filter COMPREPLY by excluding the options that do not match the word that is being completed.
# 2. Shell-escape the COMPREPLY words so they remain syntactical words when injected into the completed command.
# 3. Add a space after the words so successful completions advance to the next word
# (we disabled this default behavior with -o nospace so we can do completions that don't want this, eg. directory names)
_comp_finish_completions() {
local partial=$(xargs <<< "${COMP_WORDS[COMP_CWORD]}") # FIXME
local word words=( "${COMPREPLY[@]}" )
COMPREPLY=()
for word in "${words[@]}"; do
( shopt -s nocasematch; [[ $word = $partial* ]] ) && COMPREPLY+=( "$(printf '%q ' "$word")" )
done
if (( ${#COMPREPLY[@]} > 1 )) && [[ $_comp_title ]]; then
printf '\n%s:' "$_comp_title"
unset _comp_title
fi
}
# Perform pathname completion.
#
# 1. Populate COMPREPLY with pathnames.
# 2. Shell-escape the COMPREPLY words so they remain syntactical words when injected into the completed command.
# 3. Add a space after file names so successful completions advance to the next word.
# Directory names are suffixed with a / instead so we can keep completing the files inside.
_comp_complete_path() {
local partial=$(xargs <<< "${COMP_WORDS[COMP_CWORD]}")
local path
COMPREPLY=()
for path in "$partial"*; do
if [[ -d $path ]]; then
COMPREPLY+=( "$(printf '%q/' "$path")" )
elif [[ -e $path ]]; then
COMPREPLY+=( "$(printf '%q ' "$path")" )
fi
done
}
_show_args() {
echo
local i=0
for arg; do
printf "arg %d: %s\n" "$((i++))" "$arg"
done
i=0
for word in "${COMP_WORDS[@]}"; do
printf "word %d: %s -> %s %s\n" "$i" "$word" "$(xargs <<< "$word")" "$( ((i == $COMP_CWORD)) && echo '<CWORD>' )"
let i++
done
}

View File

@@ -39,7 +39,7 @@ else
fi fi
# Optional features. # Optional features.
mpw_color=0 # Colorized Identicon, requires libncurses-dev mpw_color=1 # Colorized Identicon, requires libncurses-dev
### DEPENDENCIES ### DEPENDENCIES
@@ -81,11 +81,15 @@ unpack() {
mv "$files"/* . mv "$files"/* .
rmdir "$files" rmdir "$files"
fi fi
touch .unpacked
} }
fetchSource() ( fetchSource() (
source .source source .source
if [[ $pkg && -e "${pkg##*/}" ]]; then if [[ -e .unpacked ]]; then
true
elif [[ $pkg && -e "${pkg##*/}" ]]; then
files=( !("${pkg##*/}") ) files=( !("${pkg##*/}") )
[[ -e $files ]] || { [[ -e $files ]] || {
echo echo
@@ -135,11 +139,14 @@ fetchSource() (
exit 1 exit 1
fi fi
if [[ ! -e .patched ]] && (( ${#patches[@]} )); then
for patch in "${patches[@]}"; do for patch in "${patches[@]}"; do
echo echo
echo "Patching: ${PWD##*/}, for $patch..." echo "Patching: ${PWD##*/}, for $patch..."
patch -p0 < ../"${PWD##*/}-$patch.patch" patch -p0 < ../"${PWD##*/}-$patch.patch"
done done
touch .patched
fi
) )
depend() { depend() {
@@ -197,11 +204,11 @@ mpw() {
echo echo
echo "Building target: $target..." echo "Building target: $target..."
CFLAGS=( local CFLAGS=(
# include paths # include paths
-I"lib/include" -I"lib/include"
) )
LDFLAGS=( local LDFLAGS=(
# scrypt # scrypt
"lib/scrypt/scrypt-crypto_aesctr.o" "lib/scrypt/scrypt-crypto_aesctr.o"
"lib/scrypt/scrypt-sha256.o" "lib/scrypt/scrypt-sha256.o"
@@ -220,8 +227,8 @@ mpw() {
cc "${CFLAGS[@]}" "$@" -c mpw-algorithm.c -o mpw-algorithm.o cc "${CFLAGS[@]}" "$@" -c mpw-algorithm.c -o mpw-algorithm.o
cc "${CFLAGS[@]}" "$@" -c mpw-types.c -o mpw-types.o cc "${CFLAGS[@]}" "$@" -c mpw-types.c -o mpw-types.o
cc "${CFLAGS[@]}" "$@" -c mpw-util.c -o mpw-util.o cc "${CFLAGS[@]}" "$@" -c mpw-util.c -o mpw-util.o
cc "${CFLAGS[@]}" "${LDFLAGS[@]}" "$@" "mpw-algorithm.o" "mpw-types.o" "mpw-util.o" \ cc "${CFLAGS[@]}" "$@" "mpw-algorithm.o" "mpw-types.o" "mpw-util.o" \
mpw-cli.c -o mpw "${LDFLAGS[@]}" "mpw-cli.c" -o "mpw"
echo "done! Now run ./install or use ./mpw" echo "done! Now run ./install or use ./mpw"
} }
@@ -233,11 +240,11 @@ mpw-bench() {
echo echo
echo "Building target: $target..." echo "Building target: $target..."
CFLAGS=( local CFLAGS=(
# include paths # include paths
-I"lib/include" -I"lib/include"
) )
LDFLAGS=( local LDFLAGS=(
# scrypt # scrypt
"lib/scrypt/scrypt-crypto_aesctr.o" "lib/scrypt/scrypt-crypto_aesctr.o"
"lib/scrypt/scrypt-sha256.o" "lib/scrypt/scrypt-sha256.o"
@@ -257,8 +264,11 @@ mpw-bench() {
-l"crypto" -l"crypto"
) )
cc "${CFLAGS[@]}" "${LDFLAGS[@]}" "$@" "mpw-algorithm.o" "mpw-types.o" "mpw-util.o" \ cc "${CFLAGS[@]}" "$@" -c mpw-algorithm.c -o mpw-algorithm.o
mpw-bench.c -o mpw-bench cc "${CFLAGS[@]}" "$@" -c mpw-types.c -o mpw-types.o
cc "${CFLAGS[@]}" "$@" -c mpw-util.c -o mpw-util.o
cc "${CFLAGS[@]}" "$@" "mpw-algorithm.o" "mpw-types.o" "mpw-util.o" \
"${LDFLAGS[@]}" "mpw-bench.c" -o "mpw-bench"
echo "done! Now use ./mpw-bench" echo "done! Now use ./mpw-bench"
} }
@@ -269,12 +279,13 @@ mpw-tests() {
echo echo
echo "Building target: $target..." echo "Building target: $target..."
CFLAGS=( local CFLAGS=(
# include paths # include paths
-I"lib/include" -I"lib/include"
-I"/usr/include/libxml2" -I"/usr/include/libxml2"
-I"/usr/local/include/libxml2"
) )
LDFLAGS=( local LDFLAGS=(
# scrypt # scrypt
"lib/scrypt/scrypt-crypto_aesctr.o" "lib/scrypt/scrypt-crypto_aesctr.o"
"lib/scrypt/scrypt-sha256.o" "lib/scrypt/scrypt-sha256.o"
@@ -288,9 +299,12 @@ mpw-tests() {
-l"crypto" -l"xml2" -l"crypto" -l"xml2"
) )
cc "${CFLAGS[@]}" "$@" -c mpw-algorithm.c -o mpw-algorithm.o
cc "${CFLAGS[@]}" "$@" -c mpw-types.c -o mpw-types.o
cc "${CFLAGS[@]}" "$@" -c mpw-util.c -o mpw-util.o
cc "${CFLAGS[@]}" "$@" -c mpw-tests-util.c -o mpw-tests-util.o cc "${CFLAGS[@]}" "$@" -c mpw-tests-util.c -o mpw-tests-util.o
cc "${CFLAGS[@]}" "${LDFLAGS[@]}" "$@" "mpw-algorithm.o" "mpw-types.o" "mpw-util.o" "mpw-tests-util.o" \ cc "${CFLAGS[@]}" "$@" "mpw-algorithm.o" "mpw-types.o" "mpw-util.o" "mpw-tests-util.o" \
mpw-tests.c -o mpw-tests "${LDFLAGS[@]}" "mpw-tests.c" -o "mpw-tests"
echo "done! Now use ./mpw-tests" echo "done! Now use ./mpw-tests"
} }

View File

@@ -6,7 +6,8 @@
// Copyright (c) 2014 Lyndir. All rights reserved. // Copyright (c) 2014 Lyndir. All rights reserved.
// //
#import "mpw-types.h" // NOTE: mpw is currently NOT thread-safe.
#include "mpw-types.h"
typedef enum(unsigned int, MPAlgorithmVersion) { typedef enum(unsigned int, MPAlgorithmVersion) {
/** V0 did math with chars whose signedness was platform-dependent. */ /** V0 did math with chars whose signedness was platform-dependent. */

View File

@@ -9,6 +9,7 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <errno.h> #include <errno.h>
#include <arpa/inet.h>
#include "mpw-types.h" #include "mpw-types.h"
#include "mpw-util.h" #include "mpw-util.h"
@@ -37,7 +38,8 @@ static const char mpw_characterFromClass_v0(char characterClass, uint16_t seedBy
static const uint8_t *mpw_masterKeyForUser_v0(const char *fullName, const char *masterPassword) { static const uint8_t *mpw_masterKeyForUser_v0(const char *fullName, const char *masterPassword) {
const char *mpKeyScope = mpw_scopeForVariant( MPSiteVariantPassword ); const char *mpKeyScope = mpw_scopeForVariant( MPSiteVariantPassword );
trc( "fullName: %s\n", fullName ); trc( "algorithm: v%d\n", 0 );
trc( "fullName: %s (%zu)\n", fullName, mpw_charlen( fullName ) );
trc( "masterPassword: %s\n", masterPassword ); trc( "masterPassword: %s\n", masterPassword );
trc( "key scope: %s\n", mpKeyScope ); trc( "key scope: %s\n", mpKeyScope );
@@ -71,11 +73,16 @@ static const char *mpw_passwordForSite_v0(const uint8_t *masterKey, const char *
const MPSiteVariant siteVariant, const char *siteContext) { const MPSiteVariant siteVariant, const char *siteContext) {
const char *siteScope = mpw_scopeForVariant( siteVariant ); const char *siteScope = mpw_scopeForVariant( siteVariant );
trc( "algorithm: v%d\n", 0 );
trc( "siteName: %s\n", siteName ); trc( "siteName: %s\n", siteName );
trc( "siteCounter: %d\n", siteCounter ); trc( "siteCounter: %d\n", siteCounter );
trc( "siteVariant: %d\n", siteVariant ); trc( "siteVariant: %d\n", siteVariant );
trc( "siteType: %d\n", siteType ); trc( "siteType: %d\n", siteType );
trc( "site scope: %s, context: %s\n", siteScope, siteContext == NULL? "<empty>": siteContext ); trc( "site scope: %s, context: %s\n", siteScope, siteContext? "<empty>": siteContext );
trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)\n",
siteScope, mpw_hex_l( htonl( strlen( siteName ) ) ), siteName,
mpw_hex_l( htonl( siteCounter ) ),
mpw_hex_l( htonl( siteContext? strlen( siteContext ): 0 ) ), siteContext? "(null)": siteContext );
// Calculate the site seed. // Calculate the site seed.
// sitePasswordSeed = hmac-sha256( masterKey, siteScope . #siteName . siteName . siteCounter . #siteContext . siteContext ) // sitePasswordSeed = hmac-sha256( masterKey, siteScope . #siteName . siteName . siteCounter . #siteContext . siteContext )

View File

@@ -9,6 +9,7 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <errno.h> #include <errno.h>
#include <arpa/inet.h>
#include "mpw-types.h" #include "mpw-types.h"
#include "mpw-util.h" #include "mpw-util.h"
@@ -21,7 +22,8 @@
static const uint8_t *mpw_masterKeyForUser_v1(const char *fullName, const char *masterPassword) { static const uint8_t *mpw_masterKeyForUser_v1(const char *fullName, const char *masterPassword) {
const char *mpKeyScope = mpw_scopeForVariant( MPSiteVariantPassword ); const char *mpKeyScope = mpw_scopeForVariant( MPSiteVariantPassword );
trc( "fullName: %s\n", fullName ); trc( "algorithm: v%d\n", 1 );
trc( "fullName: %s (%zu)\n", fullName, mpw_charlen( fullName ) );
trc( "masterPassword: %s\n", masterPassword ); trc( "masterPassword: %s\n", masterPassword );
trc( "key scope: %s\n", mpKeyScope ); trc( "key scope: %s\n", mpKeyScope );
@@ -55,11 +57,16 @@ static const char *mpw_passwordForSite_v1(const uint8_t *masterKey, const char *
const MPSiteVariant siteVariant, const char *siteContext) { const MPSiteVariant siteVariant, const char *siteContext) {
const char *siteScope = mpw_scopeForVariant( siteVariant ); const char *siteScope = mpw_scopeForVariant( siteVariant );
trc( "algorithm: v%d\n", 1 );
trc( "siteName: %s\n", siteName ); trc( "siteName: %s\n", siteName );
trc( "siteCounter: %d\n", siteCounter ); trc( "siteCounter: %d\n", siteCounter );
trc( "siteVariant: %d\n", siteVariant ); trc( "siteVariant: %d\n", siteVariant );
trc( "siteType: %d\n", siteType ); trc( "siteType: %d\n", siteType );
trc( "site scope: %s, context: %s\n", siteScope, siteContext == NULL? "<empty>": siteContext ); trc( "site scope: %s, context: %s\n", siteScope, siteContext? "<empty>": siteContext );
trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)\n",
siteScope, mpw_hex_l( htonl( strlen( siteName ) ) ), siteName,
mpw_hex_l( htonl( siteCounter ) ),
mpw_hex_l( htonl( siteContext? strlen( siteContext ): 0 ) ), siteContext? "(null)": siteContext );
// Calculate the site seed. // Calculate the site seed.
// sitePasswordSeed = hmac-sha256( masterKey, siteScope . #siteName . siteName . siteCounter . #siteContext . siteContext ) // sitePasswordSeed = hmac-sha256( masterKey, siteScope . #siteName . siteName . siteCounter . #siteContext . siteContext )

View File

@@ -9,6 +9,7 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <errno.h> #include <errno.h>
#include <arpa/inet.h>
#include "mpw-types.h" #include "mpw-types.h"
#include "mpw-util.h" #include "mpw-util.h"
@@ -21,7 +22,8 @@
static const uint8_t *mpw_masterKeyForUser_v2(const char *fullName, const char *masterPassword) { static const uint8_t *mpw_masterKeyForUser_v2(const char *fullName, const char *masterPassword) {
const char *mpKeyScope = mpw_scopeForVariant( MPSiteVariantPassword ); const char *mpKeyScope = mpw_scopeForVariant( MPSiteVariantPassword );
trc( "fullName: %s\n", fullName ); trc( "algorithm: v%d\n", 2 );
trc( "fullName: %s (%zu)\n", fullName, mpw_charlen( fullName ) );
trc( "masterPassword: %s\n", masterPassword ); trc( "masterPassword: %s\n", masterPassword );
trc( "key scope: %s\n", mpKeyScope ); trc( "key scope: %s\n", mpKeyScope );
@@ -55,11 +57,16 @@ static const char *mpw_passwordForSite_v2(const uint8_t *masterKey, const char *
const MPSiteVariant siteVariant, const char *siteContext) { const MPSiteVariant siteVariant, const char *siteContext) {
const char *siteScope = mpw_scopeForVariant( siteVariant ); const char *siteScope = mpw_scopeForVariant( siteVariant );
trc( "algorithm: v%d\n", 2 );
trc( "siteName: %s\n", siteName ); trc( "siteName: %s\n", siteName );
trc( "siteCounter: %d\n", siteCounter ); trc( "siteCounter: %d\n", siteCounter );
trc( "siteVariant: %d\n", siteVariant ); trc( "siteVariant: %d\n", siteVariant );
trc( "siteType: %d\n", siteType ); trc( "siteType: %d\n", siteType );
trc( "site scope: %s, context: %s\n", siteScope, siteContext == NULL? "<empty>": siteContext ); trc( "site scope: %s, context: %s\n", siteScope, siteContext? "<empty>": siteContext );
trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)\n",
siteScope, mpw_hex_l( htonl( strlen( siteName ) ) ), siteName,
mpw_hex_l( htonl( siteCounter ) ),
mpw_hex_l( htonl( siteContext? strlen( siteContext ): 0 ) ), siteContext? "(null)": siteContext );
// Calculate the site seed. // Calculate the site seed.
// sitePasswordSeed = hmac-sha256( masterKey, siteScope . #siteName . siteName . siteCounter . #siteContext . siteContext ) // sitePasswordSeed = hmac-sha256( masterKey, siteScope . #siteName . siteName . siteCounter . #siteContext . siteContext )

View File

@@ -9,6 +9,7 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <errno.h> #include <errno.h>
#include <arpa/inet.h>
#include "mpw-types.h" #include "mpw-types.h"
#include "mpw-util.h" #include "mpw-util.h"
@@ -21,7 +22,8 @@
static const uint8_t *mpw_masterKeyForUser_v3(const char *fullName, const char *masterPassword) { static const uint8_t *mpw_masterKeyForUser_v3(const char *fullName, const char *masterPassword) {
const char *mpKeyScope = mpw_scopeForVariant( MPSiteVariantPassword ); const char *mpKeyScope = mpw_scopeForVariant( MPSiteVariantPassword );
trc( "fullName: %s\n", fullName ); trc( "algorithm: v%d\n", 3 );
trc( "fullName: %s (%zu)\n", fullName, strlen( fullName ) );
trc( "masterPassword: %s\n", masterPassword ); trc( "masterPassword: %s\n", masterPassword );
trc( "key scope: %s\n", mpKeyScope ); trc( "key scope: %s\n", mpKeyScope );
@@ -55,11 +57,16 @@ static const char *mpw_passwordForSite_v3(const uint8_t *masterKey, const char *
const MPSiteVariant siteVariant, const char *siteContext) { const MPSiteVariant siteVariant, const char *siteContext) {
const char *siteScope = mpw_scopeForVariant( siteVariant ); const char *siteScope = mpw_scopeForVariant( siteVariant );
trc( "algorithm: v%d\n", 3 );
trc( "siteName: %s\n", siteName ); trc( "siteName: %s\n", siteName );
trc( "siteCounter: %d\n", siteCounter ); trc( "siteCounter: %d\n", siteCounter );
trc( "siteVariant: %d\n", siteVariant ); trc( "siteVariant: %d\n", siteVariant );
trc( "siteType: %d\n", siteType ); trc( "siteType: %d\n", siteType );
trc( "site scope: %s, context: %s\n", siteScope, siteContext == NULL? "<empty>": siteContext ); trc( "site scope: %s, context: %s\n", siteScope, siteContext? "<empty>": siteContext );
trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)\n",
siteScope, mpw_hex_l( htonl( strlen( siteName ) ) ), siteName,
mpw_hex_l( htonl( siteCounter ) ),
mpw_hex_l( htonl( siteContext? strlen( siteContext ): 0 ) ), siteContext? "(null)": siteContext );
// Calculate the site seed. // Calculate the site seed.
// sitePasswordSeed = hmac-sha256( masterKey, siteScope . #siteName . siteName . siteCounter . #siteContext . siteContext ) // sitePasswordSeed = hmac-sha256( masterKey, siteScope . #siteName . siteName . siteCounter . #siteContext . siteContext )

View File

@@ -15,7 +15,6 @@
#include <scrypt/sha256.h> #include <scrypt/sha256.h>
#include <bcrypt/ow-crypt.h> #include <bcrypt/ow-crypt.h>
#include "mpw-types.h"
#include "mpw-algorithm.h" #include "mpw-algorithm.h"
#include "mpw-util.h" #include "mpw-util.h"

View File

@@ -15,7 +15,6 @@
#define ftl(...) do { fprintf( stderr, __VA_ARGS__ ); exit(2); } while (0) #define ftl(...) do { fprintf( stderr, __VA_ARGS__ ); exit(2); } while (0)
#include "mpw-types.h"
#include "mpw-algorithm.h" #include "mpw-algorithm.h"
#include "mpw-util.h" #include "mpw-util.h"
@@ -26,7 +25,7 @@
static void usage() { static void usage() {
fprintf( stderr, "Usage: mpw [-u name] [-t type] [-c counter] site\n\n" ); fprintf( stderr, "Usage: mpw [-u name] [-t type] [-c counter] [-V version] [-v variant] [-C context] [-h] site\n\n" );
fprintf( stderr, " -u name Specify the full name of the user.\n" fprintf( stderr, " -u name Specify the full name of the user.\n"
" Defaults to %s in env.\n\n", MP_env_fullname ); " Defaults to %s in env.\n\n", MP_env_fullname );
fprintf( stderr, " -t type Specify the password's template.\n" fprintf( stderr, " -t type Specify the password's template.\n"
@@ -112,11 +111,16 @@ int main(int argc, char *const argv[]) {
ftl( "Invalid %s: %s\n", MP_env_algorithm, algorithmVersionString ); ftl( "Invalid %s: %s\n", MP_env_algorithm, algorithmVersionString );
// Read the options. // Read the options.
for (int opt; (opt = getopt( argc, argv, "u:t:c:v:V:C:h" )) != -1;) for (int opt; (opt = getopt( argc, argv, "u:P:t:c:v:V:C:h" )) != -1;)
switch (opt) { switch (opt) {
case 'u': case 'u':
fullName = optarg; fullName = optarg;
break; break;
case 'P':
// Do not use this. Passing your master password via the command-line
// is insecure. This is here for non-interactive testing purposes only.
masterPassword = strcpy( malloc( strlen( optarg ) + 1 ), optarg );
break;
case 't': case 't':
siteTypeString = optarg; siteTypeString = optarg;
break; break;

View File

@@ -5,7 +5,6 @@
#define ftl(...) do { fprintf( stderr, __VA_ARGS__ ); exit(2); } while (0) #define ftl(...) do { fprintf( stderr, __VA_ARGS__ ); exit(2); } while (0)
#include "mpw-types.h"
#include "mpw-algorithm.h" #include "mpw-algorithm.h"
#include "mpw-util.h" #include "mpw-util.h"
@@ -22,6 +21,7 @@ int main(int argc, char *const argv[]) {
// Read in the test case. // Read in the test case.
xmlChar *id = mpw_xmlTestCaseString( testCase, "id" ); xmlChar *id = mpw_xmlTestCaseString( testCase, "id" );
uint32_t algorithm = mpw_xmlTestCaseInteger( testCase, "algorithm" );
xmlChar *fullName = mpw_xmlTestCaseString( testCase, "fullName" ); xmlChar *fullName = mpw_xmlTestCaseString( testCase, "fullName" );
xmlChar *masterPassword = mpw_xmlTestCaseString( testCase, "masterPassword" ); xmlChar *masterPassword = mpw_xmlTestCaseString( testCase, "masterPassword" );
xmlChar *keyID = mpw_xmlTestCaseString( testCase, "keyID" ); xmlChar *keyID = mpw_xmlTestCaseString( testCase, "keyID" );
@@ -37,16 +37,20 @@ int main(int argc, char *const argv[]) {
// Run the test case. // Run the test case.
fprintf( stdout, "test case %s... ", id ); fprintf( stdout, "test case %s... ", id );
if (!xmlStrlen( result )) {
fprintf( stdout, "abstract.\n" );
continue;
}
// 1. calculate the master key. // 1. calculate the master key.
const uint8_t *masterKey = mpw_masterKeyForUser( const uint8_t *masterKey = mpw_masterKeyForUser(
(char *)fullName, (char *)masterPassword, MPAlgorithmVersionCurrent ); (char *)fullName, (char *)masterPassword, algorithm );
if (!masterKey) if (!masterKey)
ftl( "Couldn't derive master key." ); ftl( "Couldn't derive master key." );
// 2. calculate the site password. // 2. calculate the site password.
const char *sitePassword = mpw_passwordForSite( const char *sitePassword = mpw_passwordForSite(
masterKey, (char *)siteName, siteType, siteCounter, siteVariant, (char *)siteContext, MPAlgorithmVersionCurrent ); masterKey, (char *)siteName, siteType, siteCounter, siteVariant, (char *)siteContext, algorithm );
mpw_free( masterKey, MP_dkLen ); mpw_free( masterKey, MP_dkLen );
if (!sitePassword) if (!sitePassword)
ftl( "Couldn't derive site password." ); ftl( "Couldn't derive site password." );
@@ -57,7 +61,7 @@ int main(int argc, char *const argv[]) {
else { else {
++failedTests; ++failedTests;
fprintf( stdout, "FAILED! (result %s != expected %s)\n", result, sitePassword ); fprintf( stdout, "FAILED! (got %s != expected %s)\n", sitePassword, result );
} }
// Free test case. // Free test case.

View File

@@ -21,15 +21,17 @@
const MPSiteType mpw_typeWithName(const char *typeName) { const MPSiteType mpw_typeWithName(const char *typeName) {
// Lower-case and trim optionally leading "Generated" string from typeName to standardize it.
size_t stdTypeNameOffset = 0;
size_t stdTypeNameSize = strlen( typeName ); size_t stdTypeNameSize = strlen( typeName );
char stdTypeName[strlen( typeName )]; if (strstr(typeName, "Generated" ) == typeName)
if (stdTypeNameSize > strlen( "generated" )) stdTypeNameSize -= (stdTypeNameOffset = strlen( "Generated" ));
strcpy( stdTypeName, typeName + strlen( "generated" ) ); char stdTypeName[stdTypeNameSize + 1];
else for (size_t c = 0; c < stdTypeNameSize; ++c)
strcpy( stdTypeName, typeName ); stdTypeName[c] = (char)tolower( typeName[c + stdTypeNameOffset] );
for (char *tN = stdTypeName; *tN; ++tN) stdTypeName[stdTypeNameSize] = '\0';
*tN = (char)tolower( *tN );
// Find what site type is represented by the type name.
if (0 == strcmp( stdTypeName, "x" ) || 0 == strcmp( stdTypeName, "max" ) || 0 == strcmp( stdTypeName, "maximum" )) if (0 == strcmp( stdTypeName, "x" ) || 0 == strcmp( stdTypeName, "max" ) || 0 == strcmp( stdTypeName, "maximum" ))
return MPSiteTypeGeneratedMaximum; return MPSiteTypeGeneratedMaximum;
if (0 == strcmp( stdTypeName, "l" ) || 0 == strcmp( stdTypeName, "long" )) if (0 == strcmp( stdTypeName, "l" ) || 0 == strcmp( stdTypeName, "long" ))
@@ -47,11 +49,10 @@ const MPSiteType mpw_typeWithName(const char *typeName) {
if (0 == strcmp( stdTypeName, "p" ) || 0 == strcmp( stdTypeName, "phrase" )) if (0 == strcmp( stdTypeName, "p" ) || 0 == strcmp( stdTypeName, "phrase" ))
return MPSiteTypeGeneratedPhrase; return MPSiteTypeGeneratedPhrase;
fprintf( stderr, "Not a generated type name: %s", stdTypeName ); ftl( "Not a generated type name: %s", stdTypeName );
abort();
} }
inline const char **mpw_templatesForType(MPSiteType type, size_t *count) { const char **mpw_templatesForType(MPSiteType type, size_t *count) {
if (!(type & MPSiteTypeClassGenerated)) { if (!(type & MPSiteTypeClassGenerated)) {
ftl( "Not a generated type: %d", type ); ftl( "Not a generated type: %d", type );
@@ -61,42 +62,42 @@ inline const char **mpw_templatesForType(MPSiteType type, size_t *count) {
switch (type) { switch (type) {
case MPSiteTypeGeneratedMaximum: { case MPSiteTypeGeneratedMaximum: {
*count = 2; return alloc_array( *count, const char *,
return (const char *[]){ "anoxxxxxxxxxxxxxxxxx", "axxxxxxxxxxxxxxxxxno" }; "anoxxxxxxxxxxxxxxxxx", "axxxxxxxxxxxxxxxxxno" );
} }
case MPSiteTypeGeneratedLong: { case MPSiteTypeGeneratedLong: {
*count = 21; return alloc_array( *count, const char *,
return (const char *[]){ "CvcvnoCvcvCvcv", "CvcvCvcvnoCvcv", "CvcvCvcvCvcvno", "CvcvnoCvcvCvcv", "CvcvCvcvnoCvcv", "CvcvCvcvCvcvno",
"CvccnoCvcvCvcv", "CvccCvcvnoCvcv", "CvccCvcvCvcvno", "CvccnoCvcvCvcv", "CvccCvcvnoCvcv", "CvccCvcvCvcvno",
"CvcvnoCvccCvcv", "CvcvCvccnoCvcv", "CvcvCvccCvcvno", "CvcvnoCvccCvcv", "CvcvCvccnoCvcv", "CvcvCvccCvcvno",
"CvcvnoCvcvCvcc", "CvcvCvcvnoCvcc", "CvcvCvcvCvccno", "CvcvnoCvcvCvcc", "CvcvCvcvnoCvcc", "CvcvCvcvCvccno",
"CvccnoCvccCvcv", "CvccCvccnoCvcv", "CvccCvccCvcvno", "CvccnoCvccCvcv", "CvccCvccnoCvcv", "CvccCvccCvcvno",
"CvcvnoCvccCvcc", "CvcvCvccnoCvcc", "CvcvCvccCvccno", "CvcvnoCvccCvcc", "CvcvCvccnoCvcc", "CvcvCvccCvccno",
"CvccnoCvcvCvcc", "CvccCvcvnoCvcc", "CvccCvcvCvccno" }; "CvccnoCvcvCvcc", "CvccCvcvnoCvcc", "CvccCvcvCvccno" );
} }
case MPSiteTypeGeneratedMedium: { case MPSiteTypeGeneratedMedium: {
*count = 2; return alloc_array( *count, const char *,
return (const char *[]){ "CvcnoCvc", "CvcCvcno" }; "CvcnoCvc", "CvcCvcno" );
} }
case MPSiteTypeGeneratedBasic: { case MPSiteTypeGeneratedBasic: {
*count = 3; return alloc_array( *count, const char *,
return (const char *[]){ "aaanaaan", "aannaaan", "aaannaaa" }; "aaanaaan", "aannaaan", "aaannaaa" );
} }
case MPSiteTypeGeneratedShort: { case MPSiteTypeGeneratedShort: {
*count = 1; return alloc_array( *count, const char *,
return (const char *[]){"Cvcn"}; "Cvcn" );
} }
case MPSiteTypeGeneratedPIN: { case MPSiteTypeGeneratedPIN: {
*count = 1; return alloc_array( *count, const char *,
return (const char *[]){ "nnnn" }; "nnnn" );
} }
case MPSiteTypeGeneratedName: { case MPSiteTypeGeneratedName: {
*count = 1; return alloc_array( *count, const char *,
return (const char *[]) {"cvccvcvcv"}; "cvccvcvcv" );
} }
case MPSiteTypeGeneratedPhrase: { case MPSiteTypeGeneratedPhrase: {
*count = 3; return alloc_array( *count, const char *,
return (const char *[]){ "cvcc cvc cvccvcv cvc", "cvc cvccvcvcv cvcv", "cv cvccv cvc cvcvccv" }; "cvcc cvc cvccvcv cvc", "cvc cvccvcvcv cvcv", "cv cvccv cvc cvcvccv" );
} }
default: { default: {
ftl( "Unknown generated type: %d", type ); ftl( "Unknown generated type: %d", type );
@@ -113,15 +114,19 @@ const char *mpw_templateForType(MPSiteType type, uint8_t seedByte) {
if (!count) if (!count)
return NULL; return NULL;
return templates[seedByte % count]; char const *template = templates[seedByte % count];
free( templates );
return template;
} }
const MPSiteVariant mpw_variantWithName(const char *variantName) { const MPSiteVariant mpw_variantWithName(const char *variantName) {
char stdVariantName[strlen( variantName )]; // Lower-case and trim optionally leading "generated" string from typeName to standardize it.
strcpy( stdVariantName, variantName ); size_t stdVariantNameSize = strlen( variantName );
for (char *vN = stdVariantName; *vN; ++vN) char stdVariantName[stdVariantNameSize + 1];
*vN = (char)tolower( *vN ); for (size_t c = 0; c < stdVariantNameSize; ++c)
stdVariantName[c] = (char)tolower( variantName[c] );
stdVariantName[stdVariantNameSize] = '\0';
if (0 == strcmp( stdVariantName, "p" ) || 0 == strcmp( stdVariantName, "password" )) if (0 == strcmp( stdVariantName, "p" ) || 0 == strcmp( stdVariantName, "password" ))
return MPSiteVariantPassword; return MPSiteVariantPassword;

View File

@@ -6,7 +6,10 @@
// Copyright (c) 2014 Lyndir. All rights reserved. // Copyright (c) 2014 Lyndir. All rights reserved.
// //
#ifndef _MPW_TYPES_H
#define _MPW_TYPES_H
#include <stdlib.h> #include <stdlib.h>
#include <stdint.h>
#ifdef NS_ENUM #ifdef NS_ENUM
#define enum(_type, _name) NS_ENUM(_type, _name) #define enum(_type, _name) NS_ENUM(_type, _name)
@@ -72,9 +75,10 @@ const char *mpw_scopeForVariant(MPSiteVariant variant);
const MPSiteType mpw_typeWithName(const char *typeName); const MPSiteType mpw_typeWithName(const char *typeName);
/** /**
* @return An array of internal strings that express the templates to use for the given type. * @return A newly allocated array of internal strings that express the templates to use for the given type.
* The amount of elements in the array is stored in count. * The amount of elements in the array is stored in count.
* If an unsupported type is given, count will be 0 and will return NULL. * If an unsupported type is given, count will be 0 and will return NULL.
* The array needs to be free'ed, the strings themselves must not be free'ed or modified.
*/ */
const char **mpw_templatesForType(MPSiteType type, size_t *count); const char **mpw_templatesForType(MPSiteType type, size_t *count);
/** /**
@@ -92,3 +96,4 @@ const char *mpw_charactersInClass(char characterClass);
*/ */
const char mpw_characterFromClass(char characterClass, uint8_t seedByte); const char mpw_characterFromClass(char characterClass, uint8_t seedByte);
#endif // _MPW_TYPES_H

View File

@@ -9,7 +9,12 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <locale.h>
#ifdef COLOR
#include <unistd.h>
#include <curses.h>
#include <term.h>
#endif
#include <scrypt/sha256.h> #include <scrypt/sha256.h>
#include <scrypt/crypto_scrypt.h> #include <scrypt/crypto_scrypt.h>
@@ -75,7 +80,7 @@ uint8_t const *mpw_scrypt(const size_t keySize, const char *secret, const uint8_
uint8_t const *mpw_hmac_sha256(const uint8_t *key, const size_t keySize, const uint8_t *salt, const size_t saltSize) { uint8_t const *mpw_hmac_sha256(const uint8_t *key, const size_t keySize, const uint8_t *salt, const size_t saltSize) {
uint8_t *const buffer = malloc(32); uint8_t *const buffer = malloc( 32 );
if (!buffer) if (!buffer)
return NULL; return NULL;
@@ -91,20 +96,35 @@ const char *mpw_idForBuf(const void *buf, size_t length) {
return mpw_hex( hash, 32 ); return mpw_hex( hash, 32 );
} }
static char *mpw_hex_buf = NULL; static char **mpw_hex_buf = NULL;
static unsigned int mpw_hex_buf_i = 0;
const char *mpw_hex(const void *buf, size_t length) { const char *mpw_hex(const void *buf, size_t length) {
mpw_hex_buf = realloc( mpw_hex_buf, length * 2 + 1 ); // FIXME
for (size_t kH = 0; kH < length; kH++) if (!mpw_hex_buf) {
sprintf( &(mpw_hex_buf[kH * 2]), "%02X", ((const uint8_t *)buf)[kH] ); mpw_hex_buf = malloc( 10 * sizeof( char * ) );
for (uint8_t i = 0; i < 10; ++i)
mpw_hex_buf[i] = NULL;
}
mpw_hex_buf_i = (mpw_hex_buf_i + 1) % 10;
return mpw_hex_buf; mpw_hex_buf[mpw_hex_buf_i] = realloc( mpw_hex_buf[mpw_hex_buf_i], length * 2 + 1 );
for (size_t kH = 0; kH < length; kH++)
sprintf( &(mpw_hex_buf[mpw_hex_buf_i][kH * 2]), "%02X", ((const uint8_t *)buf)[kH] );
return mpw_hex_buf[mpw_hex_buf_i];
}
const char *mpw_hex_l(uint32_t number) {
return mpw_hex( &number, sizeof( number ) );
} }
#ifdef COLOR #ifdef COLOR
static int putvari; static int putvari;
static char *putvarc = NULL; static char *putvarc = NULL;
static bool istermsetup = false; static int istermsetup = 0;
static void initputvar() { static void initputvar() {
if (putvarc) if (putvarc)
free(putvarc); free(putvarc);
@@ -128,7 +148,8 @@ const char *mpw_identicon(const char *fullName, const char *masterPassword) {
const char *accessory[] = { const char *accessory[] = {
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" }; "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""
};
uint8_t identiconSeed[32]; uint8_t identiconSeed[32];
HMAC_SHA256_Buf( masterPassword, strlen( masterPassword ), fullName, strlen( fullName ), identiconSeed ); HMAC_SHA256_Buf( masterPassword, strlen( masterPassword ), fullName, strlen( fullName ), identiconSeed );
@@ -165,8 +186,33 @@ const char *mpw_identicon(const char *fullName, const char *masterPassword) {
return identicon; return identicon;
} }
const size_t mpw_charlen(const char *string) { /**
* @return the amount of bytes used by UTF-8 to encode a single character that starts with the given byte.
*/
static int mpw_charByteSize(unsigned char utf8Byte) {
setlocale( LC_ALL, "en_US.UTF-8" ); if (!utf8Byte)
return mbstowcs( NULL, string, strlen( string ) ); return 0;
if ((utf8Byte & 0x80) == 0)
return 1;
if ((utf8Byte & 0xC0) != 0xC0)
return 0;
if ((utf8Byte & 0xE0) == 0xC0)
return 2;
if ((utf8Byte & 0xF0) == 0xE0)
return 3;
if ((utf8Byte & 0xF8) == 0xF0)
return 4;
return 0;
}
const size_t mpw_charlen(const char *utf8String) {
size_t charlen = 0;
char *remainingString = (char *)utf8String;
for (int charByteSize; (charByteSize = mpw_charByteSize( (unsigned char)*remainingString )); remainingString += charByteSize)
++charlen;
return charlen;
} }

View File

@@ -6,6 +6,8 @@
// Copyright (c) 2014 Lyndir. All rights reserved. // Copyright (c) 2014 Lyndir. All rights reserved.
// //
#include <stdint.h>
//// Logging. //// Logging.
#ifdef DEBUG #ifdef DEBUG
@@ -13,14 +15,24 @@
#define trc(...) fprintf( stderr, __VA_ARGS__ ) #define trc(...) fprintf( stderr, __VA_ARGS__ )
#endif #endif
#else #else
#ifndef trc
#define trc(...) do {} while (0) #define trc(...) do {} while (0)
#endif #endif
#endif
#ifndef ftl #ifndef ftl
#define ftl(...) do { fprintf( stderr, __VA_ARGS__ ); abort(); } while (0) #define ftl(...) do { fprintf( stderr, __VA_ARGS__ ); abort(); } while (0)
#endif #endif
//// Buffers and memory. //// Buffers and memory.
#define alloc_array(_count, _type, ...) ({ \
_type stackElements[] = { __VA_ARGS__ }; \
_count = sizeof( stackElements ) / sizeof( _type ); \
_type *allocElements = malloc( sizeof( stackElements ) ); \
memcpy( allocElements, stackElements, sizeof( stackElements ) ); \
allocElements; \
})
/** Push a buffer onto a buffer. reallocs the given buffer and appends the given buffer. */ /** Push a buffer onto a buffer. reallocs the given buffer and appends the given buffer. */
void mpw_pushBuf( void mpw_pushBuf(
uint8_t **const buffer, size_t *const bufferSize, const void *pushBuffer, const size_t pushSize); uint8_t **const buffer, size_t *const bufferSize, const void *pushBuffer, const size_t pushSize);
@@ -54,6 +66,7 @@ uint8_t const *mpw_hmac_sha256(
/** Encode a buffer as a string of hexadecimal characters. /** Encode a buffer as a string of hexadecimal characters.
* @return A C-string in a reused buffer, do not free or store it. */ * @return A C-string in a reused buffer, do not free or store it. */
const char *mpw_hex(const void *buf, size_t length); const char *mpw_hex(const void *buf, size_t length);
const char *mpw_hex_l(uint32_t number);
/** Encode a fingerprint for a buffer. /** Encode a fingerprint for a buffer.
* @return A C-string in a reused buffer, do not free or store it. */ * @return A C-string in a reused buffer, do not free or store it. */
const char *mpw_idForBuf(const void *buf, size_t length); const char *mpw_idForBuf(const void *buf, size_t length);
@@ -63,4 +76,5 @@ const char *mpw_identicon(const char *fullName, const char *masterPassword);
//// String utilities. //// String utilities.
const size_t mpw_charlen(const char *string); /** @return The amount of display characters in the given UTF-8 string. */
const size_t mpw_charlen(const char *utf8String);

View File

@@ -0,0 +1,69 @@
#!/usr/bin/env bash
source bashcomplib
# completing the 'mpw' command.
_comp_mpw() {
local optarg= cword=${COMP_WORDS[COMP_CWORD]} pcword=
(( COMP_CWORD )) && pcword=${COMP_WORDS[COMP_CWORD - 1]}
case $pcword in
-u) # complete full names.
COMPREPLY=( ~/.mpw.d/*.mpsites )
[[ -e $COMPREPLY ]] || COMPREPLY=()
COMPREPLY=( "${COMPREPLY[@]##*/}" ) COMPREPLY=( "${COMPREPLY[@]%.mpsites}" )
;;
-t) # complete types.
COMPREPLY=( maximum long medium basic short pin name phrase )
;;
-c) # complete counter.
COMPREPLY=( 1 )
;;
-V) # complete versions.
COMPREPLY=( 0 1 2 3 )
;;
-v) # complete variants.
COMPREPLY=( password login answer )
;;
-C) # complete context.
;;
*)
# previous word is not an option we can complete, complete site name (or option if leading -)
if [[ $cword = -* ]]; then
COMPREPLY=( -u -t -c -V -v -C )
else
local w fullName=$MP_FULLNAME
for (( w = 0; w < ${#COMP_WORDS[@]}; ++w )); do
[[ ${COMP_WORDS[w]} = -u ]] && fullName=$(xargs <<< "${COMP_WORDS[w + 1]}") && break
done
if [[ -e ~/.mpw.d/"$fullName.mpsites" ]]; then
IFS=$'\n' read -d '' -ra COMPREPLY < <(awk -F$'\t' '!/^ *#/{sub(/^ */, "", $2); print $2}' ~/.mpw.d/"$fullName.mpsites")
printf -v _comp_title 'Sites for %s' "$fullName"
else
# Default list from the Alexa Top 500
COMPREPLY=(
163.com 360.cn 9gag.com adobe.com alibaba.com aliexpress.com amazon.com
apple.com archive.org ask.com baidu.com battle.net booking.com buzzfeed.com
chase.com cnn.com comcast.net craigslist.org dailymotion.com dell.com
deviantart.com diply.com disqus.com dropbox.com ebay.com engadget.com
espn.go.com evernote.com facebook.com fedex.com feedly.com flickr.com
flipkart.com github.com gizmodo.com go.com goodreads.com google.com
huffingtonpost.com hulu.com ign.com ikea.com imdb.com imgur.com
indiatimes.com instagram.com jd.com kickass.to kickstarter.com linkedin.com
live.com livedoor.com mail.ru mozilla.org naver.com netflix.com newegg.com
nicovideo.jp nytimes.com pandora.com paypal.com pinterest.com pornhub.com
qq.com rakuten.co.jp reddit.com redtube.com shutterstock.com skype.com
soso.com spiegel.de spotify.com stackexchange.com steampowered.com
stumbleupon.com taobao.com target.com thepiratebay.se tmall.com
torrentz.eu tripadvisor.com tube8.com tubecup.com tudou.com tumblr.com
twitter.com uol.com.br vimeo.com vk.com walmart.com weibo.com whatsapp.com
wikia.com wikipedia.org wired.com wordpress.com xhamster.com xinhuanet.com
xvideos.com yahoo.com yandex.ru yelp.com youku.com youporn.com ziddu.com
)
fi
fi ;;
esac
_comp_finish_completions
}
#complete -F _show_args mpw
complete -o nospace -F _comp_mpw mpw

2
MasterPassword/Java/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
target
dependency-reduced-pom.xml

View File

@@ -7,13 +7,12 @@
<parent> <parent>
<groupId>com.lyndir.masterpassword</groupId> <groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword</artifactId> <artifactId>masterpassword</artifactId>
<version>GIT-SNAPSHOT</version> <version>2.3</version>
</parent> </parent>
<name>Master Password Algorithm Implementation</name> <name>Master Password Algorithm Implementation</name>
<description>The implementation of the Master Password algorithm</description> <description>The implementation of the Master Password algorithm</description>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword-algorithm</artifactId> <artifactId>masterpassword-algorithm</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
@@ -24,12 +23,12 @@
<dependency> <dependency>
<groupId>com.lyndir.lhunath.opal</groupId> <groupId>com.lyndir.lhunath.opal</groupId>
<artifactId>opal-system</artifactId> <artifactId>opal-system</artifactId>
<version>1.6-p6</version> <version>1.6-p8</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.lyndir.lhunath.opal</groupId> <groupId>com.lyndir.lhunath.opal</groupId>
<artifactId>opal-crypto</artifactId> <artifactId>opal-crypto</artifactId>
<version>1.6-p6</version> <version>1.6-p8</version>
</dependency> </dependency>
<!-- EXTERNAL DEPENDENCIES --> <!-- EXTERNAL DEPENDENCIES -->

View File

@@ -0,0 +1,106 @@
package com.lyndir.masterpassword;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import com.google.common.collect.ImmutableMap;
import com.lyndir.lhunath.opal.system.MessageAuthenticationDigests;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.awt.*;
import java.nio.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Map;
/**
* @author lhunath, 15-03-29
*/
public class MPIdenticon {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MPIdenticon.class );
private static final Charset charset = StandardCharsets.UTF_8;
private static final Color[] colors = new Color[]{
Color.RED, Color.GREEN, Color.YELLOW, Color.BLUE, Color.MAGENTA, Color.CYAN, Color.MONO };
private static final char[] leftArm = new char[]{ '╔', '╚', '╰', '═' };
private static final char[] rightArm = new char[]{ '╗', '╝', '╯', '═' };
private static final char[] body = new char[]{ '█', '░', '▒', '▓', '☺', '☻' };
private static final char[] accessory = new char[]{
'◈', '◎', '◐', '◑', '◒', '◓', '☀', '☁', '☂', '☃', '☄', '★', '☆', '☎', '☏', '⎈', '⌂', '☘', '☢', '☣', '☕', '⌚', '⌛', '⏰', '⚡',
'⛄', '⛅', '☔', '♔', '♕', '♖', '♗', '♘', '♙', '♚', '♛', '♜', '♝', '♞', '♟', '♨', '♩', '♪', '♫', '⚐', '⚑', '⚔', '⚖', '⚙', '⚠',
'⌘', '⏎', '✄', '✆', '✈', '✉', '✌' };
private final String fullName;
private final Color color;
private final String text;
public MPIdenticon(String fullName, String masterPassword) {
this( fullName, masterPassword.toCharArray() );
}
public MPIdenticon(String fullName, char[] masterPassword) {
this.fullName = fullName;
byte[] masterPasswordBytes = charset.encode( CharBuffer.wrap( masterPassword ) ).array();
ByteBuffer identiconSeedBytes = ByteBuffer.wrap(
MessageAuthenticationDigests.HmacSHA256.of( masterPasswordBytes, fullName.getBytes( charset ) ) );
Arrays.fill( masterPasswordBytes, (byte) 0 );
IntBuffer identiconSeedBuffer = IntBuffer.allocate( identiconSeedBytes.capacity() );
while (identiconSeedBytes.hasRemaining())
identiconSeedBuffer.put( identiconSeedBytes.get() & 0xFF );
int[] identiconSeed = identiconSeedBuffer.array();
color = colors[identiconSeed[4] % colors.length];
text = strf( "%c%c%c%c", leftArm[identiconSeed[0] % leftArm.length], body[identiconSeed[1] % body.length],
rightArm[identiconSeed[2] % rightArm.length], accessory[identiconSeed[3] % accessory.length] );
}
public String getFullName() {
return fullName;
}
public String getText() {
return text;
}
public Color getColor() {
return color;
}
public enum BackgroundMode {
DARK, LIGHT
}
public enum Color {
RED( "#dc322f", "#dc322f" ),
GREEN( "#859900", "#859900" ),
YELLOW( "#b58900", "#b58900" ),
BLUE( "#268bd2", "#268bd2" ),
MAGENTA( "#d33682", "#d33682" ),
CYAN( "#2aa198", "#2aa198" ),
MONO( "#93a1a1", "#586e75" );
private final String rgbDark;
private final String rgbLight;
Color(final String rgbDark, final String rgbLight) {
this.rgbDark = rgbDark;
this.rgbLight = rgbLight;
}
public java.awt.Color getAWTColor(BackgroundMode backgroundMode) {
switch (backgroundMode) {
case DARK:
return new java.awt.Color( Integer.decode( rgbDark ) );
case LIGHT:
return new java.awt.Color( Integer.decode( rgbLight ) );
}
throw new UnsupportedOperationException( "Unsupported background mode: " + backgroundMode );
}
}
}

View File

@@ -6,6 +6,7 @@ import com.lyndir.lhunath.opal.system.logging.Logger;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.jetbrains.annotations.Contract;
/** /**
@@ -143,11 +144,12 @@ public enum MPSiteType {
} }
/** /**
* @param name The name of the type to look up. It is matched case insensitively. * @param name The name fromInt the type to look up. It is matched case insensitively.
* *
* @return The type registered with the given name. * @return The type registered with the given name.
*/ */
public static MPSiteType forName(final String name) { @Contract("!null -> !null, null -> null")
public static MPSiteType forName(@Nullable final String name) {
if (name == null) if (name == null)
return null; return null;

View File

@@ -3,6 +3,8 @@ package com.lyndir.masterpassword;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.logging.Logger;
import java.util.List; import java.util.List;
import javax.annotation.Nullable;
import org.jetbrains.annotations.Contract;
/** /**
@@ -61,11 +63,12 @@ public enum MPSiteVariant {
throw logger.bug( "No variant for option: %s", option ); throw logger.bug( "No variant for option: %s", option );
} }
/** /**
* @param name The name of the variant to look up. It is matched case insensitively. * @param name The name fromInt the variant to look up. It is matched case insensitively.
* *
* @return The variant registered with the given name. * @return The variant registered with the given name.
*/ */
public static MPSiteVariant forName(final String name) { @Contract("!null -> !null, null -> null")
public static MPSiteVariant forName(@Nullable final String name) {
if (name == null) if (name == null)
return null; return null;

View File

@@ -3,10 +3,8 @@ package com.lyndir.masterpassword;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf; import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.lyndir.lhunath.opal.system.util.MetaObject; import com.lyndir.lhunath.opal.system.util.MetaObject;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**

View File

@@ -1,8 +1,6 @@
package com.lyndir.masterpassword; package com.lyndir.masterpassword;
import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.MetaObject;
import com.lyndir.lhunath.opal.system.util.ObjectMeta;
/** /**

View File

@@ -1,154 +1,165 @@
package com.lyndir.masterpassword; package com.lyndir.masterpassword;
import com.google.common.base.Charsets;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.primitives.Bytes;
import com.lambdaworks.crypto.SCrypt;
import com.lyndir.lhunath.opal.system.*; import com.lyndir.lhunath.opal.system.*;
import com.lyndir.lhunath.opal.system.logging.Logger; 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;
import java.util.Arrays; import java.util.Arrays;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.jetbrains.annotations.NotNull;
/** /**
* @author lhunath, 2014-08-30 * @author lhunath, 2014-08-30
*/ */
public class MasterKey { public abstract class MasterKey {
public static final int ALGORITHM = 1;
public static final String VERSION = "2.1";
@SuppressWarnings("UnusedDeclaration") @SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MasterKey.class ); private static final Logger logger = Logger.get( MasterKey.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 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;
@Nonnull
private final String fullName; private final String fullName;
private final byte[] masterKey;
private boolean valid; @Nullable
private byte[] masterKey;
public MasterKey(final String fullName, final String masterPassword) { public static MasterKey create(final String fullName, final char[] masterPassword) {
return create( Version.CURRENT, fullName, masterPassword );
}
@Nonnull
public static MasterKey create(Version version, final String fullName, final char[] masterPassword) {
switch (version) {
case V0:
return new MasterKeyV0( fullName ).revalidate( masterPassword );
case V1:
return new MasterKeyV1( fullName ).revalidate( masterPassword );
case V2:
return new MasterKeyV2( fullName ).revalidate( masterPassword );
case V3:
return new MasterKeyV3( fullName ).revalidate( masterPassword );
}
throw new UnsupportedOperationException( "Unsupported version: " + version );
}
protected MasterKey(@NotNull final String fullName) {
this.fullName = fullName; this.fullName = fullName;
logger.trc( "fullName: %s", fullName ); logger.trc( "fullName: %s", fullName );
logger.trc( "masterPassword: %s", masterPassword );
long start = System.currentTimeMillis();
byte[] userNameBytes = fullName.getBytes( MP_charset );
byte[] userNameLengthBytes = bytesForInt( userNameBytes.length );
String mpKeyScope = MPSiteVariant.Password.getScope();
byte[] masterKeySalt = Bytes.concat( mpKeyScope.getBytes( MP_charset ), userNameLengthBytes, userNameBytes );
logger.trc( "key scope: %s", mpKeyScope );
logger.trc( "masterKeySalt ID: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) );
try {
masterKey = SCrypt.scrypt( masterPassword.getBytes( MP_charset ), masterKeySalt, MP_N, MP_r, MP_p, MP_dkLen );
valid = true;
logger.trc( "masterKey ID: %s (derived in %.2fs)", CodeUtils.encodeHex( idForBytes( masterKey ) ),
(System.currentTimeMillis() - start) / 1000D );
}
catch (GeneralSecurityException e) {
throw logger.bug( e );
}
} }
@Nullable
protected abstract byte[] deriveKey(final char[] masterPassword);
public abstract Version getAlgorithmVersion();
@NotNull
public String getFullName() { public String getFullName() {
return fullName; return fullName;
} }
@Nonnull
protected byte[] getKey() {
Preconditions.checkState( isValid() );
return Preconditions.checkNotNull( masterKey );
}
public byte[] getKeyID() { public byte[] getKeyID() {
Preconditions.checkState( valid ); return idForBytes( getKey() );
return idForBytes( masterKey );
} }
private byte[] getSubKey(final int subkeyLength) { public abstract String encode(@Nonnull final String siteName, final MPSiteType siteType, int siteCounter,
final MPSiteVariant siteVariant, @Nullable final String siteContext);
Preconditions.checkState( valid ); public boolean isValid() {
byte[] subkey = new byte[Math.min( subkeyLength, masterKey.length )]; return masterKey != null;
System.arraycopy( masterKey, 0, subkey, 0, subkey.length );
return subkey;
}
public String encode(final String siteName, final MPSiteType siteType, int siteCounter, final MPSiteVariant siteVariant,
@Nullable final String siteContext) {
Preconditions.checkState( valid );
Preconditions.checkArgument( siteType.getTypeClass() == MPSiteTypeClass.Generated );
Preconditions.checkArgument( !siteName.isEmpty() );
logger.trc( "siteName: %s", siteName );
logger.trc( "siteCounter: %d", siteCounter );
logger.trc( "siteVariant: %d (%s)", siteVariant.ordinal(), siteVariant );
logger.trc( "siteType: %d (%s)", siteType.ordinal(), siteType );
if (siteCounter == 0)
siteCounter = (int) (System.currentTimeMillis() / (300 * 1000)) * 300;
String siteScope = siteVariant.getScope();
byte[] siteNameBytes = siteName.getBytes( MP_charset );
byte[] siteNameLengthBytes = bytesForInt( siteNameBytes.length );
byte[] siteCounterBytes = bytesForInt( siteCounter );
byte[] siteContextBytes = siteContext == null? null: siteContext.getBytes( MP_charset );
byte[] siteContextLengthBytes = bytesForInt( siteContextBytes == null? 0: siteContextBytes.length );
logger.trc( "site scope: %s, context: %s", siteScope, siteContext == null? "<empty>": siteContext );
logger.trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)", siteScope, CodeUtils.encodeHex( siteNameLengthBytes ),
siteName, CodeUtils.encodeHex( siteCounterBytes ), CodeUtils.encodeHex( siteContextLengthBytes ),
siteContext == null? "(null)": siteContext );
byte[] sitePasswordInfo = Bytes.concat( siteScope.getBytes( MP_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
if (siteContextBytes != null)
sitePasswordInfo = Bytes.concat( sitePasswordInfo, siteContextLengthBytes, siteContextBytes );
logger.trc( "sitePasswordInfo ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) );
byte[] sitePasswordSeed = MP_mac.of( masterKey, sitePasswordInfo );
logger.trc( "sitePasswordSeed ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordSeed ) ) );
Preconditions.checkState( sitePasswordSeed.length > 0 );
int templateIndex = sitePasswordSeed[0] & 0xFF; // Mask the integer's sign.
MPTemplate template = siteType.getTemplateAtRollingIndex( templateIndex );
logger.trc( "type %s, template: %s", siteType, template.getTemplateString() );
StringBuilder password = new StringBuilder( template.length() );
for (int i = 0; i < template.length(); ++i) {
int characterIndex = sitePasswordSeed[i + 1] & 0xFF; // Mask the integer's sign.
MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i );
char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex );
logger.trc( "class %c, index %d (0x%02X) -> character: %c", characterClass.getIdentifier(), characterIndex,
sitePasswordSeed[i + 1], passwordCharacter );
password.append( passwordCharacter );
}
return password.toString();
} }
public void invalidate() { public void invalidate() {
valid = false; if (masterKey != null) {
Arrays.fill( masterKey, (byte) 0 ); Arrays.fill( masterKey, (byte) 0 );
masterKey = null;
}
} }
private static byte[] bytesForInt(final int integer) { public MasterKey revalidate(final char[] masterPassword) {
return ByteBuffer.allocate( MP_intLen / Byte.SIZE ).order( MP_byteOrder ).putInt( integer ).array(); invalidate();
logger.trc( "masterPassword: %s", new String( masterPassword ) );
long start = System.currentTimeMillis();
masterKey = deriveKey( masterPassword );
if (masterKey == null)
logger.dbg( "masterKey calculation failed after %.2fs.", (System.currentTimeMillis() - start) / 1000D );
else
logger.trc( "masterKey ID: %s (derived in %.2fs)", CodeUtils.encodeHex( idForBytes( masterKey ) ),
(System.currentTimeMillis() - start) / 1000D );
return this;
} }
private static byte[] idForBytes(final byte[] bytes) { protected abstract byte[] bytesForInt(final int integer);
return MP_hash.of( bytes );
protected abstract byte[] idForBytes(final byte[] bytes);
public enum Version {
/**
* bugs:
* - does math with chars whose signedness was platform-dependent.
* - miscounted the byte-length fromInt multi-byte site names.
* - miscounted the byte-length fromInt multi-byte full names.
*/
V0,
/**
* bugs:
* - miscounted the byte-length fromInt multi-byte site names.
* - miscounted the byte-length fromInt multi-byte full names.
*/
V1,
/**
* bugs:
* - miscounted the byte-length fromInt multi-byte full names.
*/
V2,
/**
* bugs:
* - no known issues.
*/
V3;
public static final Version CURRENT = V3;
public static Version fromInt(final int algorithmVersion) {
return values()[algorithmVersion];
}
public int toInt() {
return ordinal();
}
public String toBundleVersion() {
switch (this) {
case V0:
return "1.0";
case V1:
return "2.0";
case V2:
return "2.1";
case V3:
return "2.2";
}
throw new UnsupportedOperationException( "Unsupported version: " + this );
}
} }
} }

View File

@@ -0,0 +1,146 @@
package com.lyndir.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.system.*;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.nio.*;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import javax.annotation.Nullable;
/**
* bugs:
* - does math with chars whose signedness was platform-dependent.
* - miscounted the byte-length fromInt multi-byte site names.
* - miscounted the byte-length fromInt multi-byte full names.
*
* @author lhunath, 2014-08-30
*/
public class MasterKeyV0 extends MasterKey {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MasterKeyV0.class );
protected final int MP_N = 32768;
protected final int MP_r = 8;
protected final int MP_p = 2;
protected final int MP_dkLen = 64;
protected final int MP_intLen = 32;
protected final Charset MP_charset = Charsets.UTF_8;
protected final ByteOrder MP_byteOrder = ByteOrder.BIG_ENDIAN;
protected final MessageDigests MP_hash = MessageDigests.SHA256;
protected final MessageAuthenticationDigests MP_mac = MessageAuthenticationDigests.HmacSHA256;
public MasterKeyV0(final String fullName) {
super( fullName );
}
@Override
public Version getAlgorithmVersion() {
return Version.V0;
}
@Nullable
@Override
protected byte[] deriveKey(final char[] masterPassword) {
String fullName = getFullName();
byte[] fullNameBytes = fullName.getBytes( MP_charset );
byte[] fullNameLengthBytes = bytesForInt( fullName.length() );
String mpKeyScope = MPSiteVariant.Password.getScope();
byte[] masterKeySalt = Bytes.concat( mpKeyScope.getBytes( MP_charset ), fullNameLengthBytes, fullNameBytes );
logger.trc( "key scope: %s", mpKeyScope );
logger.trc( "masterKeySalt ID: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) );
ByteBuffer mpBytesBuf = MP_charset.encode( CharBuffer.wrap( masterPassword ) );
byte[] mpBytes = new byte[mpBytesBuf.remaining()];
mpBytesBuf.get( mpBytes, 0, mpBytes.length );
Arrays.fill( mpBytesBuf.array(), (byte) 0 );
try {
return SCrypt.scrypt( mpBytes, masterKeySalt, MP_N, MP_r, MP_p, MP_dkLen );
}
catch (GeneralSecurityException e) {
logger.bug( e );
return null;
}
finally {
Arrays.fill( mpBytes, (byte) 0 );
}
}
public String encode(final String siteName, final MPSiteType siteType, int siteCounter, final MPSiteVariant siteVariant,
@Nullable final String siteContext) {
Preconditions.checkArgument( siteType.getTypeClass() == MPSiteTypeClass.Generated );
Preconditions.checkArgument( !siteName.isEmpty() );
logger.trc( "siteName: %s", siteName );
logger.trc( "siteCounter: %d", siteCounter );
logger.trc( "siteVariant: %d (%s)", siteVariant.ordinal(), siteVariant );
logger.trc( "siteType: %d (%s)", siteType.ordinal(), siteType );
if (siteCounter == 0)
siteCounter = (int) (System.currentTimeMillis() / (300 * 1000)) * 300;
String siteScope = siteVariant.getScope();
byte[] siteNameBytes = siteName.getBytes( MP_charset );
byte[] siteNameLengthBytes = bytesForInt( siteName.length() );
byte[] siteCounterBytes = bytesForInt( siteCounter );
byte[] siteContextBytes = siteContext == null || siteContext.isEmpty()? null: siteContext.getBytes( MP_charset );
byte[] siteContextLengthBytes = bytesForInt( siteContextBytes == null? 0: siteContextBytes.length );
logger.trc( "site scope: %s, context: %s", siteScope, siteContextBytes == null? "<empty>": siteContext );
logger.trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)", siteScope, CodeUtils.encodeHex( siteNameLengthBytes ),
siteName, CodeUtils.encodeHex( siteCounterBytes ), CodeUtils.encodeHex( siteContextLengthBytes ),
siteContextBytes == null? "(null)": siteContext );
byte[] sitePasswordInfo = Bytes.concat( siteScope.getBytes( MP_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
if (siteContextBytes != null)
sitePasswordInfo = Bytes.concat( sitePasswordInfo, siteContextLengthBytes, siteContextBytes );
logger.trc( "sitePasswordInfo ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) );
byte[] sitePasswordSeedBytes = MP_mac.of( getKey(), sitePasswordInfo );
int[] sitePasswordSeed = new int[sitePasswordSeedBytes.length];
for (int i = 0; i < sitePasswordSeedBytes.length; ++i) {
ByteBuffer buf = ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( ByteOrder.BIG_ENDIAN );
Arrays.fill( buf.array(), sitePasswordSeedBytes[i] > 0? (byte)0x00: (byte) 0xFF );
buf.position( 2 );
buf.put( sitePasswordSeedBytes[i] ).rewind();
sitePasswordSeed[i] = buf.getInt() & 0xFFFF;
}
logger.trc( "sitePasswordSeed ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordSeedBytes ) ) );
Preconditions.checkState( sitePasswordSeed.length > 0 );
int templateIndex = sitePasswordSeed[0];
MPTemplate template = siteType.getTemplateAtRollingIndex( templateIndex );
logger.trc( "type %s, template: %s", siteType, template.getTemplateString() );
StringBuilder password = new StringBuilder( template.length() );
for (int i = 0; i < template.length(); ++i) {
int characterIndex = sitePasswordSeed[i + 1];
MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i );
char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex );
logger.trc( "class %c, index %d (0x%02X) -> character: %c", characterClass.getIdentifier(), characterIndex,
sitePasswordSeed[i + 1], passwordCharacter );
password.append( passwordCharacter );
}
return password.toString();
}
@Override
protected byte[] bytesForInt(final int integer) {
return ByteBuffer.allocate( MP_intLen / Byte.SIZE ).order( MP_byteOrder ).putInt( integer ).array();
}
@Override
protected byte[] idForBytes(final byte[] bytes) {
return MP_hash.of( bytes );
}
}

View File

@@ -0,0 +1,82 @@
package com.lyndir.masterpassword;
import com.google.common.base.Preconditions;
import com.google.common.primitives.Bytes;
import com.lyndir.lhunath.opal.system.*;
import com.lyndir.lhunath.opal.system.logging.Logger;
import javax.annotation.Nullable;
/**
* bugs:
* - miscounted the byte-length fromInt multi-byte site names.
* - miscounted the byte-length fromInt multi-byte full names.
*
* @author lhunath, 2014-08-30
*/
public class MasterKeyV1 extends MasterKeyV0 {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MasterKeyV1.class );
public MasterKeyV1(final String fullName) {
super( fullName );
}
@Override
public Version getAlgorithmVersion() {
return Version.V1;
}
public String encode(final String siteName, final MPSiteType siteType, int siteCounter, final MPSiteVariant siteVariant,
@Nullable final String siteContext) {
Preconditions.checkArgument( siteType.getTypeClass() == MPSiteTypeClass.Generated );
Preconditions.checkArgument( !siteName.isEmpty() );
logger.trc( "siteName: %s", siteName );
logger.trc( "siteCounter: %d", siteCounter );
logger.trc( "siteVariant: %d (%s)", siteVariant.ordinal(), siteVariant );
logger.trc( "siteType: %d (%s)", siteType.ordinal(), siteType );
if (siteCounter == 0)
siteCounter = (int) (System.currentTimeMillis() / (300 * 1000)) * 300;
String siteScope = siteVariant.getScope();
byte[] siteNameBytes = siteName.getBytes( MP_charset );
byte[] siteNameLengthBytes = bytesForInt( siteName.length() );
byte[] siteCounterBytes = bytesForInt( siteCounter );
byte[] siteContextBytes = siteContext == null || siteContext.isEmpty()? null: siteContext.getBytes( MP_charset );
byte[] siteContextLengthBytes = bytesForInt( siteContextBytes == null? 0: siteContextBytes.length );
logger.trc( "site scope: %s, context: %s", siteScope, siteContextBytes == null? "<empty>": siteContext );
logger.trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)", siteScope, CodeUtils.encodeHex( siteNameLengthBytes ),
siteName, CodeUtils.encodeHex( siteCounterBytes ), CodeUtils.encodeHex( siteContextLengthBytes ),
siteContextBytes == null? "(null)": siteContext );
byte[] sitePasswordInfo = Bytes.concat( siteScope.getBytes( MP_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
if (siteContextBytes != null)
sitePasswordInfo = Bytes.concat( sitePasswordInfo, siteContextLengthBytes, siteContextBytes );
logger.trc( "sitePasswordInfo ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) );
byte[] sitePasswordSeed = MP_mac.of( getKey(), sitePasswordInfo );
logger.trc( "sitePasswordSeed ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordSeed ) ) );
Preconditions.checkState( sitePasswordSeed.length > 0 );
int templateIndex = sitePasswordSeed[0] & 0xFF; // Mask the integer's sign.
MPTemplate template = siteType.getTemplateAtRollingIndex( templateIndex );
logger.trc( "type %s, template: %s", siteType, template.getTemplateString() );
StringBuilder password = new StringBuilder( template.length() );
for (int i = 0; i < template.length(); ++i) {
int characterIndex = sitePasswordSeed[i + 1] & 0xFF; // Mask the integer's sign.
MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i );
char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex );
logger.trc( "class %c, index %d (0x%02X) -> character: %c", characterClass.getIdentifier(), characterIndex,
sitePasswordSeed[i + 1], passwordCharacter );
password.append( passwordCharacter );
}
return password.toString();
}
}

View File

@@ -0,0 +1,81 @@
package com.lyndir.masterpassword;
import com.google.common.base.Preconditions;
import com.google.common.primitives.Bytes;
import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.lhunath.opal.system.logging.Logger;
import javax.annotation.Nullable;
/**
* bugs:
* - miscounted the byte-length fromInt multi-byte full names.
*
* @author lhunath, 2014-08-30
*/
public class MasterKeyV2 extends MasterKeyV1 {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MasterKeyV2.class );
public MasterKeyV2(final String fullName) {
super( fullName );
}
@Override
public Version getAlgorithmVersion() {
return Version.V2;
}
public String encode(final String siteName, final MPSiteType siteType, int siteCounter, final MPSiteVariant siteVariant,
@Nullable final String siteContext) {
Preconditions.checkArgument( siteType.getTypeClass() == MPSiteTypeClass.Generated );
Preconditions.checkArgument( !siteName.isEmpty() );
logger.trc( "siteName: %s", siteName );
logger.trc( "siteCounter: %d", siteCounter );
logger.trc( "siteVariant: %d (%s)", siteVariant.ordinal(), siteVariant );
logger.trc( "siteType: %d (%s)", siteType.ordinal(), siteType );
if (siteCounter == 0)
siteCounter = (int) (System.currentTimeMillis() / (300 * 1000)) * 300;
String siteScope = siteVariant.getScope();
byte[] siteNameBytes = siteName.getBytes( MP_charset );
byte[] siteNameLengthBytes = bytesForInt( siteNameBytes.length );
byte[] siteCounterBytes = bytesForInt( siteCounter );
byte[] siteContextBytes = siteContext == null || siteContext.isEmpty()? null: siteContext.getBytes( MP_charset );
byte[] siteContextLengthBytes = bytesForInt( siteContextBytes == null? 0: siteContextBytes.length );
logger.trc( "site scope: %s, context: %s", siteScope, siteContextBytes == null? "<empty>": siteContext );
logger.trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)", siteScope, CodeUtils.encodeHex( siteNameLengthBytes ),
siteName, CodeUtils.encodeHex( siteCounterBytes ), CodeUtils.encodeHex( siteContextLengthBytes ),
siteContextBytes == null? "(null)": siteContext );
byte[] sitePasswordInfo = Bytes.concat( siteScope.getBytes( MP_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
if (siteContextBytes != null)
sitePasswordInfo = Bytes.concat( sitePasswordInfo, siteContextLengthBytes, siteContextBytes );
logger.trc( "sitePasswordInfo ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) );
byte[] sitePasswordSeed = MP_mac.of( getKey(), sitePasswordInfo );
logger.trc( "sitePasswordSeed ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordSeed ) ) );
Preconditions.checkState( sitePasswordSeed.length > 0 );
int templateIndex = sitePasswordSeed[0] & 0xFF; // Mask the integer's sign.
MPTemplate template = siteType.getTemplateAtRollingIndex( templateIndex );
logger.trc( "type %s, template: %s", siteType, template.getTemplateString() );
StringBuilder password = new StringBuilder( template.length() );
for (int i = 0; i < template.length(); ++i) {
int characterIndex = sitePasswordSeed[i + 1] & 0xFF; // Mask the integer's sign.
MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i );
char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex );
logger.trc( "class %c, index %d (0x%02X) -> character: %c", characterClass.getIdentifier(), characterIndex,
sitePasswordSeed[i + 1], passwordCharacter );
password.append( passwordCharacter );
}
return password.toString();
}
}

View File

@@ -0,0 +1,62 @@
package com.lyndir.masterpassword;
import com.google.common.primitives.Bytes;
import com.lambdaworks.crypto.SCrypt;
import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import javax.annotation.Nullable;
/**
* bugs:
* - no known issues.
*
* @author lhunath, 2014-08-30
*/
public class MasterKeyV3 extends MasterKeyV2 {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MasterKeyV3.class );
public MasterKeyV3(final String fullName) {
super( fullName );
}
@Override
public Version getAlgorithmVersion() {
return Version.V3;
}
@Nullable
@Override
protected byte[] deriveKey(final char[] masterPassword) {
byte[] fullNameBytes = getFullName().getBytes( MP_charset );
byte[] fullNameLengthBytes = bytesForInt( fullNameBytes.length );
String mpKeyScope = MPSiteVariant.Password.getScope();
byte[] masterKeySalt = Bytes.concat( mpKeyScope.getBytes( MP_charset ), fullNameLengthBytes, fullNameBytes );
logger.trc( "key scope: %s", mpKeyScope );
logger.trc( "masterKeySalt ID: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) );
ByteBuffer mpBytesBuf = MP_charset.encode( CharBuffer.wrap( masterPassword ) );
byte[] mpBytes = new byte[mpBytesBuf.remaining()];
mpBytesBuf.get( mpBytes, 0, mpBytes.length );
Arrays.fill( mpBytesBuf.array(), (byte)0 );
try {
return SCrypt.scrypt( mpBytes, masterKeySalt, MP_N, MP_r, MP_p, MP_dkLen );
}
catch (GeneralSecurityException e) {
logger.bug( e );
return null;
}
finally {
Arrays.fill( mpBytes, (byte) 0 );
}
}
}

View File

@@ -0,0 +1,9 @@
/**
*
* @author lhunath, 15-02-04
*/
@ParametersAreNonnullByDefault package com.lyndir.masterpassword;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@@ -1,10 +1,10 @@
package com.lyndir.masterpassword; package com.lyndir.masterpassword;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*; import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.NNSupplier; import com.lyndir.lhunath.opal.system.util.*;
import com.lyndir.lhunath.opal.system.util.NSupplier;
import java.util.List; import java.util.List;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -25,8 +25,9 @@ public class MPWTests {
@XmlElement(name = "case") @XmlElement(name = "case")
private List<Case> cases; private List<Case> cases;
@Nonnull
public List<Case> getCases() { public List<Case> getCases() {
return cases; return checkNotNull( cases );
} }
public Case getCase(String identifier) { public Case getCase(String identifier) {
@@ -45,6 +46,8 @@ public class MPWTests {
@XmlAttribute @XmlAttribute
private String parent; private String parent;
@XmlElement @XmlElement
private String algorithm;
@XmlElement
private String fullName; private String fullName;
@XmlElement @XmlElement
private String masterPassword; private String masterPassword;
@@ -65,76 +68,86 @@ public class MPWTests {
private transient Case parentCase; private transient Case parentCase;
public void setTests(MPWTests tests) { public void initializeParentHierarchy(MPWTests tests) {
if (parent != null) { if (parent != null) {
parentCase = tests.getCase( parent ); parentCase = tests.getCase( parent );
parentCase.initializeParentHierarchy( tests );
}
algorithm = ifNotNullElse( algorithm, new NNSupplier<String>() {
@Nonnull
@Override
public String get() {
return checkNotNull( parentCase.algorithm );
}
} );
fullName = ifNotNullElse( fullName, new NNSupplier<String>() { fullName = ifNotNullElse( fullName, new NNSupplier<String>() {
@Nonnull @Nonnull
@Override @Override
public String get() { public String get() {
return parentCase.getFullName(); return checkNotNull( parentCase.fullName );
} }
} ); } );
masterPassword = ifNotNullElse( masterPassword, new NNSupplier<String>() { masterPassword = ifNotNullElse( masterPassword, new NNSupplier<String>() {
@Nonnull @Nonnull
@Override @Override
public String get() { public String get() {
return parentCase.getMasterPassword(); return checkNotNull( parentCase.masterPassword );
} }
} ); } );
keyID = ifNotNullElse( keyID, new NNSupplier<String>() { keyID = ifNotNullElse( keyID, new NNSupplier<String>() {
@Nonnull @Nonnull
@Override @Override
public String get() { public String get() {
return parentCase.getKeyID(); return checkNotNull( parentCase.keyID );
} }
} ); } );
siteName = ifNotNullElse( siteName, new NNSupplier<String>() { siteName = ifNotNullElse( siteName, new NNSupplier<String>() {
@Nonnull @Nonnull
@Override @Override
public String get() { public String get() {
return parentCase.getSiteName(); return checkNotNull( parentCase.siteName );
} }
} ); } );
siteCounter = ifNotNullElse( siteCounter, new NNSupplier<Integer>() { siteCounter = ifNotNullElse( siteCounter, new NNSupplier<Integer>() {
@Nonnull @Nonnull
@Override @Override
public Integer get() { public Integer get() {
return parentCase.getSiteCounter(); return checkNotNull( parentCase.siteCounter );
} }
} ); } );
siteType = ifNotNullElse( siteType, new NNSupplier<String>() { siteType = ifNotNullElse( siteType, new NNSupplier<String>() {
@Nonnull @Nonnull
@Override @Override
public String get() { public String get() {
return parentCase.getSiteType().name(); return checkNotNull( parentCase.siteType );
} }
} ); } );
siteVariant = ifNotNullElse( siteVariant, new NNSupplier<String>() { siteVariant = ifNotNullElse( siteVariant, new NNSupplier<String>() {
@Nonnull @Nonnull
@Override @Override
public String get() { public String get() {
return parentCase.getSiteVariant().name(); return checkNotNull( parentCase.siteVariant );
} }
} ); } );
siteContext = ifNotNullElseNullable( siteContext, new NSupplier<String>() { siteContext = ifNotNullElse( siteContext, new NNSupplier<String>() {
@Nonnull @Nonnull
@Override @Override
public String get() { public String get() {
return parentCase.getSiteContext(); return parentCase == null? "": checkNotNull( parentCase.siteContext );
} }
} ); } );
result = ifNotNullElse( result, new NNSupplier<String>() { result = ifNotNullElse( result, new NNSupplier<String>() {
@Nonnull @Nonnull
@Override @Override
public String get() { public String get() {
return parentCase.getResult(); return parentCase == null? "": checkNotNull( parentCase.result );
} }
} ); } );
} }
}
@Nonnull
public String getIdentifier() { public String getIdentifier() {
return identifier; return identifier;
} }
@@ -144,40 +157,53 @@ public class MPWTests {
return parentCase; return parentCase;
} }
@Nonnull
public MasterKey.Version getAlgorithm() {
return MasterKey.Version.fromInt( ConversionUtils.toIntegerNN( algorithm ) );
}
@Nonnull
public String getFullName() { public String getFullName() {
return fullName; return checkNotNull( fullName );
} }
public String getMasterPassword() { @Nonnull
return masterPassword; public char[] getMasterPassword() {
return checkNotNull( masterPassword ).toCharArray();
} }
@Nonnull
public String getKeyID() { public String getKeyID() {
return keyID; return checkNotNull( keyID );
} }
@Nonnull
public String getSiteName() { public String getSiteName() {
return siteName; return checkNotNull( siteName );
} }
public int getSiteCounter() { public int getSiteCounter() {
return siteCounter; return ifNotNullElse( siteCounter, 1 );
} }
@Nonnull
public MPSiteType getSiteType() { public MPSiteType getSiteType() {
return MPSiteType.forName( siteType ); return MPSiteType.forName( checkNotNull( siteType ) );
} }
@Nonnull
public MPSiteVariant getSiteVariant() { public MPSiteVariant getSiteVariant() {
return MPSiteVariant.forName( siteVariant ); return MPSiteVariant.forName( checkNotNull( siteVariant ) );
} }
@Nonnull
public String getSiteContext() { public String getSiteContext() {
return siteContext; return checkNotNull( siteContext );
} }
@Nonnull
public String getResult() { public String getResult() {
return result; return checkNotNull( result );
} }
@Override @Override

View File

@@ -5,6 +5,7 @@ import static org.testng.Assert.*;
import com.google.common.io.Resources; import com.google.common.io.Resources;
import com.lyndir.lhunath.opal.system.CodeUtils; import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.StringUtils;
import java.net.URL; import java.net.URL;
import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBContext;
import org.testng.annotations.BeforeMethod; import org.testng.annotations.BeforeMethod;
@@ -26,7 +27,7 @@ public class MasterKeyTest {
URL testCasesResource = Resources.getResource( "mpw_tests.xml" ); URL testCasesResource = Resources.getResource( "mpw_tests.xml" );
tests = (MPWTests) JAXBContext.newInstance( MPWTests.class ).createUnmarshaller().unmarshal( testCasesResource ); tests = (MPWTests) JAXBContext.newInstance( MPWTests.class ).createUnmarshaller().unmarshal( testCasesResource );
for (MPWTests.Case testCase : tests.getCases()) for (MPWTests.Case testCase : tests.getCases())
testCase.setTests( tests ); testCase.initializeParentHierarchy( tests );
defaultCase = tests.getCase( MPWTests.ID_DEFAULT ); defaultCase = tests.getCase( MPWTests.ID_DEFAULT );
} }
@@ -35,10 +36,15 @@ public class MasterKeyTest {
throws Exception { throws Exception {
for (MPWTests.Case testCase : tests.getCases()) { for (MPWTests.Case testCase : tests.getCases()) {
MasterKey masterKey = new MasterKey( testCase.getFullName(), testCase.getMasterPassword() ); if (testCase.getResult().isEmpty())
continue;
logger.inf( "Running test case: %s [testEncode]", testCase.getIdentifier() );
MasterKey masterKey = MasterKey.create( testCase.getAlgorithm(), testCase.getFullName(), testCase.getMasterPassword() );
assertEquals( assertEquals(
masterKey.encode( testCase.getSiteName(), testCase.getSiteType(), testCase.getSiteCounter(), testCase.getSiteVariant(), masterKey.encode( testCase.getSiteName(), testCase.getSiteType(), testCase.getSiteCounter(), testCase.getSiteVariant(),
testCase.getSiteContext() ), testCase.getResult(), "Failed test case: " + testCase ); testCase.getSiteContext() ), testCase.getResult(), "Failed test case: " + testCase );
logger.inf( "passed!" );
} }
} }
@@ -46,7 +52,7 @@ public class MasterKeyTest {
public void testGetUserName() public void testGetUserName()
throws Exception { throws Exception {
assertEquals( new MasterKey( defaultCase.getFullName(), defaultCase.getMasterPassword() ).getFullName(), assertEquals( MasterKey.create( defaultCase.getFullName(), defaultCase.getMasterPassword() ).getFullName(),
defaultCase.getFullName() ); defaultCase.getFullName() );
} }
@@ -55,8 +61,13 @@ public class MasterKeyTest {
throws Exception { throws Exception {
for (MPWTests.Case testCase : tests.getCases()) { for (MPWTests.Case testCase : tests.getCases()) {
MasterKey masterKey = new MasterKey( testCase.getFullName(), testCase.getMasterPassword() ); if (testCase.getResult().isEmpty())
continue;
logger.inf( "Running test case: %s [testGetKeyID]", testCase.getIdentifier() );
MasterKey masterKey = MasterKey.create( testCase.getFullName(), testCase.getMasterPassword() );
assertEquals( CodeUtils.encodeHex( masterKey.getKeyID() ), testCase.getKeyID(), "Failed test case: " + testCase ); assertEquals( CodeUtils.encodeHex( masterKey.getKeyID() ), testCase.getKeyID(), "Failed test case: " + testCase );
logger.inf( "passed!" );
} }
} }
@@ -65,7 +76,7 @@ public class MasterKeyTest {
throws Exception { throws Exception {
try { try {
MasterKey masterKey = new MasterKey( defaultCase.getFullName(), defaultCase.getMasterPassword() ); MasterKey masterKey = MasterKey.create( defaultCase.getFullName(), defaultCase.getMasterPassword() );
masterKey.invalidate(); masterKey.invalidate();
masterKey.encode( defaultCase.getSiteName(), defaultCase.getSiteType(), defaultCase.getSiteCounter(), masterKey.encode( defaultCase.getSiteName(), defaultCase.getSiteType(), defaultCase.getSiteCounter(),
defaultCase.getSiteVariant(), defaultCase.getSiteContext() ); defaultCase.getSiteVariant(), defaultCase.getSiteContext() );

View File

@@ -1,5 +1,7 @@
<tests> <tests>
<!-- Default values for all parameters. -->
<case id="default"> <case id="default">
<algorithm><!-- current --></algorithm>
<fullName>Robert Lee Mitchell</fullName> <fullName>Robert Lee Mitchell</fullName>
<masterPassword>banana colored duckling</masterPassword> <masterPassword>banana colored duckling</masterPassword>
<keyID>98EEF4D1DF46D849574A82A03C3177056B15DFFCA29BB3899DE4628453675302</keyID> <keyID>98EEF4D1DF46D849574A82A03C3177056B15DFFCA29BB3899DE4628453675302</keyID>
@@ -7,67 +9,271 @@
<siteCounter>1</siteCounter> <siteCounter>1</siteCounter>
<siteType>GeneratedLong</siteType> <siteType>GeneratedLong</siteType>
<siteVariant>Password</siteVariant> <siteVariant>Password</siteVariant>
<result><!-- abstract --></result>
</case>
<!-- Algorithm 3 -->
<case id="v3" parent="default">
<algorithm>3</algorithm>
<result>Jejr5[RepuSosp</result> <result>Jejr5[RepuSosp</result>
</case> </case>
<case id="mb_fullName" parent="default"> <case id="v3_mb_fullName" parent="v3">
<fullName></fullName> <fullName></fullName>
<keyID>1717AA1F9BF5BA56CD0965CDA3D78E6D2E6A1EA8C067A8EA621F3DDAD4A87EB8</keyID> <keyID>1717AA1F9BF5BA56CD0965CDA3D78E6D2E6A1EA8C067A8EA621F3DDAD4A87EB8</keyID>
<result>NopaDajh8=Fene</result> <result>NopaDajh8=Fene</result>
</case> </case>
<case id="mb_masterPassword" parent="default"> <case id="v3_mb_masterPassword" parent="v3">
<masterPassword></masterPassword> <masterPassword></masterPassword>
<keyID>351432B8528A5ABECAB768CA95015097DE76FE14C41E10AF36C67DCFB8917E08</keyID> <keyID>351432B8528A5ABECAB768CA95015097DE76FE14C41E10AF36C67DCFB8917E08</keyID>
<result>QesuHirv5-Xepl</result> <result>QesuHirv5-Xepl</result>
</case> </case>
<case id="mb_siteName" parent="default"> <case id="v3_mb_siteName" parent="v3">
<siteName></siteName> <siteName></siteName>
<result>LiheCuwhSerz6)</result> <result>LiheCuwhSerz6)</result>
</case> </case>
<case id="loginName" parent="default"> <case id="v3_loginName" parent="v3">
<siteVariant>Login</siteVariant> <siteVariant>Login</siteVariant>
<siteType>GeneratedName</siteType> <siteType>GeneratedName</siteType>
<result>wohzaqage</result> <result>wohzaqage</result>
</case> </case>
<case id="securityAnswer" parent="default"> <case id="v3_securityAnswer" parent="v3">
<siteVariant>Answer</siteVariant> <siteVariant>Answer</siteVariant>
<siteType>GeneratedPhrase</siteType> <siteType>GeneratedPhrase</siteType>
<result>xin diyjiqoja hubu</result> <result>xin diyjiqoja hubu</result>
</case> </case>
<case id="securityAnswer_context" parent="securityAnswer"> <case id="v3_securityAnswer_context" parent="v3_securityAnswer">
<siteContext>question</siteContext> <siteContext>question</siteContext>
<result>xogx tem cegyiva jab</result> <result>xogx tem cegyiva jab</result>
</case> </case>
<case id="type_maximum" parent="default"> <case id="v3_type_maximum" parent="v3">
<siteType>GeneratedMaximum</siteType> <siteType>GeneratedMaximum</siteType>
<result>W6@692^B1#&amp;@gVdSdLZ@</result> <result>W6@692^B1#&amp;@gVdSdLZ@</result>
</case> </case>
<case id="type_medium" parent="default"> <case id="v3_type_medium" parent="v3">
<siteType>GeneratedMedium</siteType> <siteType>GeneratedMedium</siteType>
<result>Jej2$Quv</result> <result>Jej2$Quv</result>
</case> </case>
<case id="type_basic" parent="default"> <case id="v3_type_basic" parent="v3">
<siteType>GeneratedBasic</siteType> <siteType>GeneratedBasic</siteType>
<result>WAo2xIg6</result> <result>WAo2xIg6</result>
</case> </case>
<case id="type_short" parent="default"> <case id="v3_type_short" parent="v3">
<siteType>GeneratedShort</siteType> <siteType>GeneratedShort</siteType>
<result>Jej2</result> <result>Jej2</result>
</case> </case>
<case id="type_pin" parent="default"> <case id="v3_type_pin" parent="v3">
<siteType>GeneratedPIN</siteType> <siteType>GeneratedPIN</siteType>
<result>7662</result> <result>7662</result>
</case> </case>
<case id="type_name" parent="default"> <case id="v3_type_name" parent="v3">
<siteType>GeneratedName</siteType> <siteType>GeneratedName</siteType>
<result>jejraquvo</result> <result>jejraquvo</result>
</case> </case>
<case id="type_phrase" parent="default"> <case id="v3_type_phrase" parent="v3">
<siteType>GeneratedPhrase</siteType> <siteType>GeneratedPhrase</siteType>
<result>jejr quv cabsibu tam</result> <result>jejr quv cabsibu tam</result>
</case> </case>
<case id="counter_ceiling" parent="default"> <case id="v3_counter_ceiling" parent="v3">
<siteCounter>4294967295</siteCounter> <siteCounter>4294967295</siteCounter>
<result>XambHoqo6[Peni</result> <result>XambHoqo6[Peni</result>
</case> </case>
<!-- Algorithm 2 -->
<case id="v2" parent="default">
<algorithm>2</algorithm>
<result>Jejr5[RepuSosp</result>
</case>
<case id="v2_mb_fullName" parent="v2">
<fullName></fullName>
<keyID>1717AA1F9BF5BA56CD0965CDA3D78E6D2E6A1EA8C067A8EA621F3DDAD4A87EB8</keyID>
<result>WaqoGuho2[Xaxw</result>
</case>
<case id="v2_mb_masterPassword" parent="v2">
<masterPassword></masterPassword>
<keyID>351432B8528A5ABECAB768CA95015097DE76FE14C41E10AF36C67DCFB8917E08</keyID>
<result>QesuHirv5-Xepl</result>
</case>
<case id="v2_mb_siteName" parent="v2">
<siteName></siteName>
<result>LiheCuwhSerz6)</result>
</case>
<case id="v2_loginName" parent="v2">
<siteVariant>Login</siteVariant>
<siteType>GeneratedName</siteType>
<result>wohzaqage</result>
</case>
<case id="v2_securityAnswer" parent="v2">
<siteVariant>Answer</siteVariant>
<siteType>GeneratedPhrase</siteType>
<result>xin diyjiqoja hubu</result>
</case>
<case id="v2_securityAnswer_context" parent="v2_securityAnswer">
<siteContext>question</siteContext>
<result>xogx tem cegyiva jab</result>
</case>
<case id="v2_type_maximum" parent="v2">
<siteType>GeneratedMaximum</siteType>
<result>W6@692^B1#&amp;@gVdSdLZ@</result>
</case>
<case id="v2_type_medium" parent="v2">
<siteType>GeneratedMedium</siteType>
<result>Jej2$Quv</result>
</case>
<case id="v2_type_basic" parent="v2">
<siteType>GeneratedBasic</siteType>
<result>WAo2xIg6</result>
</case>
<case id="v2_type_short" parent="v2">
<siteType>GeneratedShort</siteType>
<result>Jej2</result>
</case>
<case id="v2_type_pin" parent="v2">
<siteType>GeneratedPIN</siteType>
<result>7662</result>
</case>
<case id="v2_type_name" parent="v2">
<siteType>GeneratedName</siteType>
<result>jejraquvo</result>
</case>
<case id="v2_type_phrase" parent="v2">
<siteType>GeneratedPhrase</siteType>
<result>jejr quv cabsibu tam</result>
</case>
<case id="v2_counter_ceiling" parent="v2">
<siteCounter>4294967295</siteCounter>
<result>XambHoqo6[Peni</result>
</case>
<!-- Algorithm 1 -->
<case id="v1" parent="default">
<algorithm>1</algorithm>
<result>Jejr5[RepuSosp</result>
</case>
<case id="v1_mb_fullName" parent="v1">
<fullName></fullName>
<keyID>1717AA1F9BF5BA56CD0965CDA3D78E6D2E6A1EA8C067A8EA621F3DDAD4A87EB8</keyID>
<result>WaqoGuho2[Xaxw</result>
</case>
<case id="v1_mb_masterPassword" parent="v1">
<masterPassword></masterPassword>
<keyID>351432B8528A5ABECAB768CA95015097DE76FE14C41E10AF36C67DCFB8917E08</keyID>
<result>QesuHirv5-Xepl</result>
</case>
<case id="v1_mb_siteName" parent="v1">
<siteName></siteName>
<result>WawiYarp2@Kodh</result>
</case>
<case id="v1_loginName" parent="v1">
<siteVariant>Login</siteVariant>
<siteType>GeneratedName</siteType>
<result>wohzaqage</result>
</case>
<case id="v1_securityAnswer" parent="v1">
<siteVariant>Answer</siteVariant>
<siteType>GeneratedPhrase</siteType>
<result>xin diyjiqoja hubu</result>
</case>
<case id="v1_securityAnswer_context" parent="v1_securityAnswer">
<siteContext>question</siteContext>
<result>xogx tem cegyiva jab</result>
</case>
<case id="v1_type_maximum" parent="v1">
<siteType>GeneratedMaximum</siteType>
<result>W6@692^B1#&amp;@gVdSdLZ@</result>
</case>
<case id="v1_type_medium" parent="v1">
<siteType>GeneratedMedium</siteType>
<result>Jej2$Quv</result>
</case>
<case id="v1_type_basic" parent="v1">
<siteType>GeneratedBasic</siteType>
<result>WAo2xIg6</result>
</case>
<case id="v1_type_short" parent="v1">
<siteType>GeneratedShort</siteType>
<result>Jej2</result>
</case>
<case id="v1_type_pin" parent="v1">
<siteType>GeneratedPIN</siteType>
<result>7662</result>
</case>
<case id="v1_type_name" parent="v1">
<siteType>GeneratedName</siteType>
<result>jejraquvo</result>
</case>
<case id="v1_type_phrase" parent="v1">
<siteType>GeneratedPhrase</siteType>
<result>jejr quv cabsibu tam</result>
</case>
<case id="v1_counter_ceiling" parent="v1">
<siteCounter>4294967295</siteCounter>
<result>XambHoqo6[Peni</result>
</case>
<!-- Algorithm 0 -->
<case id="v0" parent="default">
<algorithm>0</algorithm>
<result>Feji5@ReduWosh</result>
</case>
<case id="v0_mb_fullName" parent="v0">
<fullName></fullName>
<keyID>1717AA1F9BF5BA56CD0965CDA3D78E6D2E6A1EA8C067A8EA621F3DDAD4A87EB8</keyID>
<result>HajrYudo7@Mamh</result>
</case>
<case id="v0_mb_masterPassword" parent="v0">
<masterPassword></masterPassword>
<keyID>351432B8528A5ABECAB768CA95015097DE76FE14C41E10AF36C67DCFB8917E08</keyID>
<result>MewmDini0]Meho</result>
</case>
<case id="v0_mb_siteName" parent="v0">
<siteName></siteName>
<result>HahiVana2@Nole</result>
</case>
<case id="v0_loginName" parent="v0">
<siteVariant>Login</siteVariant>
<siteType>GeneratedName</siteType>
<result>lozwajave</result>
</case>
<case id="v0_securityAnswer" parent="v0">
<siteVariant>Answer</siteVariant>
<siteType>GeneratedPhrase</siteType>
<result>miy lirfijoja dubu</result>
</case>
<case id="v0_securityAnswer_context" parent="v0_securityAnswer">
<siteContext>question</siteContext>
<result>movm bex gevrica jaf</result>
</case>
<case id="v0_type_maximum" parent="v0">
<siteType>GeneratedMaximum</siteType>
<result>w1!3bA3icmRAc)SS@lwl</result>
</case>
<case id="v0_type_medium" parent="v0">
<siteType>GeneratedMedium</siteType>
<result>Fej7]Jug</result>
</case>
<case id="v0_type_basic" parent="v0">
<siteType>GeneratedBasic</siteType>
<result>wvH7irC1</result>
</case>
<case id="v0_type_short" parent="v0">
<siteType>GeneratedShort</siteType>
<result>Fej7</result>
</case>
<case id="v0_type_pin" parent="v0">
<siteType>GeneratedPIN</siteType>
<result>2117</result>
</case>
<case id="v0_type_name" parent="v0">
<siteType>GeneratedName</siteType>
<result>fejrajugo</result>
</case>
<case id="v0_type_phrase" parent="v0">
<siteType>GeneratedPhrase</siteType>
<result>fejr jug gabsibu bax</result>
</case>
<case id="v0_counter_ceiling" parent="v0">
<siteCounter>4294967295</siteCounter>
<result>QateDojh1@Hecn</result>
</case>
</tests> </tests>

View File

@@ -1,18 +1,18 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.lyndir.masterpassword" package="com.lyndir.masterpassword"
android:versionCode="1" android:versionCode="2003"
android:versionName="GIT-SNAPSHOT"> android:versionName="2.3">
<uses-sdk <uses-sdk
android:minSdkVersion="14" android:minSdkVersion="19"
android:targetSdkVersion="19" /> android:targetSdkVersion="21" />
<application <application
android:icon="@drawable/icon" android:icon="@drawable/icon"
android:label="@string/app_name" android:label="@string/app_name"
android:allowBackup="true"> android:allowBackup="true">
<activity android:name=".EmergencyActivity"> <activity android:name=".EmergencyActivity" android:theme="@style/MPTheme">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />

View File

@@ -0,0 +1,13 @@
To build this module, please ensure you've done the following setup:
1. Installed the Android SDK and fully downloaded the Android SDK platform 21 in it.
2. Set the environment variable ANDROID_HOME in your shell or in ~/.mavenrc to point to the root of your Android SDK install.
3. Installed the Android SDK into your Maven's local repository.
3a. Clone the maven-android-sdk-deployer available from here: https://github.com/mosabua/maven-android-sdk-deployer.git
3b. In the root of this project, run: mvn install -P 5.0
To build this module:
1. Build the parent, by going into 'MasterPassword/Java' and running: mvn clean install
2. Build this module, by going into 'MasterPassword/Java/masterpassword-android' and running: mvn clean install
3. You can then find the APK in: 'MasterPassword/Java/masterpassword-android/target'

View File

@@ -1,3 +0,0 @@
# File used by Eclipse to determine the target system
# Project target.
target=android-16

View File

@@ -7,13 +7,12 @@
<parent> <parent>
<groupId>com.lyndir.masterpassword</groupId> <groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword</artifactId> <artifactId>masterpassword</artifactId>
<version>GIT-SNAPSHOT</version> <version>2.3</version>
</parent> </parent>
<name>Master Password Android</name> <name>Master Password Android</name>
<description>An Android application to the Master Password algorithm</description> <description>An Android application to the Master Password algorithm</description>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword-android</artifactId> <artifactId>masterpassword-android</artifactId>
<packaging>apk</packaging> <packaging>apk</packaging>
@@ -30,7 +29,7 @@
<skip>false</skip> <skip>false</skip>
</zipalign> </zipalign>
<sdk> <sdk>
<platform>19</platform> <platform>21</platform>
</sdk> </sdk>
</configuration> </configuration>
</plugin> </plugin>
@@ -39,9 +38,32 @@
<profiles> <profiles>
<profile> <profile>
<id>sign</id> <id>release</id>
<build> <build>
<plugins> <plugins>
<plugin>
<groupId>com.jayway.maven.plugins.android.generation2</groupId>
<artifactId>android-maven-plugin</artifactId>
<configuration>
<sign>
<debug>false</debug>
</sign>
</configuration>
<executions>
<execution>
<id>manifest-update</id>
<phase>process-resources</phase>
<goals>
<goal>manifest-update</goal>
</goals>
<configuration>
<manifestVersionCodeUpdateFromVersion>true</manifestVersionCodeUpdateFromVersion>
</configuration>
</execution>
</executions>
</plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jarsigner-plugin</artifactId> <artifactId>maven-jarsigner-plugin</artifactId>
@@ -54,14 +76,14 @@
<phase>package</phase> <phase>package</phase>
<inherited>true</inherited> <inherited>true</inherited>
<configuration> <configuration>
<archiveDirectory></archiveDirectory> <archiveDirectory />
<includes> <includes>
<include>target/*.apk</include> <include>target/*.apk</include>
</includes> </includes>
<keystore>release.jks</keystore> <keystore>release.jks</keystore>
<storepass>${env.PASSWORD}</storepass> <storepass>${env.PASSWORD}</storepass>
<keypass>${env.PASSWORD}</keypass> <keypass>${env.PASSWORD}</keypass>
<alias>android</alias> <alias>masterpassword-android</alias>
<arguments> <arguments>
<argument>-sigalg</argument><argument>MD5withRSA</argument> <argument>-sigalg</argument><argument>MD5withRSA</argument>
<argument>-digestalg</argument><argument>SHA1</argument> <argument>-digestalg</argument><argument>SHA1</argument>
@@ -70,16 +92,6 @@
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
<plugin>
<groupId>com.jayway.maven.plugins.android.generation2</groupId>
<artifactId>android-maven-plugin</artifactId>
<inherited>true</inherited>
<configuration>
<sign>
<debug>false</debug>
</sign>
</configuration>
</plugin>
</plugins> </plugins>
</build> </build>
</profile> </profile>
@@ -92,7 +104,7 @@
<dependency> <dependency>
<groupId>com.lyndir.masterpassword</groupId> <groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword-algorithm</artifactId> <artifactId>masterpassword-algorithm</artifactId>
<version>GIT-SNAPSHOT</version> <version>2.3</version>
</dependency> </dependency>
<dependency> <dependency>
@@ -103,22 +115,21 @@
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-android</artifactId> <artifactId>slf4j-android</artifactId>
<version>1.7.13-underscore</version>
</dependency> </dependency>
<!-- clone https://github.com/mosabua/maven-android-sdk-deployer.git
run mvn install -P 4.4 -->
<dependency> <dependency>
<groupId>android</groupId> <groupId>android</groupId>
<artifactId>android</artifactId> <artifactId>android</artifactId>
<version>5.0.1_r2</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.lambdaworks</groupId> <groupId>com.lambdaworks</groupId>
<artifactId>libscrypt</artifactId> <artifactId>scrypt</artifactId>
<version>1.4.0</version> <version>1.4.0-android</version>
<type>so</type> <type>jar</type>
<classifier>android</classifier> <classifier>native</classifier>
<scope>runtime</scope>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@@ -0,0 +1 @@
/Users/lhunath/SpiderOak Hive/secret/release-com.lyndir.masterpassword.jks

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 KiB

After

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<size
android:width="20dp"
android:height="20dp" />
</shape>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -8,82 +8,166 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="20dp"
android:orientation="vertical" android:orientation="vertical"
android:gravity="center"> android:gravity="center">
<View <View
android:layout_width="0dp" android:layout_width="1dp"
android:layout_height="0dp"
android:layout_weight="1" />
<ProgressBar
android:id="@+id/progressView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true" />
<View
android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1" /> android:layout_weight="1" />
<EditText <EditText
android:id="@+id/userNameField" android:id="@+id/fullNameField"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:nextFocusForward="@+id/masterPasswordField"
android:inputType="text|textCapWords|textPersonName" android:inputType="text|textCapWords|textPersonName"
android:hint="@string/userName.hint" android:hint="@string/fullName_hint"
android:gravity="center" android:gravity="center"
android:textColor="#FFFFFF" android:textColor="#FFFFFF"
android:textSize="26sp" /> android:textSize="26sp" />
<EditText <CheckBox
android:id="@+id/masterPasswordField" android:id="@+id/rememberFullNameField"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:nextFocusForward="@+id/rememberPasswordField"
android:textSize="14sp"
android:textColor="@android:color/tertiary_text_dark"
android:text="@string/remember" />
<EditText
android:id="@id/masterPasswordField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nextFocusForward="@+id/siteNameField"
android:inputType="text|textPassword" android:inputType="text|textPassword"
android:hint="@string/masterPassword.hint" android:hint="@string/masterPassword_hint"
android:password="true"
android:gravity="center" android:gravity="center"
android:textColor="#FFFFFF" android:textColor="#FFFFFF"
android:textSize="18sp" /> android:textSize="18sp" />
<ImageView <CheckBox
android:layout_width="wrap_content" android:id="@id/rememberPasswordField"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:src="@drawable/double_" android:textSize="14sp"
android:contentDescription="@string/empty" /> android:textColor="@android:color/tertiary_text_dark"
android:text="@string/forgetOnClose" />
<EditText <EditText
android:id="@+id/siteNameField" android:id="@id/siteNameField"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:nextFocusForward="@+id/sitePasswordField"
android:inputType="text|textNoSuggestions|textUri" android:inputType="text|textNoSuggestions|textUri"
android:hint="@string/siteName.hint" android:hint="@string/siteName_hint"
android:gravity="center" android:gravity="center"
android:textColor="#FFFFFF" android:textColor="#FFFFFF"
android:textSize="26sp" /> android:textSize="18sp" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ProgressBar
android:id="@+id/progressView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="20dp"
android:indeterminate="true" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical">
<Button <Button
android:id="@+id/sitePasswordField" android:id="@id/sitePasswordField"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:nextFocusForward="@+id/siteTypeField"
android:gravity="center" android:gravity="center"
android:background="@null" android:background="@android:color/transparent"
android:textColor="#FFFFFF" android:textColor="#FFFFFF"
android:textSize="32sp" android:textSize="32sp"
android:text="LuxdZozvDuma4[" android:text="LuxdZozvDuma4["
android:onClick="copySitePassword" /> android:onClick="copySitePassword" />
<Spinner <TextView
android:id="@+id/typeField" android:id="@+id/sitePasswordTip"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content"
android:labelFor="@id/sitePasswordField"
android:gravity="center"
android:background="@android:color/transparent"
android:textSize="14sp"
android:textColor="@android:color/tertiary_text_dark"
android:text="@string/sitePassword_hint" />
<NumberPicker </LinearLayout>
android:id="@+id/counterField" </FrameLayout>
android:layout_width="wrap_content"
android:layout_height="wrap_content" /> <CheckBox
android:id="@+id/maskPasswordField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textColor="@android:color/tertiary_text_dark"
android:text="@string/maskPassword" />
<Spinner
android:id="@id/siteTypeField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nextFocusForward="@+id/counterField"
android:gravity="center" />
<EditText
android:id="@id/counterField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nextFocusForward="@+id/siteVersionField"
android:gravity="center"
android:inputType="text|textNoSuggestions"
android:textColor="#FFFFFF"
android:textSize="18sp"
android:text="1" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:labelFor="@id/counterField"
android:gravity="center"
android:background="@android:color/transparent"
android:textSize="14sp"
android:textColor="@android:color/tertiary_text_dark"
android:text="@string/siteCounter_hint" />
<Spinner
android:id="@id/siteVersionField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nextFocusForward="@id/rememberFullNameField"
android:gravity="center" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:labelFor="@id/siteVersionField"
android:gravity="center"
android:background="@android:color/transparent"
android:textSize="14sp"
android:textColor="@android:color/tertiary_text_dark"
android:text="@string/siteVersion_hint" />
<View
android:layout_width="1dp"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout> </LinearLayout>

View File

@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/background">
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:fillViewport="true">
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<View
android:layout_width="match_parent"
android:layout_height="0dp" />
<LinearLayout
android:id="@+id/users"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:divider="@drawable/divider20"
android:showDividers="middle" />
<View
android:layout_width="match_parent"
android:layout_height="0dp" />
</LinearLayout>
</HorizontalScrollView>
</FrameLayout>

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/userName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableTop="@drawable/avatar0"
android:drawablePadding="8dp"
android:text="Maarten Billemont" />

View File

@@ -1,9 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">Master Password</string> <string name="app_name">Master Password</string>
<string name="avatar">User Avatar</string> <string name="remember">Remember</string>
<string name="siteName.hint">Site Name</string> <string name="forgetOnClose">Forget on close</string>
<string name="userName.hint">Your Name</string> <string name="maskPassword">Hide password</string>
<string name="masterPassword.hint">Your Master Password</string> <string name="fullName_hint">Your full name</string>
<string name="masterPassword_hint">Your master password</string>
<string name="siteName_hint">eg. google.com</string>
<string name="sitePassword_hint">Tap to copy</string>
<string name="siteCounter_hint">Password #</string>
<string name="siteVersion_hint">Algorithm</string>
<string name="empty" /> <string name="empty" />
</resources> </resources>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="MPTheme" parent="android:Theme.Holo.Dialog.MinWidth">
</style>
</resources>

View File

@@ -2,26 +2,34 @@ package com.lyndir.masterpassword;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf; import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import android.app.Activity; import android.app.*;
import android.content.*; import android.content.*;
import android.content.ClipboardManager;
import android.graphics.Paint; import android.graphics.Paint;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.text.Editable; import android.text.*;
import android.text.TextWatcher; import android.text.method.PasswordTransformationMethod;
import android.view.View; import android.view.View;
import android.view.WindowManager;
import android.widget.*; import android.widget.*;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import butterknife.InjectView; import butterknife.InjectView;
import com.google.common.base.Throwables; import com.google.common.base.Throwables;
import com.google.common.util.concurrent.*; import com.google.common.util.concurrent.*;
import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
import java.util.*;
import java.util.concurrent.*; import java.util.concurrent.*;
import javax.annotation.Nullable;
public class EmergencyActivity extends Activity { public class EmergencyActivity extends Activity {
@SuppressWarnings("UnusedDeclaration") @SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( EmergencyActivity.class ); private static final Logger logger = Logger.get( EmergencyActivity.class );
private static final ClipData EMPTY_CLIP = new ClipData( new ClipDescription( "", new String[0] ), new ClipData.Item( "" ) );
private static final int PASSWORD_NOTIFICATION = 0;
private final ListeningExecutorService executor = MoreExecutors.listeningDecorator( Executors.newSingleThreadExecutor() ); private final ListeningExecutorService executor = MoreExecutors.listeningDecorator( Executors.newSingleThreadExecutor() );
private final ValueChangedListener updateMasterKey = new ValueChangedListener() { private final ValueChangedListener updateMasterKey = new ValueChangedListener() {
@@ -42,8 +50,8 @@ public class EmergencyActivity extends Activity {
@InjectView(R.id.progressView) @InjectView(R.id.progressView)
ProgressBar progressView; ProgressBar progressView;
@InjectView(R.id.userNameField) @InjectView(R.id.fullNameField)
EditText userNameField; EditText fullNameField;
@InjectView(R.id.masterPasswordField) @InjectView(R.id.masterPasswordField)
EditText masterPasswordField; EditText masterPasswordField;
@@ -51,101 +59,184 @@ public class EmergencyActivity extends Activity {
@InjectView(R.id.siteNameField) @InjectView(R.id.siteNameField)
EditText siteNameField; EditText siteNameField;
@InjectView(R.id.typeField) @InjectView(R.id.siteTypeField)
Spinner typeField; Spinner siteTypeField;
@InjectView(R.id.counterField) @InjectView(R.id.counterField)
NumberPicker counterField; EditText counterField;
@InjectView(R.id.siteVersionField)
Spinner siteVersionField;
@InjectView(R.id.sitePasswordField) @InjectView(R.id.sitePasswordField)
TextView sitePasswordField; Button sitePasswordField;
@InjectView(R.id.sitePasswordTip)
TextView sitePasswordTip;
@InjectView(R.id.rememberFullNameField)
CheckBox rememberFullNameField;
@InjectView(R.id.rememberPasswordField)
CheckBox forgetPasswordField;
@InjectView(R.id.maskPasswordField)
CheckBox maskPasswordField;
private int hc_userName; private int hc_userName;
private int hc_masterPassword; private int hc_masterPassword;
private String sitePassword;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate( savedInstanceState ); super.onCreate( savedInstanceState );
Res.init( getResources() ); Res.init( getResources() );
getWindow().setFlags( WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE );
setContentView( R.layout.activity_emergency ); setContentView( R.layout.activity_emergency );
ButterKnife.inject( this ); ButterKnife.inject( this );
userNameField.setOnFocusChangeListener( updateMasterKey ); fullNameField.setOnFocusChangeListener( updateMasterKey );
masterPasswordField.setOnFocusChangeListener( updateMasterKey ); masterPasswordField.setOnFocusChangeListener( updateMasterKey );
siteNameField.addTextChangedListener( updateSitePassword ); siteNameField.addTextChangedListener( updateSitePassword );
typeField.setOnItemSelectedListener( updateSitePassword ); siteTypeField.setOnItemSelectedListener( updateSitePassword );
counterField.setOnValueChangedListener( updateSitePassword ); counterField.addTextChangedListener( updateSitePassword );
siteVersionField.setOnItemSelectedListener( updateMasterKey );
sitePasswordField.addTextChangedListener( new ValueChangedListener() {
@Override
void update() {
boolean noPassword = TextUtils.isEmpty( sitePasswordField.getText() );
sitePasswordTip.setVisibility( noPassword? View.INVISIBLE: View.VISIBLE );
userNameField.setTypeface( Res.exo_Thin ); if (noPassword)
userNameField.setPaintFlags( userNameField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG ); sitePassword = null;
}
} );
fullNameField.setTypeface( Res.exo_Thin );
fullNameField.setPaintFlags( fullNameField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
masterPasswordField.setTypeface( Res.sourceCodePro_ExtraLight ); masterPasswordField.setTypeface( Res.sourceCodePro_ExtraLight );
masterPasswordField.setPaintFlags( userNameField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG ); masterPasswordField.setPaintFlags( masterPasswordField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
siteNameField.setTypeface( Res.exo_Regular ); siteNameField.setTypeface( Res.exo_Regular );
siteNameField.setPaintFlags( userNameField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG ); siteNameField.setPaintFlags( siteNameField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
sitePasswordField.setTypeface( Res.sourceCodePro_Black ); sitePasswordField.setTypeface( Res.sourceCodePro_Black );
sitePasswordField.setPaintFlags( userNameField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG ); sitePasswordField.setPaintFlags( sitePasswordField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
typeField.setAdapter( new ArrayAdapter<>( this, R.layout.type_item, MPSiteType.forClass( MPSiteTypeClass.Generated ) ) ); siteTypeField.setAdapter( new ArrayAdapter<>( this, R.layout.spinner_item, MPSiteType.forClass( MPSiteTypeClass.Generated ) ) );
typeField.setSelection( MPSiteType.GeneratedLong.ordinal() ); siteTypeField.setSelection( MPSiteType.GeneratedLong.ordinal() );
counterField.setMinValue( 1 ); siteVersionField.setAdapter( new ArrayAdapter<>( this, R.layout.spinner_item, MasterKey.Version.values() ) );
counterField.setMaxValue( Integer.MAX_VALUE ); siteVersionField.setSelection( MasterKey.Version.CURRENT.ordinal() );
counterField.setWrapSelectorWheel( false );
rememberFullNameField.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
getPreferences( MODE_PRIVATE ).edit().putBoolean( "rememberFullName", isChecked ).apply();
if (isChecked)
getPreferences( MODE_PRIVATE ).edit().putString( "fullName", fullNameField.getText().toString() ).apply();
else
getPreferences( MODE_PRIVATE ).edit().putString( "fullName", "" ).apply();
}
} );
forgetPasswordField.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
getPreferences( MODE_PRIVATE ).edit().putBoolean( "forgetPassword", isChecked ).apply();
}
} );
maskPasswordField.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
getPreferences( MODE_PRIVATE ).edit().putBoolean( "maskPassword", isChecked ).apply();
sitePasswordField.setTransformationMethod( isChecked? new PasswordTransformationMethod(): null );
}
} );
} }
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
userNameField.setText( getPreferences( MODE_PRIVATE ).getString( "userName", "" ) ); fullNameField.setText( getPreferences( MODE_PRIVATE ).getString( "fullName", "" ) );
rememberFullNameField.setChecked( isRememberFullNameEnabled() );
forgetPasswordField.setChecked( isForgetPasswordEnabled() );
maskPasswordField.setChecked( isMaskPasswordEnabled() );
sitePasswordField.setTransformationMethod( isMaskPasswordEnabled()? new PasswordTransformationMethod(): null );
if (TextUtils.isEmpty( masterPasswordField.getText() ))
masterPasswordField.requestFocus(); masterPasswordField.requestFocus();
else
siteNameField.requestFocus();
} }
@Override @Override
protected void onPause() { protected void onPause() {
if (isForgetPasswordEnabled()) {
synchronized (this) { synchronized (this) {
hc_userName = hc_masterPassword = 0; hc_userName = hc_masterPassword = 0;
if (masterKeyFuture != null) { if (masterKeyFuture != null) {
masterKeyFuture.cancel( true ); masterKeyFuture.cancel( true );
masterKeyFuture = null; masterKeyFuture = null;
} }
masterPasswordField.setText( "" );
}
} }
siteNameField.setText( "" );
sitePasswordField.setText( "" ); sitePasswordField.setText( "" );
progressView.setVisibility( View.INVISIBLE ); progressView.setVisibility( View.INVISIBLE );
super.onPause(); super.onPause();
} }
private synchronized void updateMasterKey() { private boolean isRememberFullNameEnabled() {
final String userName = userNameField.getText().toString(); return getPreferences( MODE_PRIVATE ).getBoolean( "rememberFullName", false );
final String masterPassword = masterPasswordField.getText().toString(); }
if (userName.hashCode() == hc_userName && masterPassword.hashCode() == hc_masterPassword)
return;
hc_userName = userName.hashCode();
hc_masterPassword = masterPassword.hashCode();
SharedPreferences.Editor pref = getPreferences( MODE_PRIVATE ).edit(); private boolean isForgetPasswordEnabled() {
pref.putString( "userName", userName ); return getPreferences( MODE_PRIVATE ).getBoolean( "forgetPassword", false );
pref.apply(); }
private boolean isMaskPasswordEnabled() {
return getPreferences( MODE_PRIVATE ).getBoolean( "maskPassword", false );
}
private synchronized void updateMasterKey() {
final String fullName = fullNameField.getText().toString();
final char[] masterPassword = masterPasswordField.getText().toString().toCharArray();
final MasterKey.Version version = (MasterKey.Version) siteVersionField.getSelectedItem();
try {
if (fullName.hashCode() == hc_userName && Arrays.hashCode( masterPassword ) == hc_masterPassword &&
masterKeyFuture != null && masterKeyFuture.get().getAlgorithmVersion() == version)
return;
}
catch (InterruptedException | ExecutionException e) {
return;
}
hc_userName = fullName.hashCode();
hc_masterPassword = Arrays.hashCode( masterPassword );
if (isRememberFullNameEnabled())
getPreferences( MODE_PRIVATE ).edit().putString( "fullName", fullName ).apply();
if (masterKeyFuture != null) if (masterKeyFuture != null)
masterKeyFuture.cancel( true ); masterKeyFuture.cancel( true );
if (userName.isEmpty() || masterPassword.isEmpty()) { if (fullName.isEmpty() || masterPassword.length == 0) {
sitePasswordField.setText( "" ); sitePasswordField.setText( "" );
progressView.setVisibility( View.INVISIBLE ); progressView.setVisibility( View.INVISIBLE );
return; return;
} }
sitePasswordField.setText( "" );
progressView.setVisibility( View.VISIBLE ); progressView.setVisibility( View.VISIBLE );
(masterKeyFuture = executor.submit( new Callable<MasterKey>() { (masterKeyFuture = executor.submit( new Callable<MasterKey>() {
@Override @Override
public MasterKey call() public MasterKey call()
throws Exception { throws Exception {
try { try {
return new MasterKey( userName, masterPassword ); return MasterKey.create( version, fullName, masterPassword );
} }
catch (RuntimeException e) { catch (RuntimeException e) {
sitePasswordField.setText( "" ); sitePasswordField.setText( "" );
@@ -169,21 +260,25 @@ public class EmergencyActivity extends Activity {
private void updateSitePassword() { private void updateSitePassword() {
final String siteName = siteNameField.getText().toString(); final String siteName = siteNameField.getText().toString();
final MPSiteType type = (MPSiteType) typeField.getSelectedItem(); final MPSiteType type = (MPSiteType) siteTypeField.getSelectedItem();
final int counter = counterField.getValue(); final int counter = ConversionUtils.toIntegerNN( counterField.getText() );
if (masterKeyFuture == null || siteName.isEmpty() || type == null) { if (masterKeyFuture == null || siteName.isEmpty() || type == null) {
sitePasswordField.setText( "" ); sitePasswordField.setText( "" );
progressView.setVisibility( View.INVISIBLE ); progressView.setVisibility( View.INVISIBLE );
if (masterKeyFuture == null)
updateMasterKey();
return; return;
} }
sitePasswordField.setText( "" );
progressView.setVisibility( View.VISIBLE ); progressView.setVisibility( View.VISIBLE );
executor.submit( new Runnable() { executor.submit( new Runnable() {
@Override @Override
public void run() { public void run() {
try { try {
final String sitePassword = masterKeyFuture.get().encode( siteName, type, counter, MPSiteVariant.Password, null ); sitePassword = masterKeyFuture.get().encode( siteName, type, counter, MPSiteVariant.Password, null );
runOnUiThread( new Runnable() { runOnUiThread( new Runnable() {
@Override @Override
@@ -214,19 +309,45 @@ public class EmergencyActivity extends Activity {
} }
public void copySitePassword(View view) { public void copySitePassword(View view) {
String sitePassword = sitePasswordField.getText().toString(); final String currentSitePassword = this.sitePassword;
if (sitePassword.isEmpty()) if (TextUtils.isEmpty( currentSitePassword ))
return; return;
ClipDescription description = new ClipDescription( strf( "Password for %s", siteNameField.getText() ), final ClipboardManager clipboardManager = (ClipboardManager) getSystemService( CLIPBOARD_SERVICE );
new String[]{ ClipDescription.MIMETYPE_TEXT_PLAIN } ); final NotificationManager notificationManager = (NotificationManager) getSystemService( Context.NOTIFICATION_SERVICE );
((ClipboardManager) getSystemService( CLIPBOARD_SERVICE )).setPrimaryClip(
new ClipData( description, new ClipData.Item( sitePassword ) ) );
Intent startMain = new Intent(Intent.ACTION_MAIN); String title = strf( "Password for %s", siteNameField.getText() );
startMain.addCategory(Intent.CATEGORY_HOME); ClipDescription description = new ClipDescription( title, new String[]{ ClipDescription.MIMETYPE_TEXT_PLAIN } );
startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); clipboardManager.setPrimaryClip( new ClipData( description, new ClipData.Item( currentSitePassword ) ) );
startActivity(startMain);
Notification.Builder notificationBuilder = new Notification.Builder( this ).setContentTitle( title )
.setContentText( "Paste the password into your app." )
.setSmallIcon( R.drawable.icon )
.setAutoCancel( true );
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
notificationBuilder.setVisibility( Notification.VISIBILITY_SECRET )
.setCategory( Notification.CATEGORY_RECOMMENDATION )
.setLocalOnly( true );
notificationManager.notify( PASSWORD_NOTIFICATION, notificationBuilder.build() );
final Timer timer = new Timer();
timer.schedule( new TimerTask() {
@Override
public void run() {
ClipData clip = clipboardManager.getPrimaryClip();
for (int i = 0; i < clip.getItemCount(); ++i)
if (currentSitePassword.equals( clip.getItemAt( i ).coerceToText( EmergencyActivity.this ) )) {
clipboardManager.setPrimaryClip( EMPTY_CLIP );
break;
}
notificationManager.cancel( PASSWORD_NOTIFICATION );
timer.cancel();
}
}, 20000 );
Intent startMain = new Intent( Intent.ACTION_MAIN );
startMain.addCategory( Intent.CATEGORY_HOME );
startMain.setFlags( Intent.FLAG_ACTIVITY_NEW_TASK );
startActivity( startMain );
} }
private abstract class ValueChangedListener private abstract class ValueChangedListener

Some files were not shown because too many files have changed in this diff Show More