2
0

Compare commits

...

36 Commits

Author SHA1 Message Date
Maarten Billemont
b779ff5d1c Added C tests for mpw_tests.xml 2014-12-21 23:45:19 -05:00
Maarten Billemont
73c10906e3 Some better memory maintenance. 2014-12-21 12:37:21 -05:00
Maarten Billemont
0ccd545dd4 More restructuring and rewriting of the C code. 2014-12-20 14:30:34 -05:00
Maarten Billemont
49da0b47c7 Complete an initial rewrite/restructure of mpw.c 2014-12-20 00:21:03 -05:00
Maarten Billemont
672b28a5b7 Restructure, split up mpw cli from mpw core. 2014-12-19 23:15:32 -05:00
Maarten Billemont
2dbada3c7c Update build script to install header files in a shared include path, akin to how ObjC does. 2014-12-19 09:00:38 -05:00
Maarten Billemont
3dbc105fbd Update C code for inclusion in ObjC and update scrypt to colin's latest code. 2014-12-19 00:03:54 -05:00
Maarten Billemont
43d55211b0 Revert "Revert accidentally committed changes."
This reverts commit a62ae8c757.
2014-12-18 17:47:38 -05:00
Maarten Billemont
f170e9df69 Update Java GUI. 2014-12-17 00:44:48 -05:00
Maarten Billemont
1fbb6b0754 Clear the password input field and pop a warning when entering bad master password. 2014-12-17 00:31:34 -05:00
Maarten Billemont
4c526d6f08 Fixed a bug causing exceptions loading maximum security passwords. 2014-12-17 00:18:02 -05:00
Maarten Billemont
a62ae8c757 Revert accidentally committed changes. 2014-12-16 23:07:31 -05:00
Maarten Billemont
f2eb53569b Use ~/.mpwrc since ~/.mpw is used by other tools.. 2014-12-16 22:14:52 -05:00
Maarten Billemont
c2a6a3d035 Full ability to load, add and autocomplete sites from history. 2014-12-16 22:13:11 -05:00
Maarten Billemont
97dcc65eac Update install scripts for new env var names. 2014-12-16 08:54:58 -05:00
Maarten Billemont
1bd76dbb61 Read avatar from user export and allow user to modify it by clicking the picture. 2014-12-12 17:25:32 -05:00
Maarten Billemont
0fdf894bf0 Fix issue with passing context in Java algorithm and test case. 2014-12-12 10:54:28 -05:00
Maarten Billemont
19202e07d4 Merge branch 'master' of github.com:Lyndir/MasterPassword 2014-12-12 10:14:09 -05:00
Maarten Billemont
84b624aea2 WIP - integrate user and site storage through export files into Java Swing GUI. 2014-12-11 20:35:19 -05:00
Maarten Billemont
c7ac5087b3 Fix test cases loginName, securityAnswer and securityAnswer_context 2014-12-10 10:34:55 -05:00
Maarten Billemont
4ff8cd6d90 A persistence model for the Java implementation and ability to parse in and write out export files. 2014-12-08 11:11:29 -05:00
Maarten Billemont
3f4558da2b Standardize on a naming scheme: cipher -> template, userName -> fullName, element -> site. 2014-12-05 17:59:10 -05:00
Maarten Billemont
b976e79b0f Fix some mpw.c link errors.
[FIXED]     Libraries should be specified after the objects that need them for some compilers apparently..
[FIXED]     bool type requires stdbool.h, disable more color code when COLOR not defined.
[UPDATED]   Disable mpw_color by default as it doesn't link cleanly on some Linux'es for now..
2014-12-05 13:51:02 -05:00
Maarten Billemont
3d064fa68d A full test script for various inputs and a Java TestNG implementation that tests it. 2014-12-05 02:17:28 -05:00
Maarten Billemont
1a1e024178 Fix Java unit test expected values from C implementation's output. 2014-12-04 00:35:21 -05:00
Maarten Billemont
4876d62b56 Copy the string into masterPassword because the line is getting free'd. 2014-12-04 00:28:38 -05:00
Maarten Billemont
8006b7096f Make Java and C debug output comparable. 2014-12-03 23:36:18 -05:00
Maarten Billemont
a82ce7310d Remove plist dependency, fix length bug, import ciphers.plist.
[REMOVED]   Java code no longer depends on ciphers.plist and net.sf.plist.
[ADDED]     Java code now explicitly defines the algorithm's templates.
[FIXED]     Java code now properly counts the site name and user name's byte length.
[FIXED]     Java code now explicitly uses 32-bit integers.
2014-12-03 00:46:00 -05:00
Maarten Billemont
ae08cb62c5 Add the env variables to the usage output. 2014-12-03 00:40:54 -05:00
Maarten Billemont
c48fba6c01 No color fixes & malloc bug in .mpw reading.
[FIXED]     We weren't properly excluding all dependencies on ncurses when colors are not enabled.
[FIXED]     There was a memory realloc bug when reading multiple lines from ~/.mpw.
2014-12-02 20:46:53 -05:00
Maarten Billemont
3db25e7e3b Some more attempts at being better at memory handling + remove useless and untested cygwin stuff. 2014-11-23 14:01:29 -05:00
Maarten Billemont
1f7a49378b Allocate enough space for the NUL c-string delimitor. 2014-11-23 13:44:37 -05:00
Maarten Billemont
37ec21f5be Disable colors when output is not a terminal. 2014-11-23 13:33:09 -05:00
Maarten Billemont
2b8498f569 Support for patching dependencies + ARM patch for bcrypt. 2014-11-21 09:39:30 -05:00
Maarten Billemont
5c4fc61a12 Bump bashlib. 2014-11-21 08:15:51 -05:00
Maarten Billemont
c0ec65bbae Make identicon color an optional feature and specify the dependency. 2014-11-21 08:06:29 -05:00
84 changed files with 3820 additions and 1604 deletions

1
.gitignore vendored
View File

@@ -37,5 +37,6 @@ MasterPassword/C/*.o
MasterPassword/C/mpw-*.tar.gz MasterPassword/C/mpw-*.tar.gz
MasterPassword/C/mpw MasterPassword/C/mpw
MasterPassword/C/mpw-bench MasterPassword/C/mpw-bench
MasterPassword/C/mpw-tests
MasterPassword/C/lib/*/* MasterPassword/C/lib/*/*
!MasterPassword/C/lib/*/.source !MasterPassword/C/lib/*/.source

View File

@@ -2,6 +2,7 @@
<profile version="1.0" is_locked="false"> <profile version="1.0" is_locked="false">
<option name="myName" value="Project Default" /> <option name="myName" value="Project Default" />
<option name="myLocal" value="false" /> <option name="myLocal" value="false" />
<inspection_tool class="Convert to string" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="FunctionImplicitDeclarationInspection" enabled="false" level="WARNING" enabled_by_default="false" /> <inspection_tool class="FunctionImplicitDeclarationInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="ImplicitIntegerAndEnumConversion" enabled="false" level="WARNING" enabled_by_default="false" /> <inspection_tool class="ImplicitIntegerAndEnumConversion" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="LossyEncoding" enabled="true" level="WARNING" enabled_by_default="true" /> <inspection_tool class="LossyEncoding" enabled="true" level="WARNING" enabled_by_default="true" />
@@ -9,6 +10,7 @@
<inspection_tool class="OCNotLocalizedStringInspection" enabled="false" level="WARNING" enabled_by_default="false" /> <inspection_tool class="OCNotLocalizedStringInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="OCUnusedMacroInspection" enabled="false" level="WARNING" enabled_by_default="false" /> <inspection_tool class="OCUnusedMacroInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="OCUnusedMethodInspection" enabled="false" level="WARNING" enabled_by_default="false" /> <inspection_tool class="OCUnusedMethodInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="Replace with subshell" enabled="true" level="INFO" enabled_by_default="true" />
<inspection_tool class="SignednessMismatch" enabled="false" level="WARNING" enabled_by_default="false" /> <inspection_tool class="SignednessMismatch" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UnavailableInDeploymentTarget" enabled="true" level="INFO" enabled_by_default="true" /> <inspection_tool class="UnavailableInDeploymentTarget" enabled="true" level="INFO" enabled_by_default="true" />
<inspection_tool class="UnusedLocalVariable" enabled="false" level="WARNING" enabled_by_default="false" /> <inspection_tool class="UnusedLocalVariable" enabled="false" level="WARNING" enabled_by_default="false" />

2
External/Pearl vendored

View File

@@ -27,7 +27,7 @@
# ______________________________________________________________________ # ______________________________________________________________________
# | | # | |
# | .:: TABLE OF CONTENTS ::. | # | .: TABLE OF CONTENTS :. |
# |______________________________________________________________________| # |______________________________________________________________________|
# #
# chr decimal # chr decimal
@@ -66,12 +66,6 @@
# readwhile command [args] # readwhile command [args]
# Outputs the characters typed by the user into the terminal's input buffer while running the given command. # Outputs the characters typed by the user into the terminal's input buffer while running the given command.
# #
# pushqueue element ...
# Pushes the given arguments as elements onto the queue.
#
# popqueue
# Pops one element off the queue.
#
# log [format] [arguments...] # log [format] [arguments...]
# Log an event at a certain importance level. # Log an event at a certain importance level.
# The event is expressed as a printf(1) format argument. # The event is expressed as a printf(1) format argument.
@@ -132,7 +126,7 @@ _tocHash=71e13f42e1ea82c1c7019b27a3bc71f3
# ______________________________________________________________________ # ______________________________________________________________________
# | | # | |
# | .:: GLOBAL CONFIGURATION ::. | # | .: GLOBAL CONFIGURATION :. |
# |______________________________________________________________________| # |______________________________________________________________________|
# Unset all exported functions. Exported functions are evil. # Unset all exported functions. Exported functions are evil.
@@ -177,7 +171,7 @@ genToc() {
# ______________________________________________________________________ # ______________________________________________________________________
# | | # | |
# | .:: GLOBAL DECLARATIONS ::. | # | .: GLOBAL DECLARATIONS :. |
# |______________________________________________________________________| # |______________________________________________________________________|
# Variables for convenience sequences. # Variables for convenience sequences.
@@ -190,8 +184,8 @@ runner=( '> >' \
# Variables for terminal requests. # Variables for terminal requests.
[[ -t 2 && $TERM != dumb ]] && { [[ -t 2 && $TERM != dumb ]] && {
COLUMNS=$( tput cols || tput co ) # Columns in a line COLUMNS=$({ tput cols || tput co;} 2>&3) # Columns in a line
LINES=$( tput lines || tput li ) # Lines on screen LINES=$({ tput lines || tput li;} 2>&3) # Lines on screen
alt=$( tput smcup || tput ti ) # Start alt display alt=$( tput smcup || tput ti ) # Start alt display
ealt=$( tput rmcup || tput te ) # End alt display ealt=$( tput rmcup || tput te ) # End alt display
hide=$( tput civis || tput vi ) # Hide cursor hide=$( tput civis || tput vi ) # Hide cursor
@@ -230,7 +224,7 @@ runner=( '> >' \
tput eA; tput as; tput eA; tput as;
tput ac; tput ae; } ) # Drawing characters tput ac; tput ae; } ) # Drawing characters
back=$'\b' back=$'\b'
} 2>/dev/null ||: } 3>&2 2>/dev/null ||:
@@ -238,7 +232,7 @@ runner=( '> >' \
# ______________________________________________________________________ # ______________________________________________________________________
# | | # | |
# | .:: FUNCTION DECLARATIONS ::. | # | .: FUNCTION DECLARATIONS :. |
# |______________________________________________________________________| # |______________________________________________________________________|
@@ -465,23 +459,6 @@ readwhile() {
# __________________________________________________________________________
# |__ popqueue ______________________________________________________________|
#
# popqueue
#
# Pops one element off the queue.
# If no elements are available on the queue, this command fails with exit code 1.
#
popqueue() {
local REPLY
[[ $_queue ]] && read -t0 <&"${_queue[0]}" || return
IFS= read -r -d '' <&"${_queue[0]}"
printf %s "$REPLY"
} # _____________________________________________________________________
# ______________________________________________________________________ # ______________________________________________________________________
# |__ Latest ____________________________________________________________| # |__ Latest ____________________________________________________________|
# #
@@ -1566,7 +1543,7 @@ stackTrace() {
# ______________________________________________________________________ # ______________________________________________________________________
# | | # | |
# | .:: ENTRY POINT ::. | # | .: ENTRY POINT :. |
# |______________________________________________________________________| # |______________________________________________________________________|
# Make sure this file is sourced and not executed. # Make sure this file is sourced and not executed.
@@ -1586,6 +1563,6 @@ stackTrace() {
} }
: :
: .:: END SOURCING ::. : .: END SOURCING :.
: ______________________________________________________________________ : ______________________________________________________________________
: :

View File

@@ -9,6 +9,8 @@
# try ./build -lrt instead. # try ./build -lrt instead.
# - If you see 'x86.S:202: Error: junk at end of line, first unrecognized character is `,'', # - If you see 'x86.S:202: Error: junk at end of line, first unrecognized character is `,'',
# try commenting the line in lib/bcrypt/x86.S. # try commenting the line in lib/bcrypt/x86.S.
# - Take a look at the "Optional features" section. Some features have dependencies,
# either make sure you have them or disable those features.
# #
# BUGS # BUGS
# masterpassword@lyndir.com # masterpassword@lyndir.com
@@ -31,10 +33,14 @@ else
# Modify here or override using targets='mpw mpw-bench' ./build # Modify here or override using targets='mpw mpw-bench' ./build
targets=( targets=(
mpw # C CLI version of Master Password. mpw # C CLI version of Master Password.
#mpw-bench # C CLI Master Password benchmark utility. mpw-bench # C CLI Master Password benchmark utility.
mpw-tests # C Master Password algorithm tester.
) )
fi fi
# Optional features.
mpw_color=0 # Colorized Identicon, requires libncurses-dev
### DEPENDENCIES ### DEPENDENCIES
@@ -128,12 +134,18 @@ fetchSource() (
echo >&2 "error: into: $PWD" echo >&2 "error: into: $PWD"
exit 1 exit 1
fi fi
for patch in "${patches[@]}"; do
echo
echo "Patching: ${PWD##*/}, for $patch..."
patch -p0 < ../"${PWD##*/}-$patch.patch"
done
) )
depend() { depend() {
echo echo
echo "Checking dependency: $1..." echo "Checking dependency: $1..."
[[ -e "lib/$1/.built" ]] && return [[ -e "lib/include/$1" ]] && return
pushd "lib/$1" pushd "lib/$1"
fetchSource fetchSource
@@ -169,7 +181,8 @@ depend() {
fi fi
make make
date > .built install -d "../include/$1/"
find . -name '*.h' -exec install -m 444 {} "../include/$1/" \;
else else
echo >&2 "error: Don't know how to build: $1" echo >&2 "error: Don't know how to build: $1"
exit 1 exit 1
@@ -186,13 +199,9 @@ mpw() {
echo "Building target: $target..." echo "Building target: $target..."
CFLAGS=( CFLAGS=(
# include paths # include paths
-I"lib/scrypt/lib" -I"lib/scrypt/libcperciva" -I"lib/include"
) )
LDFLAGS=( LDFLAGS=(
# library paths
-L"." -L"lib/scrypt"
# link libraries
-l"crypto" -l"curses"
# scrypt # scrypt
"lib/scrypt/scrypt-crypto_aesctr.o" "lib/scrypt/scrypt-crypto_aesctr.o"
"lib/scrypt/scrypt-sha256.o" "lib/scrypt/scrypt-sha256.o"
@@ -200,10 +209,19 @@ mpw() {
"lib/scrypt/scrypt-memlimit.o" "lib/scrypt/scrypt-memlimit.o"
"lib/scrypt/scrypt-scryptenc_cpuperf.o" "lib/scrypt/scrypt-scryptenc_cpuperf.o"
"lib/scrypt/scrypt-scryptenc.o" "lib/scrypt/scrypt-scryptenc.o"
# library paths
-L"." -L"lib/scrypt"
# link libraries
-l"crypto"
) )
# optional features
(( mpw_color )) && CFLAGS+=( -DCOLOR ) LDFLAGS+=( -l"curses" )
cc "${CFLAGS[@]}" -c types.c -o types.o "$@" cc "${CFLAGS[@]}" "$@" -c mpw-algorithm.c -o mpw-algorithm.o
cc "${CFLAGS[@]}" "${LDFLAGS[@]}" "types.o" mpw.c -o mpw "$@" cc "${CFLAGS[@]}" "$@" -c mpw-types.c -o mpw-types.o
cc "${CFLAGS[@]}" "$@" -c mpw-util.c -o mpw-util.o
cc "${CFLAGS[@]}" "${LDFLAGS[@]}" "$@" "mpw-algorithm.o" "mpw-types.o" "mpw-util.o" \
mpw-cli.c -o mpw
echo "done! Now run ./install or use ./mpw" echo "done! Now run ./install or use ./mpw"
} }
@@ -217,15 +235,9 @@ mpw-bench() {
echo "Building target: $target..." echo "Building target: $target..."
CFLAGS=( CFLAGS=(
# include paths # include paths
-I"lib/scrypt/lib" -I"lib/scrypt/libcperciva" -I"lib/include"
-I"lib/bcrypt"
) )
LDFLAGS=( LDFLAGS=(
# library paths
-L"." -L"lib/scrypt"
-L"lib/bcrypt"
# libraries
-l"crypto"
# scrypt # scrypt
"lib/scrypt/scrypt-crypto_aesctr.o" "lib/scrypt/scrypt-crypto_aesctr.o"
"lib/scrypt/scrypt-sha256.o" "lib/scrypt/scrypt-sha256.o"
@@ -238,14 +250,51 @@ mpw-bench() {
"lib/bcrypt/crypt_gensalt.o" "lib/bcrypt/crypt_gensalt.o"
"lib/bcrypt/wrapper.o" "lib/bcrypt/wrapper.o"
"lib/bcrypt/x86.o" "lib/bcrypt/x86.o"
# library paths
-L"." -L"lib/scrypt"
-L"lib/bcrypt"
# link libraries
-l"crypto"
) )
cc "${CFLAGS[@]}" -c types.c -o types.o "$@" cc "${CFLAGS[@]}" "${LDFLAGS[@]}" "$@" "mpw-algorithm.o" "mpw-types.o" "mpw-util.o" \
cc "${CFLAGS[@]}" "${LDFLAGS[@]}" "types.o" mpw-bench.c -o mpw-bench "$@" mpw-bench.c -o mpw-bench
echo "done! Now use ./mpw-bench" echo "done! Now use ./mpw-bench"
} }
### MPW-TESTS
mpw-tests() {
depend scrypt
echo
echo "Building target: $target..."
CFLAGS=(
# include paths
-I"lib/include"
-I"/usr/include/libxml2"
)
LDFLAGS=(
# scrypt
"lib/scrypt/scrypt-crypto_aesctr.o"
"lib/scrypt/scrypt-sha256.o"
"lib/scrypt/scrypt-crypto_scrypt-nosse.o"
"lib/scrypt/scrypt-memlimit.o"
"lib/scrypt/scrypt-scryptenc_cpuperf.o"
"lib/scrypt/scrypt-scryptenc.o"
# library paths
-L"." -L"lib/scrypt"
# link libraries
-l"crypto" -l"xml2"
)
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" \
mpw-tests.c -o mpw-tests
echo "done! Now use ./mpw-tests"
}
### TARGETS ### TARGETS
haslib() { haslib() {

View File

@@ -45,8 +45,8 @@ fi
echo echo
inf "You can also save your user name in ~/.bashrc. Leave blank to skip this step." inf "You can also save your user name in ~/.bashrc. Leave blank to skip this step."
if MP_USERNAME=$(ask "Your full name:") && [[ $MP_USERNAME ]] ; then if MP_FULLNAME=$(ask "Your full name:") && [[ $MP_FULLNAME ]] ; then
printf 'export MP_USERNAME=%q\n' "$MP_USERNAME" >> ~/.bashrc printf 'export MP_FULLNAME=%q\n' "$MP_FULLNAME" >> ~/.bashrc
fi fi
echo echo

View File

@@ -0,0 +1,12 @@
--- x86.S 2014-11-21 09:09:58.000000000 -0500
+++ x86.S 2014-11-21 09:11:01.000000000 -0500
@@ -199,5 +199,9 @@
#endif
#if defined(__ELF__) && defined(__linux__)
+#if defined(__arm__)
+.section .note.GNU-stack,"",%progbits
+#else
.section .note.GNU-stack,"",@progbits
#endif
+#endif

View File

@@ -1,3 +1,4 @@
home=http://www.openwall.com/crypt/ home=http://www.openwall.com/crypt/
pkg=http://www.openwall.com/crypt/crypt_blowfish-1.3.tar.gz pkg=http://www.openwall.com/crypt/crypt_blowfish-1.3.tar.gz
pkg_sha256=83fa01fca6996fe8d882b7f8e9ba0305a5664936100b01481ea3c6a8ce8d72fd pkg_sha256=83fa01fca6996fe8d882b7f8e9ba0305a5664936100b01481ea3c6a8ce8d72fd
patches=(arm)

View File

@@ -0,0 +1,111 @@
//
// mpw-algorithm.c
// MasterPassword
//
// Created by Maarten Billemont on 2014-12-20.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "mpw-types.h"
#include "mpw-util.h"
#include "mpw-algorithm.h"
#define MP_N 32768
#define MP_r 8
#define MP_p 2
#define MP_hash PearlHashSHA256
const uint8_t *mpw_masterKeyForUser(const char *fullName, const char *masterPassword) {
const char *mpKeyScope = mpw_scopeForVariant( MPSiteVariantPassword );
trc( "fullName: %s\n", fullName );
trc( "masterPassword: %s\n", masterPassword );
trc( "key scope: %s\n", mpKeyScope );
// Calculate the master key salt.
// masterKeySalt = mpKeyScope . #fullName . fullName
size_t masterKeySaltSize = 0;
uint8_t *masterKeySalt = NULL;
mpw_pushString( &masterKeySalt, &masterKeySaltSize, mpKeyScope );
mpw_pushInt( &masterKeySalt, &masterKeySaltSize, htonl( strlen( fullName ) ) );
mpw_pushString( &masterKeySalt, &masterKeySaltSize, fullName );
if (!masterKeySalt) {
ftl( "Could not allocate master key salt: %d\n", errno );
return NULL;
}
trc( "masterKeySalt ID: %s\n", mpw_idForBuf( masterKeySalt, masterKeySaltSize ) );
// Calculate the master key.
// masterKey = scrypt( masterPassword, masterKeySalt )
const uint8_t *masterKey = mpw_scrypt( MP_dkLen, masterPassword, masterKeySalt, masterKeySaltSize, MP_N, MP_r, MP_p );
mpw_free( masterKeySalt, masterKeySaltSize );
if (!masterKey) {
ftl( "Could not allocate master key: %d\n", errno );
return NULL;
}
trc( "masterKey ID: %s\n", mpw_idForBuf( masterKey, MP_dkLen ) );
return masterKey;
}
const char *mpw_passwordForSite(const uint8_t *masterKey, const char *siteName, const MPSiteType siteType, const uint32_t siteCounter,
const MPSiteVariant siteVariant, const char *siteContext) {
const char *siteScope = mpw_scopeForVariant( siteVariant );
trc( "siteName: %s\n", siteName );
trc( "siteCounter: %d\n", siteCounter );
trc( "siteVariant: %d\n", siteVariant );
trc( "siteType: %d\n", siteType );
trc( "site scope: %s, context: %s\n", siteScope, siteContext == NULL? "<empty>": siteContext );
// Calculate the site seed.
// sitePasswordSeed = hmac-sha256( masterKey, siteScope . #siteName . siteName . siteCounter . #siteContext . siteContext )
size_t sitePasswordInfoSize = 0;
uint8_t *sitePasswordInfo = NULL;
mpw_pushString( &sitePasswordInfo, &sitePasswordInfoSize, siteScope );
mpw_pushInt( &sitePasswordInfo, &sitePasswordInfoSize, htonl( strlen( siteName ) ) );
mpw_pushString( &sitePasswordInfo, &sitePasswordInfoSize, siteName );
mpw_pushInt( &sitePasswordInfo, &sitePasswordInfoSize, htonl( siteCounter ) );
if (siteContext) {
mpw_pushInt( &sitePasswordInfo, &sitePasswordInfoSize, htonl( strlen( siteContext ) ) );
mpw_pushString( &sitePasswordInfo, &sitePasswordInfoSize, siteContext );
}
if (!sitePasswordInfo) {
ftl( "Could not allocate site seed info: %d\n", errno );
return NULL;
}
trc( "sitePasswordInfo ID: %s\n", mpw_idForBuf( sitePasswordInfo, sitePasswordInfoSize ) );
const uint8_t *sitePasswordSeed = mpw_hmac_sha256( masterKey, MP_dkLen, sitePasswordInfo, sitePasswordInfoSize );
mpw_free( sitePasswordInfo, sitePasswordInfoSize );
if (!sitePasswordSeed) {
ftl( "Could not allocate site seed: %d\n", errno );
return NULL;
}
trc( "sitePasswordSeed ID: %s\n", mpw_idForBuf( sitePasswordSeed, 32 ) );
// Determine the template.
const char *template = mpw_templateForType( siteType, sitePasswordSeed[0] );
trc( "type %d, template: %s\n", siteType, template );
if (strlen( template ) > 32) {
ftl( "Template too long for password seed: %lu", strlen( template ) );
mpw_free( sitePasswordSeed, sizeof( sitePasswordSeed ) );
return NULL;
}
// Encode the password from the seed using the template.
char *const sitePassword = calloc( strlen( template ) + 1, sizeof( char ) );
for (size_t c = 0; c < strlen( template ); ++c) {
sitePassword[c] = mpw_characterFromClass( template[c], sitePasswordSeed[c + 1] );
trc( "class %c, index %u (0x%02X) -> character: %c\n", template[c], sitePasswordSeed[c + 1], sitePasswordSeed[c + 1],
sitePassword[c] );
}
mpw_free( sitePasswordSeed, sizeof( sitePasswordSeed ) );
return sitePassword;
}

View File

@@ -0,0 +1,20 @@
//
// mpw-algorithm.h
// MasterPassword
//
// Created by Maarten Billemont on 2014-12-20.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#define MP_dkLen 64
/** Derive the master key for a user based on their name and master password.
* @return A new MP_dkLen-byte allocated buffer or NULL if an allocation error occurred. */
const uint8_t *mpw_masterKeyForUser(
const char *fullName, const char *masterPassword);
/** Encode a password for the site from the given master key and site parameters.
* @return A newly allocated string or NULL if an allocation error occurred. */
const char *mpw_passwordForSite(
const uint8_t *masterKey, const char *siteName, const MPSiteType siteType, const uint32_t siteCounter,
const MPSiteVariant siteVariant, const char *siteContext);

View File

@@ -1,20 +1,23 @@
#include <sys/time.h> //
// mpw-bench.c
// MasterPassword
//
// Created by Maarten Billemont on 2014-12-20.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#include <stdio.h> #include <stdio.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <math.h>
#include <pwd.h>
#include <netinet/in.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <errno.h> #include <errno.h>
#include <sys/time.h>
#include <alg/sha256.h> #include <scrypt/sha256.h>
#include <crypto/crypto_scrypt.h> #include <bcrypt/ow-crypt.h>
#include <ow-crypt.h>
#include "types.h" #include "mpw-types.h"
#include "mpw-algorithm.h"
#include "mpw-util.h"
#define MP_N 32768 #define MP_N 32768
#define MP_r 8 #define MP_r 8
@@ -22,166 +25,82 @@
#define MP_dkLen 64 #define MP_dkLen 64
#define MP_hash PearlHashSHA256 #define MP_hash PearlHashSHA256
static void mpw_getTime(struct timeval *time) {
if (gettimeofday( time, NULL ) != 0)
ftl( "Could not get time: %d\n", errno );
}
static const double mpw_showSpeed(struct timeval startTime, const unsigned int iterations, const char *operation) {
struct timeval endTime;
mpw_getTime( &endTime );
const time_t dsec = (endTime.tv_sec - startTime.tv_sec);
const suseconds_t dusec = (endTime.tv_usec - startTime.tv_usec);
const double elapsed = dsec + dusec / 1000000.;
const double speed = iterations / elapsed;
fprintf( stderr, " done. " );
fprintf( stdout, "%d %s iterations in %llds %lldµs -> %.2f/s\n", iterations, operation, (long long)dsec, (long long)dusec, speed );
return speed;
}
int main(int argc, char *const argv[]) { int main(int argc, char *const argv[]) {
char *userName = "Robert Lee Mitchel"; const char *fullName = "Robert Lee Mitchel";
char *masterPassword = "banana colored duckling"; const char *masterPassword = "banana colored duckling";
char *siteName = "masterpasswordapp.com"; const char *siteName = "masterpasswordapp.com";
uint32_t siteCounter = 1; const uint32_t siteCounter = 1;
MPElementType siteType = MPElementTypeGeneratedLong; const MPSiteType siteType = MPSiteTypeGeneratedLong;
const MPSiteVariant siteVariant = MPSiteVariantPassword;
// Start MP const char *siteContext = NULL;
struct timeval startTime; struct timeval startTime;
if (gettimeofday(&startTime, NULL) != 0) {
fprintf(stderr, "Could not get time: %d\n", errno);
return 1;
}
int iterations = 100; // Start MPW
unsigned int iterations = 100;
mpw_getTime( &startTime );
for (int i = 0; i < iterations; ++i) { for (int i = 0; i < iterations; ++i) {
// Calculate the master key salt. const uint8_t *masterKey = mpw_masterKeyForUser( fullName, masterPassword );
char *mpNameSpace = "com.lyndir.masterpassword"; if (!masterKey)
const uint32_t n_userNameLength = htonl(strlen(userName)); ftl( "Could not allocate master key: %d\n", errno );
const size_t masterKeySaltLength = strlen(mpNameSpace) + sizeof(n_userNameLength) + strlen(userName); free( (void *)mpw_passwordForSite( masterKey, siteName, siteType, siteCounter, siteVariant, siteContext ) );
char *masterKeySalt = malloc( masterKeySaltLength ); free( (void *)masterKey );
if (!masterKeySalt) {
fprintf(stderr, "Could not allocate master key salt: %d\n", errno);
return 1;
}
char *mKS = masterKeySalt;
memcpy(mKS, mpNameSpace, strlen(mpNameSpace)); mKS += strlen(mpNameSpace);
memcpy(mKS, &n_userNameLength, sizeof(n_userNameLength)); mKS += sizeof(n_userNameLength);
memcpy(mKS, userName, strlen(userName)); mKS += strlen(userName);
if (mKS - masterKeySalt != masterKeySaltLength)
abort();
trc("masterKeySalt ID: %s\n", IDForBuf(masterKeySalt, masterKeySaltLength));
// Calculate the master key.
uint8_t *masterKey = malloc( MP_dkLen );
if (!masterKey) {
fprintf(stderr, "Could not allocate master key: %d\n", errno);
return 1;
}
if (crypto_scrypt( (const uint8_t *)masterPassword, strlen(masterPassword), (const uint8_t *)masterKeySalt, masterKeySaltLength, MP_N, MP_r, MP_p, masterKey, MP_dkLen ) < 0) {
fprintf(stderr, "Could not generate master key: %d\n", errno);
return 1;
}
memset(masterKeySalt, 0, masterKeySaltLength);
free(masterKeySalt);
// Calculate the site seed.
const uint32_t n_siteNameLength = htonl(strlen(siteName));
const uint32_t n_siteCounter = htonl(siteCounter);
const size_t sitePasswordInfoLength = strlen(mpNameSpace) + sizeof(n_siteNameLength) + strlen(siteName) + sizeof(n_siteCounter);
char *sitePasswordInfo = malloc( sitePasswordInfoLength );
if (!sitePasswordInfo) {
fprintf(stderr, "Could not allocate site seed: %d\n", errno);
return 1;
}
char *sPI = sitePasswordInfo;
memcpy(sPI, mpNameSpace, strlen(mpNameSpace)); sPI += strlen(mpNameSpace);
memcpy(sPI, &n_siteNameLength, sizeof(n_siteNameLength)); sPI += sizeof(n_siteNameLength);
memcpy(sPI, siteName, strlen(siteName)); sPI += strlen(siteName);
memcpy(sPI, &n_siteCounter, sizeof(n_siteCounter)); sPI += sizeof(n_siteCounter);
if (sPI - sitePasswordInfo != sitePasswordInfoLength)
abort();
uint8_t sitePasswordSeed[32];
HMAC_SHA256_Buf(masterKey, MP_dkLen, sitePasswordInfo, sitePasswordInfoLength, sitePasswordSeed);
memset(masterKey, 0, MP_dkLen);
memset(sitePasswordInfo, 0, sitePasswordInfoLength);
free(masterKey);
free(sitePasswordInfo);
// Determine the cipher.
const char *cipher = CipherForType(siteType, sitePasswordSeed[0]);
trc("type %d, cipher: %s\n", siteType, cipher);
if (strlen(cipher) > 32)
abort();
// Encode the password from the seed using the cipher.
char *sitePassword = calloc(strlen(cipher) + 1, sizeof(char));
for (int c = 0; c < strlen(cipher); ++c) {
sitePassword[c] = CharacterFromClass(cipher[c], sitePasswordSeed[c + 1]);
trc("class %c, character: %c\n", cipher[c], sitePassword[c]);
}
memset(sitePasswordSeed, 0, sizeof(sitePasswordSeed));
if (i % 1 == 0) if (i % 1 == 0)
fprintf( stderr, "\rmpw: iteration %d / %d..", i, iterations ); fprintf( stderr, "\rmpw: iteration %d / %d..", i, iterations );
} }
const double mpwSpeed = mpw_showSpeed( startTime, iterations, "mpw" );
// Output timing results.
struct timeval endTime;
if (gettimeofday(&endTime, NULL) != 0) {
fprintf(stderr, "Could not get time: %d\n", errno);
return 1;
}
long long secs = (endTime.tv_sec - startTime.tv_sec);
long long usecs = (endTime.tv_usec - startTime.tv_usec);
double elapsed = secs + usecs / 1000000.0;
double mpwSpeed = iterations / elapsed;
fprintf( stdout, " done. %d iterations in %llds %lldµs -> %.2f/s\n", iterations, secs, usecs, mpwSpeed );
// Start SHA-256 // Start SHA-256
if (gettimeofday(&startTime, NULL) != 0) {
fprintf(stderr, "Could not get time: %d\n", errno);
return 1;
}
iterations = 50000000; iterations = 50000000;
uint8_t hash[32]; uint8_t hash[32];
mpw_getTime( &startTime );
for (int i = 0; i < iterations; ++i) { for (int i = 0; i < iterations; ++i) {
SHA256_Buf(masterPassword, strlen(masterPassword), hash); SHA256_Buf( masterPassword, strlen( masterPassword ), hash );
if (i % 1000 == 0) if (i % 1000 == 0)
fprintf( stderr, "\rsha256: iteration %d / %d..", i, iterations ); fprintf( stderr, "\rsha256: iteration %d / %d..", i, iterations );
} }
const double sha256Speed = mpw_showSpeed( startTime, iterations, "sha256" );
// Output timing results.
if (gettimeofday(&endTime, NULL) != 0) {
fprintf(stderr, "Could not get time: %d\n", errno);
return 1;
}
secs = (endTime.tv_sec - startTime.tv_sec);
usecs = (endTime.tv_usec - startTime.tv_usec);
elapsed = secs + usecs / 1000000.0;
double sha256Speed = iterations / elapsed;
fprintf( stdout, " done. %d iterations in %llds %lldµs -> %.2f/s\n", iterations, secs, usecs, sha256Speed );
// Start BCrypt // Start BCrypt
if (gettimeofday(&startTime, NULL) != 0) {
fprintf(stderr, "Could not get time: %d\n", errno);
return 1;
}
int bcrypt_cost = 9; int bcrypt_cost = 9;
iterations = 600; iterations = 600;
mpw_getTime( &startTime );
for (int i = 0; i < iterations; ++i) { for (int i = 0; i < iterations; ++i) {
crypt(masterPassword, crypt_gensalt("$2b$", bcrypt_cost, userName, strlen(userName))); crypt( masterPassword, crypt_gensalt( "$2b$", bcrypt_cost, fullName, strlen( fullName ) ) );
if (i % 10 == 0) if (i % 10 == 0)
fprintf( stderr, "\rbcrypt (cost %d): iteration %d / %d..", bcrypt_cost, i, iterations ); fprintf( stderr, "\rbcrypt (cost %d): iteration %d / %d..", bcrypt_cost, i, iterations );
} }
const double bcrypt9Speed = mpw_showSpeed( startTime, iterations, "bcrypt9" );
// Output timing results.
if (gettimeofday(&endTime, NULL) != 0) {
fprintf(stderr, "Could not get time: %d\n", errno);
return 1;
}
secs = (endTime.tv_sec - startTime.tv_sec);
usecs = (endTime.tv_usec - startTime.tv_usec);
elapsed = secs + usecs / 1000000.0;
double bcrypt9Speed = iterations / elapsed;
fprintf( stdout, " done. %d iterations in %llds %lldµs -> %.2f/s\n", iterations, secs, usecs, bcrypt9Speed );
// Summarize. // Summarize.
fprintf( stdout, "\n== SUMMARY ==\nOn this machine,\n" ); fprintf( stdout, "\n== SUMMARY ==\nOn this machine,\n" );
fprintf( stdout, "mpw is %f times slower than sha256\n", sha256Speed / mpwSpeed ); fprintf( stdout, " - mpw is %f times slower than sha256.\n", sha256Speed / mpwSpeed );
fprintf( stdout, "mpw is %f times slower than bcrypt (cost 9)\n", bcrypt9Speed / mpwSpeed ); fprintf( stdout, " - mpw is %f times slower than bcrypt (cost 9).\n", bcrypt9Speed / mpwSpeed );
return 0; return 0;
} }

203
MasterPassword/C/mpw-cli.c Normal file
View File

@@ -0,0 +1,203 @@
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <pwd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#if defined(READLINE)
#include <readline/readline.h>
#elif defined(EDITLINE)
#include <histedit.h>
#endif
#define ftl(...) do { fprintf( stderr, __VA_ARGS__ ); exit(2); } while (0)
#include "mpw-types.h"
#include "mpw-algorithm.h"
#include "mpw-util.h"
#define MP_env_fullname "MP_FULLNAME"
#define MP_env_sitetype "MP_SITETYPE"
#define MP_env_sitecounter "MP_SITECOUNTER"
static void usage() {
fprintf( stderr, "Usage: mpw [-u name] [-t type] [-c counter] site\n\n" );
fprintf( stderr, " -u name Specify the full name of the user.\n"
" Defaults to %s in env.\n\n", MP_env_fullname );
fprintf( stderr, " -t type Specify the password's template.\n"
" Defaults to %s in env or 'long' for password, 'name' for login.\n"
" x, max, maximum | 20 characters, contains symbols.\n"
" l, long | Copy-friendly, 14 characters, contains symbols.\n"
" m, med, medium | Copy-friendly, 8 characters, contains symbols.\n"
" b, basic | 8 characters, no symbols.\n"
" s, short | Copy-friendly, 4 characters, no symbols.\n"
" i, pin | 4 numbers.\n"
" n, name | 9 letter name.\n"
" p, phrase | 20 character sentence.\n\n", MP_env_sitetype );
fprintf( stderr, " -c counter The value of the counter.\n"
" Defaults to %s in env or '1'.\n\n", MP_env_sitecounter );
fprintf( stderr, " -v variant The kind of content to generate.\n"
" Defaults to 'password'.\n"
" p, password | The password to log in with.\n"
" l, login | The username to log in as.\n"
" a, answer | The answer to a security question.\n\n" );
fprintf( stderr, " -C context A variant-specific context.\n"
" Defaults to empty.\n"
" -v p, password | Doesn't currently use a context.\n"
" -v l, login | Doesn't currently use a context.\n"
" -v a, answer | Empty for a universal site answer or\n"
" | the most significant word(s) of the question.\n\n" );
fprintf( stderr, " ENVIRONMENT\n\n"
" MP_FULLNAME | The full name of the user.\n"
" MP_SITETYPE | The default password template.\n"
" MP_SITECOUNTER | The default counter value.\n\n" );
exit( 0 );
}
static char *homedir(const char *filename) {
char *homedir = NULL;
struct passwd *passwd = getpwuid( getuid() );
if (passwd)
homedir = passwd->pw_dir;
if (!homedir)
homedir = getenv( "HOME" );
if (!homedir)
homedir = getcwd( NULL, 0 );
char *homefile = NULL;
asprintf( &homefile, "%s/%s", homedir, filename );
return homefile;
}
static char *getlinep(const char *prompt) {
char *buf = NULL;
size_t bufSize = 0;
ssize_t lineSize;
fprintf( stderr, "%s", prompt );
fprintf( stderr, " " );
if ((lineSize = getline( &buf, &bufSize, stdin )) < 0) {
free( buf );
return NULL;
}
buf[lineSize - 1] = 0;
return buf;
}
int main(int argc, char *const argv[]) {
// Read the environment.
char *fullName = getenv( MP_env_fullname );
const char *masterPassword = NULL;
const char *siteName = NULL;
MPSiteType siteType = MPSiteTypeGeneratedLong;
const char *siteTypeString = getenv( MP_env_sitetype );
MPSiteVariant siteVariant = MPSiteVariantPassword;
const char *siteVariantString = NULL;
const char *siteContextString = NULL;
uint32_t siteCounter = 1;
const char *siteCounterString = getenv( MP_env_sitecounter );
// Read the options.
for (int opt; (opt = getopt( argc, argv, "u:t:c:v:C:h" )) != -1;)
switch (opt) {
case 'u':
fullName = optarg;
break;
case 't':
siteTypeString = optarg;
break;
case 'c':
siteCounterString = optarg;
break;
case 'v':
siteVariantString = optarg;
break;
case 'C':
siteContextString = optarg;
break;
case 'h':
usage();
break;
case '?':
switch (optopt) {
case 'u':
ftl( "Missing full name to option: -%c\n", optopt );
break;
case 't':
ftl( "Missing type name to option: -%c\n", optopt );
break;
case 'c':
ftl( "Missing counter value to option: -%c\n", optopt );
break;
default:
ftl( "Unknown option: -%c\n", optopt );
}
default:
ftl("Unexpected option: %c", opt);
}
if (optind < argc)
siteName = argv[optind];
// Convert and validate input.
if (!fullName && !(fullName = getlinep( "Your full name:" )))
ftl( "Missing full name.\n" );
if (!siteName && !(siteName = getlinep( "Site name:" )))
ftl( "Missing site name.\n" );
if (siteCounterString)
siteCounter = (uint32_t)atol( siteCounterString );
if (siteCounter < 1)
ftl( "Invalid site counter: %d\n", siteCounter );
if (siteVariantString)
siteVariant = mpw_variantWithName( siteVariantString );
if (siteVariant == MPSiteVariantLogin)
siteType = MPSiteTypeGeneratedName;
if (siteVariant == MPSiteVariantAnswer)
siteType = MPSiteTypeGeneratedPhrase;
if (siteTypeString)
siteType = mpw_typeWithName( siteTypeString );
// Read the master password.
char *mpwConfigPath = homedir( ".mpw" );
if (!mpwConfigPath)
ftl( "Couldn't resolve path for configuration file: %d\n", errno );
trc( "mpwConfigPath: %s\n", mpwConfigPath );
FILE *mpwConfig = fopen( mpwConfigPath, "r" );
free( mpwConfigPath );
if (mpwConfig) {
char *line = NULL;
size_t linecap = 0;
while (getline( &line, &linecap, mpwConfig ) > 0) {
char *lineData = line;
if (strcmp( strsep( &lineData, ":" ), fullName ) == 0) {
masterPassword = strcpy( malloc( strlen( lineData ) ), strsep( &lineData, "\n" ) );
break;
}
}
mpw_free( line, linecap );
}
while (!masterPassword || !strlen(masterPassword))
masterPassword = getpass( "Your master password: " );
// Summarize operation.
fprintf( stderr, "%s's password for %s:\n[ %s ]: ", fullName, siteName, mpw_identicon( fullName, masterPassword ) );
// Output the password.
const uint8_t *masterKey = mpw_masterKeyForUser( fullName, masterPassword );
mpw_freeString( masterPassword );
if (!masterKey)
ftl( "Couldn't derive master key." );
const char *sitePassword = mpw_passwordForSite( masterKey, siteName, siteType, siteCounter, siteVariant, siteContextString );
mpw_free( masterKey, MP_dkLen );
if (!sitePassword)
ftl( "Couldn't derive site password." );
fprintf( stdout, "%s\n", sitePassword );
return 0;
}

View File

@@ -0,0 +1,76 @@
//
// mpw-tests-util.c
// MasterPassword
//
// Created by Maarten Billemont on 2014-12-21.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mpw-util.h"
#include "mpw-tests-util.h"
static xmlChar const *mpw_xmlPath(xmlNodePtr context) {
if (context->parent) {
char *string = calloc( 256, 1 );
snprintf( string, 256, "%s/%s", mpw_xmlPath( context->parent ), context->name );
return BAD_CAST string;
}
return context->name? context->name: (xmlChar const *)"";
}
xmlNodePtr mpw_xmlTestCaseNode(xmlNodePtr testCaseNode, const char *nodeName) {
// Try to find an attribute node.
for (xmlAttrPtr child = testCaseNode->properties; child; child = child->next)
if (xmlStrcmp( child->name, BAD_CAST nodeName ) == 0)
return (xmlNodePtr)child;
// Try to find an element node.
for (xmlNodePtr child = testCaseNode->children; child; child = child->next)
if (xmlStrcmp( child->name, BAD_CAST nodeName ) == 0)
return child;
// Missing content, try to find parent case.
if (strcmp(nodeName, "parent") == 0)
// Was just searching for testCaseNode's parent, none found.
return NULL;
xmlChar *parentId = mpw_xmlTestCaseString( testCaseNode, "parent" );
if (!parentId)
// testCaseNode has no parent, give up.
return NULL;
for (xmlNodePtr otherTestCaseNode = testCaseNode->parent->children; otherTestCaseNode; otherTestCaseNode = otherTestCaseNode->next) {
xmlChar *id = mpw_xmlTestCaseString( otherTestCaseNode, "id" );
int foundParent = xmlStrcmp( id, parentId ) == 0;
xmlFree( id );
if (foundParent) {
xmlFree( parentId );
return mpw_xmlTestCaseNode( otherTestCaseNode, nodeName );
}
}
ftl( "Missing parent: %s, for case: %s\n", parentId, mpw_xmlTestCaseString( testCaseNode, "id" ) );
}
xmlChar *mpw_xmlTestCaseString(xmlNodePtr context, const char *nodeName) {
xmlNodePtr child = mpw_xmlTestCaseNode( context, nodeName );
return xmlNodeGetContent( child );
}
uint32_t mpw_xmlTestCaseInteger(xmlNodePtr context, const char *nodeName) {
xmlChar *string = mpw_xmlTestCaseString( context, nodeName );
uint32_t integer = atol( (char *)string );
xmlFree( string );
return integer;
}

View File

@@ -0,0 +1,16 @@
//
// mpw-tests-util.h
// MasterPassword
//
// Created by Maarten Billemont on 2014-12-21.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#include <libxml/parser.h>
xmlNodePtr mpw_xmlTestCaseNode(
xmlNodePtr testCaseNode, const char *nodeName);
xmlChar *mpw_xmlTestCaseString(
xmlNodePtr context, const char *nodeName);
uint32_t mpw_xmlTestCaseInteger(
xmlNodePtr context, const char *nodeName);

View File

@@ -0,0 +1,77 @@
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#define ftl(...) do { fprintf( stderr, __VA_ARGS__ ); exit(2); } while (0)
#include "mpw-types.h"
#include "mpw-algorithm.h"
#include "mpw-util.h"
#include "mpw-tests-util.h"
int main(int argc, char *const argv[]) {
int failedTests = 0;
xmlNodePtr tests = xmlDocGetRootElement( xmlParseFile( "mpw_tests.xml" ) );
for (xmlNodePtr testCase = tests->children; testCase; testCase = testCase->next) {
if (testCase->type != XML_ELEMENT_NODE || xmlStrcmp( testCase->name, BAD_CAST "case" ) != 0)
continue;
// Read in the test case.
xmlChar *id = mpw_xmlTestCaseString( testCase, "id" );
xmlChar *fullName = mpw_xmlTestCaseString( testCase, "fullName" );
xmlChar *masterPassword = mpw_xmlTestCaseString( testCase, "masterPassword" );
xmlChar *keyID = mpw_xmlTestCaseString( testCase, "keyID" );
xmlChar *siteName = mpw_xmlTestCaseString( testCase, "siteName" );
uint32_t siteCounter = mpw_xmlTestCaseInteger( testCase, "siteCounter" );
xmlChar *siteTypeString = mpw_xmlTestCaseString( testCase, "siteType" );
xmlChar *siteVariantString = mpw_xmlTestCaseString( testCase, "siteVariant" );
xmlChar *siteContext = mpw_xmlTestCaseString( testCase, "siteContext" );
xmlChar *result = mpw_xmlTestCaseString( testCase, "result" );
MPSiteType siteType = mpw_typeWithName( (char *)siteTypeString );
MPSiteVariant siteVariant = mpw_variantWithName( (char *)siteVariantString );
// Run the test case.
fprintf( stdout, "test case %s... ", id );
// 1. calculate the master key.
const uint8_t *masterKey = mpw_masterKeyForUser(
(char *)fullName, (char *)masterPassword );
if (!masterKey)
ftl( "Couldn't derive master key." );
// 2. calculate the site password.
const char *sitePassword = mpw_passwordForSite(
masterKey, (char *)siteName, siteType, siteCounter, siteVariant, (char *)siteContext );
mpw_free( masterKey, MP_dkLen );
if (!sitePassword)
ftl( "Couldn't derive site password." );
// Check the result.
if (xmlStrcmp( result, BAD_CAST sitePassword ) == 0)
fprintf( stdout, "pass.\n" );
else {
++failedTests;
fprintf( stdout, "FAILED! (result %s != expected %s)\n", result, sitePassword );
}
// Free test case.
mpw_freeString( sitePassword );
xmlFree( id );
xmlFree( fullName );
xmlFree( masterPassword );
xmlFree( keyID );
xmlFree( siteName );
xmlFree( siteTypeString );
xmlFree( siteVariantString );
xmlFree( siteContext );
xmlFree( result );
}
return failedTests;
}

View File

@@ -0,0 +1,192 @@
//
// mpw-types.c
// MasterPassword
//
// Created by Maarten Billemont on 2012-02-01.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#ifdef COLOR
#include <curses.h>
#include <term.h>
#endif
#include "mpw-types.h"
#include "mpw-util.h"
const MPSiteType mpw_typeWithName(const char *typeName) {
size_t stdTypeNameSize = strlen( typeName );
char stdTypeName[strlen( typeName )];
if (stdTypeNameSize > strlen( "generated" ))
strcpy( stdTypeName, typeName + strlen( "generated" ) );
else
strcpy( stdTypeName, typeName );
for (char *tN = stdTypeName; *tN; ++tN)
*tN = (char)tolower( *tN );
if (0 == strcmp( stdTypeName, "x" ) || 0 == strcmp( stdTypeName, "max" ) || 0 == strcmp( stdTypeName, "maximum" ))
return MPSiteTypeGeneratedMaximum;
if (0 == strcmp( stdTypeName, "l" ) || 0 == strcmp( stdTypeName, "long" ))
return MPSiteTypeGeneratedLong;
if (0 == strcmp( stdTypeName, "m" ) || 0 == strcmp( stdTypeName, "med" ) || 0 == strcmp( stdTypeName, "medium" ))
return MPSiteTypeGeneratedMedium;
if (0 == strcmp( stdTypeName, "b" ) || 0 == strcmp( stdTypeName, "basic" ))
return MPSiteTypeGeneratedBasic;
if (0 == strcmp( stdTypeName, "s" ) || 0 == strcmp( stdTypeName, "short" ))
return MPSiteTypeGeneratedShort;
if (0 == strcmp( stdTypeName, "i" ) || 0 == strcmp( stdTypeName, "pin" ))
return MPSiteTypeGeneratedPIN;
if (0 == strcmp( stdTypeName, "n" ) || 0 == strcmp( stdTypeName, "name" ))
return MPSiteTypeGeneratedName;
if (0 == strcmp( stdTypeName, "p" ) || 0 == strcmp( stdTypeName, "phrase" ))
return MPSiteTypeGeneratedPhrase;
fprintf( stderr, "Not a generated type name: %s", stdTypeName );
abort();
}
const char *mpw_templateForType(MPSiteType type, uint8_t seedByte) {
if (!(type & MPSiteTypeClassGenerated)) {
fprintf( stderr, "Not a generated type: %d", type );
abort();
}
switch (type) {
case MPSiteTypeGeneratedMaximum: {
const char *templates[] = { "anoxxxxxxxxxxxxxxxxx", "axxxxxxxxxxxxxxxxxno" };
return templates[seedByte % 2];
}
case MPSiteTypeGeneratedLong: {
const char *templates[] = { "CvcvnoCvcvCvcv", "CvcvCvcvnoCvcv", "CvcvCvcvCvcvno",
"CvccnoCvcvCvcv", "CvccCvcvnoCvcv", "CvccCvcvCvcvno",
"CvcvnoCvccCvcv", "CvcvCvccnoCvcv", "CvcvCvccCvcvno",
"CvcvnoCvcvCvcc", "CvcvCvcvnoCvcc", "CvcvCvcvCvccno",
"CvccnoCvccCvcv", "CvccCvccnoCvcv", "CvccCvccCvcvno",
"CvcvnoCvccCvcc", "CvcvCvccnoCvcc", "CvcvCvccCvccno",
"CvccnoCvcvCvcc", "CvccCvcvnoCvcc", "CvccCvcvCvccno" };
return templates[seedByte % 21];
}
case MPSiteTypeGeneratedMedium: {
const char *templates[] = { "CvcnoCvc", "CvcCvcno" };
return templates[seedByte % 2];
}
case MPSiteTypeGeneratedBasic: {
const char *templates[] = { "aaanaaan", "aannaaan", "aaannaaa" };
return templates[seedByte % 3];
}
case MPSiteTypeGeneratedShort: {
return "Cvcn";
}
case MPSiteTypeGeneratedPIN: {
return "nnnn";
}
case MPSiteTypeGeneratedName: {
return "cvccvcvcv";
}
case MPSiteTypeGeneratedPhrase: {
const char *templates[] = { "cvcc cvc cvccvcv cvc", "cvc cvccvcvcv cvcv", "cv cvccv cvc cvcvccv" };
return templates[seedByte % 3];
}
default: {
fprintf( stderr, "Unknown generated type: %d", type );
abort();
}
}
}
const MPSiteVariant mpw_variantWithName(const char *variantName) {
char stdVariantName[strlen( variantName )];
strcpy( stdVariantName, variantName );
for (char *vN = stdVariantName; *vN; ++vN)
*vN = (char)tolower( *vN );
if (0 == strcmp( stdVariantName, "p" ) || 0 == strcmp( stdVariantName, "password" ))
return MPSiteVariantPassword;
if (0 == strcmp( stdVariantName, "l" ) || 0 == strcmp( stdVariantName, "login" ))
return MPSiteVariantLogin;
if (0 == strcmp( stdVariantName, "a" ) || 0 == strcmp( stdVariantName, "answer" ))
return MPSiteVariantAnswer;
fprintf( stderr, "Not a variant name: %s", stdVariantName );
abort();
}
const char *mpw_scopeForVariant(MPSiteVariant variant) {
switch (variant) {
case MPSiteVariantPassword: {
return "com.lyndir.masterpassword";
}
case MPSiteVariantLogin: {
return "com.lyndir.masterpassword.login";
}
case MPSiteVariantAnswer: {
return "com.lyndir.masterpassword.answer";
}
default: {
fprintf( stderr, "Unknown variant: %d", variant );
abort();
}
}
}
const char mpw_characterFromClass(char characterClass, uint8_t seedByte) {
const char *classCharacters;
switch (characterClass) {
case 'V': {
classCharacters = "AEIOU";
break;
}
case 'C': {
classCharacters = "BCDFGHJKLMNPQRSTVWXYZ";
break;
}
case 'v': {
classCharacters = "aeiou";
break;
}
case 'c': {
classCharacters = "bcdfghjklmnpqrstvwxyz";
break;
}
case 'A': {
classCharacters = "AEIOUBCDFGHJKLMNPQRSTVWXYZ";
break;
}
case 'a': {
classCharacters = "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz";
break;
}
case 'n': {
classCharacters = "0123456789";
break;
}
case 'o': {
classCharacters = "@&%?,=[]_:-+*$#!'^~;()/.";
break;
}
case 'x': {
classCharacters = "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz0123456789!@#$%^&*()";
break;
}
case ' ': {
classCharacters = " ";
break;
}
default: {
fprintf( stderr, "Unknown character class: %c", characterClass );
abort();
}
}
return classCharacters[seedByte % strlen( classCharacters )];
}

View File

@@ -0,0 +1,55 @@
//
// mpw-types.h
// MasterPassword
//
// Created by Maarten Billemont on 2012-02-01.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
//// Types.
typedef enum {
/** Generate the password to log in with. */
MPSiteVariantPassword,
/** Generate the login name to log in as. */
MPSiteVariantLogin,
/** Generate the answer to a security question. */
MPSiteVariantAnswer,
} MPSiteVariant;
typedef enum {
/** Generate the password. */
MPSiteTypeClassGenerated = 1 << 4,
/** Store the password. */
MPSiteTypeClassStored = 1 << 5,
} MPSiteTypeClass;
typedef enum {
/** Export the key-protected content data. */
MPSiteFeatureExportContent = 1 << 10,
/** Never export content. */
MPSiteFeatureDevicePrivate = 1 << 11,
} MPSiteFeature;
typedef enum {
MPSiteTypeGeneratedMaximum = 0x0 | MPSiteTypeClassGenerated | 0x0,
MPSiteTypeGeneratedLong = 0x1 | MPSiteTypeClassGenerated | 0x0,
MPSiteTypeGeneratedMedium = 0x2 | MPSiteTypeClassGenerated | 0x0,
MPSiteTypeGeneratedBasic = 0x4 | MPSiteTypeClassGenerated | 0x0,
MPSiteTypeGeneratedShort = 0x3 | MPSiteTypeClassGenerated | 0x0,
MPSiteTypeGeneratedPIN = 0x5 | MPSiteTypeClassGenerated | 0x0,
MPSiteTypeGeneratedName = 0xE | MPSiteTypeClassGenerated | 0x0,
MPSiteTypeGeneratedPhrase = 0xF | MPSiteTypeClassGenerated | 0x0,
MPSiteTypeStoredPersonal = 0x0 | MPSiteTypeClassStored | MPSiteFeatureExportContent,
MPSiteTypeStoredDevicePrivate = 0x1 | MPSiteTypeClassStored | MPSiteFeatureDevicePrivate,
} MPSiteType;
//// Type utilities.
const MPSiteVariant mpw_variantWithName(const char *variantName);
const char *mpw_scopeForVariant(MPSiteVariant variant);
const MPSiteType mpw_typeWithName(const char *typeName);
const char *mpw_templateForType(MPSiteType type, uint8_t seedByte);
const char mpw_characterFromClass(char characterClass, uint8_t seedByte);

165
MasterPassword/C/mpw-util.c Normal file
View File

@@ -0,0 +1,165 @@
//
// mpw-util.c
// MasterPassword
//
// Created by Maarten Billemont on 2014-12-20.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <scrypt/sha256.h>
#include <scrypt/crypto_scrypt.h>
#include "mpw-util.h"
void mpw_pushBuf(uint8_t **const buffer, size_t *const bufferSize, const void *pushBuffer, const size_t pushSize) {
if (*bufferSize == (size_t)-1)
// The buffer was marked as broken, it is missing a previous push. Abort to avoid corrupt content.
return;
*bufferSize += pushSize;
uint8_t *resizedBuffer = realloc( *buffer, *bufferSize );
if (!resizedBuffer) {
// realloc failed, we can't push. Mark the buffer as broken.
mpw_free( *buffer, *bufferSize - pushSize );
*bufferSize = (size_t)-1;
*buffer = NULL;
return;
}
*buffer = resizedBuffer;
uint8_t *pushDst = *buffer + *bufferSize - pushSize;
memcpy( pushDst, pushBuffer, pushSize );
}
void mpw_pushString(uint8_t **buffer, size_t *const bufferSize, const char *pushString) {
mpw_pushBuf( buffer, bufferSize, pushString, strlen( pushString ) );
}
void mpw_pushInt(uint8_t **const buffer, size_t *const bufferSize, const uint32_t pushInt) {
mpw_pushBuf( buffer, bufferSize, &pushInt, sizeof( pushInt ) );
}
void mpw_free(const void *buffer, const size_t bufferSize) {
memset( (void *)buffer, 0, bufferSize );
free( (void *)buffer );
}
void mpw_freeString(const char *string) {
mpw_free( string, strlen( string ) );
}
uint8_t const *mpw_scrypt(const size_t keySize, const char *secret, const uint8_t *salt, const size_t saltSize,
uint64_t N, uint32_t r, uint32_t p) {
uint8_t *key = malloc( keySize );
if (!key)
return NULL;
if (crypto_scrypt( (const uint8_t *)secret, strlen( secret ), salt, saltSize, N, r, p, key, keySize ) < 0) {
mpw_free( key, keySize );
return NULL;
}
return key;
}
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);
if (!buffer)
return NULL;
HMAC_SHA256_Buf( key, keySize, salt, saltSize, buffer );
return buffer;
}
const char *mpw_idForBuf(const void *buf, size_t length) {
uint8_t hash[32];
SHA256_Buf( buf, length, hash );
return mpw_hex( hash, 32 );
}
static char *mpw_hex_buf = NULL;
const char *mpw_hex(const void *buf, size_t length) {
mpw_hex_buf = realloc( mpw_hex_buf, length * 2 + 1 );
for (size_t kH = 0; kH < length; kH++)
sprintf( &(mpw_hex_buf[kH * 2]), "%02X", ((const uint8_t *)buf)[kH] );
return mpw_hex_buf;
}
#ifdef COLOR
static int putvari;
static char *putvarc = NULL;
static bool istermsetup = false;
static void initputvar() {
if (putvarc)
free(putvarc);
putvarc=(char *)calloc(256, sizeof(char));
putvari=0;
if (!istermsetup)
istermsetup = (OK == setupterm(NULL, STDERR_FILENO, NULL));
}
static int putvar(int c) {
putvarc[putvari++]=c;
return 0;
}
#endif
const char *mpw_identicon(const char *fullName, const char *masterPassword) {
const char *leftArm[] = { "", "", "", "" };
const char *rightArm[] = { "", "", "", "" };
const char *body[] = { "", "", "", "", "", "" };
const char *accessory[] = {
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" };
uint8_t identiconSeed[32];
HMAC_SHA256_Buf( masterPassword, strlen( masterPassword ), fullName, strlen( fullName ), identiconSeed );
char *colorString, *resetString;
#ifdef COLOR
if (isatty( STDERR_FILENO )) {
uint8_t colorIdentifier = (uint8_t)(identiconSeed[4] % 7 + 1);
initputvar();
tputs(tparm(tgetstr("AF", NULL), colorIdentifier), 1, putvar);
colorString = calloc(strlen(putvarc) + 1, sizeof(char));
strcpy(colorString, putvarc);
tputs(tgetstr("me", NULL), 1, putvar);
resetString = calloc(strlen(putvarc) + 1, sizeof(char));
strcpy(resetString, putvarc);
} else
#endif
{
colorString = calloc( 1, sizeof( char ) );
resetString = calloc( 1, sizeof( char ) );
}
char *identicon = (char *)calloc( 256, sizeof( char ) );
snprintf( identicon, 256, "%s%s%s%s%s%s",
colorString,
leftArm[identiconSeed[0] % (sizeof( leftArm ) / sizeof( leftArm[0] ))],
body[identiconSeed[1] % (sizeof( body ) / sizeof( body[0] ))],
rightArm[identiconSeed[2] % (sizeof( rightArm ) / sizeof( rightArm[0] ))],
accessory[identiconSeed[3] % (sizeof( accessory ) / sizeof( accessory[0] ))],
resetString );
free( colorString );
free( resetString );
return identicon;
}

View File

@@ -0,0 +1,60 @@
//
// mpw-util.h
// MasterPassword
//
// Created by Maarten Billemont on 2014-12-20.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
//// Logging.
#ifdef DEBUG
#define trc(...) fprintf( stderr, __VA_ARGS__ )
#else
#define trc(...) do {} while (0)
#endif
#ifndef ftl
#define ftl(...) do { fprintf( stderr, __VA_ARGS__ ); abort(); } while (0)
#endif
//// Buffers and memory.
/** Push a buffer onto a buffer. reallocs the given buffer and appends the given buffer. */
void mpw_pushBuf(
uint8_t **const buffer, size_t *const bufferSize, const void *pushBuffer, const size_t pushSize);
/** Push a string onto a buffer. reallocs the given buffer and appends the given string. */
void mpw_pushString(
uint8_t **buffer, size_t *const bufferSize, const char *pushString);
/** Push an integer onto a buffer. reallocs the given buffer and appends the given integer. */
void mpw_pushInt(
uint8_t **const buffer, size_t *const bufferSize, const uint32_t pushInt);
/** Free a buffer after zero'ing its contents. */
void mpw_free(
const void *buffer, const size_t bufferSize);
/** Free a string after zero'ing its contents. */
void mpw_freeString(
const char *string);
//// Cryptographic functions.
/** Perform a scrypt-based key derivation on the given key using the given salt and scrypt parameters.
* @return A new keySize-size allocated buffer. */
uint8_t const *mpw_scrypt(
const size_t keySize, const char *secret, const uint8_t *salt, const size_t saltSize,
uint64_t N, uint32_t r, uint32_t p);
/** Calculate a SHA256-based HMAC by encrypting the given salt with the given key.
* @return A new 32-byte allocated buffer. */
uint8_t const *mpw_hmac_sha256(
const uint8_t *key, const size_t keySize, const uint8_t *salt, const size_t saltSize);
//// Visualizers.
/** Encode a buffer as a string of hexadecimal characters.
* @return A reused buffer, do not free or store it. */
const char *mpw_hex(const void *buf, size_t length);
/** Encode a fingerprint for a buffer.
* @return A reused buffer, do not free or store it. */
const char *mpw_idForBuf(const void *buf, size_t length);
/** Encode a visual fingerprint for a user.
* @return A newly allocated string. */
const char *mpw_identicon(const char *fullName, const char *masterPassword);

View File

@@ -17,8 +17,8 @@ mpw() {
:| _copy 2>/dev/null :| _copy 2>/dev/null
# Ask for the user's name and password if not yet known. # Ask for the user's name and password if not yet known.
MP_USERNAME=${MP_USERNAME:-$(ask 'Your Full Name:')} MP_FULLNAME=${MP_FULLNAME:-$(ask 'Your Full Name:')}
# Start Master Password and copy the output. # Start Master Password and copy the output.
printf %s "$(MP_USERNAME=$MP_USERNAME command mpw "$@")" | _copy printf %s "$(MP_FULLNAME=$MP_FULLNAME command mpw "$@")" | _copy
} }

View File

@@ -1,316 +0,0 @@
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#if defined(__linux__)
#include <linux/fs.h>
#elif defined(__CYGWIN__)
#include <cygwin/fs.h>
#else
#include <sys/disk.h>
#endif
#include <fcntl.h>
#include <unistd.h>
#include <math.h>
#include <pwd.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <alg/sha256.h>
#include <crypto/crypto_scrypt.h>
#include "types.h"
#if defined(READLINE)
#include <readline/readline.h>
#elif defined(EDITLINE)
#include <histedit.h>
#endif
#define MP_N 32768
#define MP_r 8
#define MP_p 2
#define MP_dkLen 64
#define MP_hash PearlHashSHA256
#define MP_env_username "MP_USERNAME"
#define MP_env_sitetype "MP_SITETYPE"
#define MP_env_sitecounter "MP_SITECOUNTER"
void usage() {
fprintf(stderr, "Usage: mpw [-u name] [-t type] [-c counter] site\n\n");
fprintf(stderr, " -u name Specify the full name of the user.\n"
" Defaults to %s in env.\n\n", MP_env_username);
fprintf(stderr, " -t type Specify the password's template.\n"
" Defaults to %s in env or 'long' for password, 'name' for login.\n"
" x, max, maximum | 20 characters, contains symbols.\n"
" l, long | Copy-friendly, 14 characters, contains symbols.\n"
" m, med, medium | Copy-friendly, 8 characters, contains symbols.\n"
" b, basic | 8 characters, no symbols.\n"
" s, short | Copy-friendly, 4 characters, no symbols.\n"
" i, pin | 4 numbers.\n"
" n, name | 9 letter name.\n"
" p, phrase | 20 character sentence.\n\n", MP_env_sitetype);
fprintf(stderr, " -c counter The value of the counter.\n"
" Defaults to %s in env or '1'.\n\n", MP_env_sitecounter);
fprintf(stderr, " -v variant The kind of content to generate.\n"
" Defaults to 'password'.\n"
" p, password | The password to log in with.\n"
" l, login | The username to log in as.\n"
" a, answer | The answer to a security question.\n\n");
fprintf(stderr, " -C context A variant-specific context.\n"
" Defaults to empty.\n"
" -v p, password | Doesn't currently use a context.\n"
" -v l, login | Doesn't currently use a context.\n"
" -v a, answer | Empty for a universal site answer or\n"
" | the most significant word(s) of the question.\n\n");
exit(0);
}
char *homedir(const char *filename) {
char *homedir = NULL;
#if defined(__CYGWIN__)
homedir = getenv("USERPROFILE");
if (!homedir) {
const char *homeDrive = getenv("HOMEDRIVE");
const char *homePath = getenv("HOMEPATH");
homedir = char[strlen(homeDrive) + strlen(homePath) + 1];
sprintf(homedir, "%s/%s", homeDrive, homePath);
}
#else
struct passwd* passwd = getpwuid(getuid());
if (passwd)
homedir = passwd->pw_dir;
if (!homedir)
homedir = getenv("HOME");
#endif
if (!homedir)
homedir = getcwd(NULL, 0);
char *homefile = NULL;
asprintf(&homefile, "%s/%s", homedir, filename);
return homefile;
}
char *getlinep(const char *prompt) {
char *buf = NULL;
size_t bufSize = 0;
ssize_t lineSize;
fprintf(stderr, "%s", prompt);
fprintf(stderr, " ");
if ((lineSize = getline(&buf, &bufSize, stdin)) < 0) {
free(buf);
return NULL;
}
buf[lineSize - 1]=0;
return buf;
}
int main(int argc, char *const argv[]) {
// Read the environment.
char *userName = getenv( MP_env_username );
const char *masterPassword = NULL;
const char *siteName = NULL;
MPElementType siteType = MPElementTypeGeneratedLong;
const char *siteTypeString = getenv( MP_env_sitetype );
MPElementVariant siteVariant = MPElementVariantPassword;
const char *siteVariantString = NULL;
const char *siteContextString = NULL;
uint32_t siteCounter = 1;
const char *siteCounterString = getenv( MP_env_sitecounter );
// Read the options.
for (int opt; (opt = getopt(argc, argv, "u:t:c:v:C:h")) != -1;)
switch (opt) {
case 'u':
userName = optarg;
break;
case 't':
siteTypeString = optarg;
break;
case 'c':
siteCounterString = optarg;
break;
case 'v':
siteVariantString = optarg;
break;
case 'C':
siteContextString = optarg;
break;
case 'h':
usage();
break;
case '?':
switch (optopt) {
case 'u':
fprintf(stderr, "Missing user name to option: -%c\n", optopt);
break;
case 't':
fprintf(stderr, "Missing type name to option: -%c\n", optopt);
break;
case 'c':
fprintf(stderr, "Missing counter value to option: -%c\n", optopt);
break;
default:
fprintf(stderr, "Unknown option: -%c\n", optopt);
}
return 1;
default:
abort();
}
if (optind < argc)
siteName = argv[optind];
// Convert and validate input.
if (!userName) {
if (!(userName = getlinep("Your user name:"))) {
fprintf(stderr, "Missing user name.\n");
return 1;
}
}
trc("userName: %s\n", userName);
if (!siteName) {
if (!(siteName = getlinep("Site name:"))) {
fprintf(stderr, "Missing site name.\n");
return 1;
}
}
trc("siteName: %s\n", siteName);
if (siteCounterString)
siteCounter = atoi( siteCounterString );
if (siteCounter < 1) {
fprintf(stderr, "Invalid site counter: %d\n", siteCounter);
return 1;
}
trc("siteCounter: %d\n", siteCounter);
if (siteVariantString)
siteVariant = VariantWithName( siteVariantString );
trc("siteVariant: %d (%s)\n", siteVariant, siteVariantString);
if (siteVariant == MPElementVariantLogin)
siteType = MPElementTypeGeneratedName;
if (siteVariant == MPElementVariantAnswer)
siteType = MPElementTypeGeneratedPhrase;
if (siteTypeString)
siteType = TypeWithName( siteTypeString );
trc("siteType: %d (%s)\n", siteType, siteTypeString);
// Read the master password.
char *mpwConfigPath = homedir(".mpw");
if (!mpwConfigPath) {
fprintf(stderr, "Couldn't resolve path for configuration file: %d\n", errno);
return 1;
}
trc("mpwConfigPath: %s\n", mpwConfigPath);
FILE *mpwConfig = fopen(mpwConfigPath, "r");
free(mpwConfigPath);
if (mpwConfig) {
char *line = NULL;
size_t linecap = 0;
ssize_t linelen;
while ((linelen = getline(&line, &linecap, mpwConfig)) > 0)
if (strcmp(strsep(&line, ":"), userName) == 0) {
masterPassword = strsep(&line, "\n");
break;
}
}
while (!masterPassword)
masterPassword = getpass( "Your master password: " );
trc("masterPassword: %s\n", masterPassword);
// Summarize operation.
fprintf(stderr, "%s's password for %s:\n[ %s ]: ", userName, siteName, Identicon( userName, masterPassword ));
// Calculate the master key salt.
const char *mpKeyScope = ScopeForVariant(MPElementVariantPassword);
trc("key scope: %s\n", mpKeyScope);
const uint32_t n_userNameLength = htonl(strlen(userName));
const size_t masterKeySaltLength = strlen(mpKeyScope) + sizeof(n_userNameLength) + strlen(userName);
char *masterKeySalt = (char *)malloc( masterKeySaltLength );
if (!masterKeySalt) {
fprintf(stderr, "Could not allocate master key salt: %d\n", errno);
return 1;
}
char *mKS = masterKeySalt;
memcpy(mKS, mpKeyScope, strlen(mpKeyScope)); mKS += strlen(mpKeyScope);
memcpy(mKS, &n_userNameLength, sizeof(n_userNameLength)); mKS += sizeof(n_userNameLength);
memcpy(mKS, userName, strlen(userName)); mKS += strlen(userName);
if (mKS - masterKeySalt != masterKeySaltLength)
abort();
trc("masterKeySalt ID: %s\n", IDForBuf(masterKeySalt, masterKeySaltLength));
// Calculate the master key.
uint8_t *masterKey = (uint8_t *)malloc( MP_dkLen );
if (!masterKey) {
fprintf(stderr, "Could not allocate master key: %d\n", errno);
return 1;
}
if (crypto_scrypt( (const uint8_t *)masterPassword, strlen(masterPassword), (const uint8_t *)masterKeySalt, masterKeySaltLength, MP_N, MP_r, MP_p, masterKey, MP_dkLen ) < 0) {
fprintf(stderr, "Could not generate master key: %d\n", errno);
return 1;
}
memset(masterKeySalt, 0, masterKeySaltLength);
free(masterKeySalt);
trc("masterPassword Hex: %s\n", Hex(masterPassword, strlen(masterPassword)));
trc("masterPassword ID: %s\n", IDForBuf(masterPassword, strlen(masterPassword)));
trc("masterKey ID: %s\n", IDForBuf(masterKey, MP_dkLen));
// Calculate the site seed.
const char *mpSiteScope = ScopeForVariant(siteVariant);
trc("site scope: %s, context: %s\n", mpSiteScope, siteContextString == NULL? "<empty>": siteContextString);
const uint32_t n_siteNameLength = htonl(strlen(siteName));
const uint32_t n_siteCounter = htonl(siteCounter);
const uint32_t n_siteContextLength = siteContextString == NULL? 0: htonl(strlen(siteContextString));
size_t sitePasswordInfoLength = strlen(mpSiteScope) + sizeof(n_siteNameLength) + strlen(siteName) + sizeof(n_siteCounter);
if (siteContextString)
sitePasswordInfoLength += sizeof(n_siteContextLength) + strlen(siteContextString);
char *sitePasswordInfo = (char *)malloc( sitePasswordInfoLength );
if (!sitePasswordInfo) {
fprintf(stderr, "Could not allocate site seed: %d\n", errno);
return 1;
}
char *sPI = sitePasswordInfo;
memcpy(sPI, mpSiteScope, strlen(mpSiteScope)); sPI += strlen(mpSiteScope);
memcpy(sPI, &n_siteNameLength, sizeof(n_siteNameLength)); sPI += sizeof(n_siteNameLength);
memcpy(sPI, siteName, strlen(siteName)); sPI += strlen(siteName);
memcpy(sPI, &n_siteCounter, sizeof(n_siteCounter)); sPI += sizeof(n_siteCounter);
if (siteContextString) {
memcpy(sPI, &n_siteContextLength, sizeof(n_siteContextLength)); sPI += sizeof(n_siteContextLength);
memcpy(sPI, siteContextString, strlen(siteContextString)); sPI += strlen(siteContextString);
}
if (sPI - sitePasswordInfo != sitePasswordInfoLength)
abort();
trc("seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)\n", mpSiteScope, Hex(&n_siteNameLength, sizeof(n_siteNameLength)), siteName, Hex(&n_siteCounter, sizeof(n_siteCounter)), Hex(&n_siteContextLength, sizeof(n_siteContextLength)), siteContextString);
trc("sitePasswordInfo ID: %s\n", IDForBuf(sitePasswordInfo, sitePasswordInfoLength));
uint8_t sitePasswordSeed[32];
HMAC_SHA256_Buf(masterKey, MP_dkLen, sitePasswordInfo, sitePasswordInfoLength, sitePasswordSeed);
memset(masterKey, 0, MP_dkLen);
memset(sitePasswordInfo, 0, sitePasswordInfoLength);
free(masterKey);
free(sitePasswordInfo);
trc("sitePasswordSeed ID: %s\n", IDForBuf(sitePasswordSeed, 32));
// Determine the cipher.
const char *cipher = CipherForType(siteType, sitePasswordSeed[0]);
trc("type %s, cipher: %s\n", siteTypeString, cipher);
if (strlen(cipher) > 32)
abort();
// Encode the password from the seed using the cipher.
char *sitePassword = (char *)calloc(strlen(cipher) + 1, sizeof(char));
for (int c = 0; c < strlen(cipher); ++c) {
sitePassword[c] = CharacterFromClass(cipher[c], sitePasswordSeed[c + 1]);
trc("class %c, character: %c\n", cipher[c], sitePassword[c]);
}
memset(sitePasswordSeed, 0, sizeof(sitePasswordSeed));
// Output the password.
fprintf( stdout, "%s\n", sitePassword );
return 0;
}

View File

@@ -0,0 +1 @@
../Java/masterpassword-algorithm/src/test/resources/mpw_tests.xml

View File

@@ -1,238 +0,0 @@
//
// MPTypes.h
// MasterPassword
//
// Created by Maarten Billemont on 02/01/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <alg/sha256.h>
#include <curses.h>
#include <term.h>
#include "types.h"
const MPElementType TypeWithName(const char *typeName) {
char lowerTypeName[strlen(typeName)];
strcpy(lowerTypeName, typeName);
for (char *tN = lowerTypeName; *tN; ++tN)
*tN = tolower(*tN);
if (0 == strcmp(lowerTypeName, "x") || 0 == strcmp(lowerTypeName, "max") || 0 == strcmp(lowerTypeName, "maximum"))
return MPElementTypeGeneratedMaximum;
if (0 == strcmp(lowerTypeName, "l") || 0 == strcmp(lowerTypeName, "long"))
return MPElementTypeGeneratedLong;
if (0 == strcmp(lowerTypeName, "m") || 0 == strcmp(lowerTypeName, "med") || 0 == strcmp(lowerTypeName, "medium"))
return MPElementTypeGeneratedMedium;
if (0 == strcmp(lowerTypeName, "b") || 0 == strcmp(lowerTypeName, "basic"))
return MPElementTypeGeneratedBasic;
if (0 == strcmp(lowerTypeName, "s") || 0 == strcmp(lowerTypeName, "short"))
return MPElementTypeGeneratedShort;
if (0 == strcmp(lowerTypeName, "i") || 0 == strcmp(lowerTypeName, "pin"))
return MPElementTypeGeneratedPIN;
if (0 == strcmp(lowerTypeName, "n") || 0 == strcmp(lowerTypeName, "name"))
return MPElementTypeGeneratedName;
if (0 == strcmp(lowerTypeName, "p") || 0 == strcmp(lowerTypeName, "phrase"))
return MPElementTypeGeneratedPhrase;
fprintf(stderr, "Not a generated type name: %s", lowerTypeName);
abort();
}
const char *CipherForType(MPElementType type, uint8_t seedByte) {
if (!(type & MPElementTypeClassGenerated)) {
fprintf(stderr, "Not a generated type: %d", type);
abort();
}
switch (type) {
case MPElementTypeGeneratedMaximum: {
const char *ciphers[] = { "anoxxxxxxxxxxxxxxxxx", "axxxxxxxxxxxxxxxxxno" };
return ciphers[seedByte % 2];
}
case MPElementTypeGeneratedLong: {
const char *ciphers[] = { "CvcvnoCvcvCvcv", "CvcvCvcvnoCvcv", "CvcvCvcvCvcvno", "CvccnoCvcvCvcv", "CvccCvcvnoCvcv", "CvccCvcvCvcvno", "CvcvnoCvccCvcv", "CvcvCvccnoCvcv", "CvcvCvccCvcvno", "CvcvnoCvcvCvcc", "CvcvCvcvnoCvcc", "CvcvCvcvCvccno", "CvccnoCvccCvcv", "CvccCvccnoCvcv", "CvccCvccCvcvno", "CvcvnoCvccCvcc", "CvcvCvccnoCvcc", "CvcvCvccCvccno", "CvccnoCvcvCvcc", "CvccCvcvnoCvcc", "CvccCvcvCvccno" };
return ciphers[seedByte % 21];
}
case MPElementTypeGeneratedMedium: {
const char *ciphers[] = { "CvcnoCvc", "CvcCvcno" };
return ciphers[seedByte % 2];
}
case MPElementTypeGeneratedBasic: {
const char *ciphers[] = { "aaanaaan", "aannaaan", "aaannaaa" };
return ciphers[seedByte % 3];
}
case MPElementTypeGeneratedShort: {
return "Cvcn";
}
case MPElementTypeGeneratedPIN: {
return "nnnn";
}
case MPElementTypeGeneratedName: {
return "cvccvcvcv";
}
case MPElementTypeGeneratedPhrase: {
const char *ciphers[] = { "cvcc cvc cvccvcv cvc", "cvc cvccvcvcv cvcv", "cv cvccv cvc cvcvccv" };
return ciphers[seedByte % 3];
}
default: {
fprintf(stderr, "Unknown generated type: %d", type);
abort();
}
}
}
const MPElementVariant VariantWithName(const char *variantName) {
char lowerVariantName[strlen(variantName)];
strcpy(lowerVariantName, variantName);
for (char *vN = lowerVariantName; *vN; ++vN)
*vN = tolower(*vN);
if (0 == strcmp(lowerVariantName, "p") || 0 == strcmp(lowerVariantName, "password"))
return MPElementVariantPassword;
if (0 == strcmp(lowerVariantName, "l") || 0 == strcmp(lowerVariantName, "login"))
return MPElementVariantLogin;
if (0 == strcmp(lowerVariantName, "a") || 0 == strcmp(lowerVariantName, "answer"))
return MPElementVariantAnswer;
fprintf(stderr, "Not a variant name: %s", lowerVariantName);
abort();
}
const char *ScopeForVariant(MPElementVariant variant) {
switch (variant) {
case MPElementVariantPassword: {
return "com.lyndir.masterpassword";
}
case MPElementVariantLogin: {
return "com.lyndir.masterpassword.login";
}
case MPElementVariantAnswer: {
return "com.lyndir.masterpassword.answer";
}
default: {
fprintf(stderr, "Unknown variant: %d", variant);
abort();
}
}
}
const char CharacterFromClass(char characterClass, uint8_t seedByte) {
const char *classCharacters;
switch (characterClass) {
case 'V': {
classCharacters = "AEIOU";
break;
}
case 'C': {
classCharacters = "BCDFGHJKLMNPQRSTVWXYZ";
break;
}
case 'v': {
classCharacters = "aeiou";
break;
}
case 'c': {
classCharacters = "bcdfghjklmnpqrstvwxyz";
break;
}
case 'A': {
classCharacters = "AEIOUBCDFGHJKLMNPQRSTVWXYZ";
break;
}
case 'a': {
classCharacters = "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz";
break;
}
case 'n': {
classCharacters = "0123456789";
break;
}
case 'o': {
classCharacters = "@&%?,=[]_:-+*$#!'^~;()/.";
break;
}
case 'x': {
classCharacters = "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz0123456789!@#$%^&*()";
break;
}
case ' ': {
classCharacters = " ";
break;
}
default: {
fprintf(stderr, "Unknown character class: %c", characterClass);
abort();
}
}
return classCharacters[seedByte % strlen(classCharacters)];
}
const char *IDForBuf(const void *buf, size_t length) {
uint8_t hash[32];
SHA256_Buf(buf, length, hash);
char *id = (char *)calloc(65, sizeof(char));
for (int kH = 0; kH < 32; kH++)
sprintf(&(id[kH * 2]), "%02X", hash[kH]);
return id;
}
const char *Hex(const void *buf, size_t length) {
char *id = (char *)calloc(length*2+1, sizeof(char));
for (int kH = 0; kH < length; kH++)
sprintf(&(id[kH * 2]), "%02X", ((const uint8_t*)buf)[kH]);
return id;
}
int putvari;
char *putvarc = NULL;
static void initputvar() {
if (putvarc)
free(putvarc);
putvari=0;
putvarc=(char *)calloc(256, sizeof(char));
}
static int putvar(int c) {
putvarc[putvari++]=c;
return 0;
}
const char *Identicon(const char *userName, const char *masterPassword) {
const char *left[] = { "", "", "", "" };
const char *right[] = { "", "", "", "" };
const char *body[] = { "", "", "", "", "", "" };
const char *accessory[] = { "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" };
uint8_t identiconSeed[32];
HMAC_SHA256_Buf(masterPassword, strlen(masterPassword), userName, strlen(userName), identiconSeed);
char *identicon = (char *)calloc(20, sizeof(char));
setupterm(NULL, 2, NULL);
initputvar();
tputs(tparm(tgetstr("AF", NULL), identiconSeed[4] % 7 + 1), 1, putvar);
char red[strlen(putvarc)];
strcpy(red, putvarc);
tputs(tgetstr("me", NULL), 1, putvar);
char reset[strlen(putvarc)];
strcpy(reset, putvarc);
sprintf(identicon, "%s%s%s%s%s%s",
red,
left[identiconSeed[0] % (sizeof(left) / sizeof(left[0]))],
body[identiconSeed[1] % (sizeof(body) / sizeof(body[0]))],
right[identiconSeed[2] % (sizeof(right) / sizeof(right[0]))],
accessory[identiconSeed[3] % (sizeof(accessory) / sizeof(accessory[0]))],
reset);
return identicon;
}

View File

@@ -1,60 +0,0 @@
//
// MPTypes.h
// MasterPassword
//
// Created by Maarten Billemont on 02/01/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
typedef enum {
/** Generate the password to log in with. */
MPElementVariantPassword,
/** Generate the login name to log in as. */
MPElementVariantLogin,
/** Generate the answer to a security question. */
MPElementVariantAnswer,
} MPElementVariant;
typedef enum {
/** Generate the password. */
MPElementTypeClassGenerated = 1 << 4,
/** Store the password. */
MPElementTypeClassStored = 1 << 5,
} MPElementTypeClass;
typedef enum {
/** Export the key-protected content data. */
MPElementFeatureExportContent = 1 << 10,
/** Never export content. */
MPElementFeatureDevicePrivate = 1 << 11,
} MPElementFeature;
typedef enum {
MPElementTypeGeneratedMaximum = 0x0 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedLong = 0x1 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedMedium = 0x2 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedBasic = 0x4 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedShort = 0x3 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedPIN = 0x5 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedName = 0xE | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedPhrase = 0xF | MPElementTypeClassGenerated | 0x0,
MPElementTypeStoredPersonal = 0x0 | MPElementTypeClassStored | MPElementFeatureExportContent,
MPElementTypeStoredDevicePrivate = 0x1 | MPElementTypeClassStored | MPElementFeatureDevicePrivate,
} MPElementType;
#ifdef DEBUG
#define trc(...) fprintf(stderr, __VA_ARGS__)
#else
#define trc(...) do {} while (0)
#endif
const MPElementVariant VariantWithName(const char *variantName);
const char *ScopeForVariant(MPElementVariant variant);
const MPElementType TypeWithName(const char *typeName);
const char *CipherForType(MPElementType type, uint8_t seedByte);
const char CharacterFromClass(char characterClass, uint8_t seedByte);
const char *IDForBuf(const void *buf, size_t length);
const char *Hex(const void *buf, size_t length);
const char *Identicon(const char *userName, const char *masterPassword);

View File

@@ -33,17 +33,24 @@
</dependency> </dependency>
<!-- EXTERNAL DEPENDENCIES --> <!-- EXTERNAL DEPENDENCIES -->
<dependency>
<groupId>net.sf.plist</groupId>
<artifactId>property-list</artifactId>
<version>2.0.0</version>
</dependency>
<dependency> <dependency>
<groupId>com.lambdaworks</groupId> <groupId>com.lambdaworks</groupId>
<artifactId>scrypt</artifactId> <artifactId>scrypt</artifactId>
<version>1.4.0</version> <version>1.4.0</version>
</dependency> </dependency>
<!-- TESTING -->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -1,14 +0,0 @@
package com.lyndir.masterpassword;
/**
* <i>07 04, 2012</i>
*
* @author lhunath
*/
public enum MPElementFeature {
/** Export the key-protected content data. */
ExportContent,
/** Never export content. */
DevicePrivate,
}

View File

@@ -1,105 +0,0 @@
package com.lyndir.masterpassword;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.util.Set;
/**
* <i>07 04, 2012</i>
*
* @author lhunath
*/
public enum MPElementType {
GeneratedMaximum( "Maximum Security Password", "Maximum", "20 characters, contains symbols.", MPElementTypeClass.Generated ),
GeneratedLong( "Long Password", "Long", "Copy-friendly, 14 characters, contains symbols.", MPElementTypeClass.Generated ),
GeneratedMedium( "Medium Password", "Medium", "Copy-friendly, 8 characters, contains symbols.", MPElementTypeClass.Generated ),
GeneratedBasic( "Basic Password", "Basic", "8 characters, no symbols.", MPElementTypeClass.Generated ),
GeneratedShort( "Short Password", "Short", "Copy-friendly, 4 characters, no symbols.", MPElementTypeClass.Generated ),
GeneratedPIN( "PIN", "PIN", "4 numbers.", MPElementTypeClass.Generated ),
StoredPersonal( "Personal Password", "Personal", "AES-encrypted, exportable.", MPElementTypeClass.Stored,
MPElementFeature.ExportContent ),
StoredDevicePrivate( "Device Private Password", "Private", "AES-encrypted, not exported.", MPElementTypeClass.Stored,
MPElementFeature.DevicePrivate );
static final Logger logger = Logger.get( MPElementType.class );
private final MPElementTypeClass typeClass;
private final Set<MPElementFeature> typeFeatures;
private final String name;
private final String shortName;
private final String description;
MPElementType(final String name, final String shortName, final String description, final MPElementTypeClass typeClass,
final MPElementFeature... typeFeatures) {
this.name = name;
this.shortName = shortName;
this.typeClass = typeClass;
this.description = description;
ImmutableSet.Builder<MPElementFeature> typeFeaturesBuilder = ImmutableSet.builder();
for (final MPElementFeature typeFeature : typeFeatures) {
typeFeaturesBuilder.add( typeFeature );
}
this.typeFeatures = typeFeaturesBuilder.build();
}
public MPElementTypeClass getTypeClass() {
return typeClass;
}
public Set<MPElementFeature> getTypeFeatures() {
return typeFeatures;
}
public String getName() {
return name;
}
public String getShortName() {
return shortName;
}
public String getDescription() {
return description;
}
/**
* @param name The full or short name of the type we want to look up. It is matched case insensitively.
*
* @return The type with the given name.
*/
public static MPElementType forName(final String name) {
for (final MPElementType type : values())
if (type.getName().equalsIgnoreCase( name ) || type.getShortName().equalsIgnoreCase( name ))
return type;
throw logger.bug( "Element type not known: %s", name );
}
/**
* @param typeClass The class for which we look up types.
*
* @return All types that support the given class.
*/
public static ImmutableList<MPElementType> forClass(final MPElementTypeClass typeClass) {
ImmutableList.Builder<MPElementType> types = ImmutableList.builder();
for (final MPElementType type : values())
if (type.getTypeClass() == typeClass)
types.add( type );
return types.build();
}
}

View File

@@ -1,27 +0,0 @@
package com.lyndir.masterpassword;
import com.lyndir.masterpassword.entity.*;
/**
* <i>07 04, 2012</i>
*
* @author lhunath
*/
public enum MPElementTypeClass {
Generated(MPElementGeneratedEntity.class),
Stored(MPElementStoredEntity.class);
private final Class<? extends MPElementEntity> entityClass;
MPElementTypeClass(final Class<? extends MPElementEntity> entityClass) {
this.entityClass = entityClass;
}
public Class<? extends MPElementEntity> getEntityClass() {
return entityClass;
}
}

View File

@@ -0,0 +1,29 @@
package com.lyndir.masterpassword;
/**
* <i>07 04, 2012</i>
*
* @author lhunath
*/
public enum MPSiteFeature {
/**
* Export the key-protected content data.
*/
ExportContent( 1 << 10 ),
/**
* Never export content.
*/
DevicePrivate( 1 << 11 );
MPSiteFeature(final int mask) {
this.mask = mask;
}
private final int mask;
public int getMask() {
return mask;
}
}

View File

@@ -0,0 +1,210 @@
package com.lyndir.masterpassword;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
/**
* <i>07 04, 2012</i>
*
* @author lhunath
*/
public enum MPSiteType {
GeneratedMaximum( "20 characters, contains symbols.", //
ImmutableList.of( "x", "max", "maximum" ), //
ImmutableList.of( new MPTemplate( "anoxxxxxxxxxxxxxxxxx" ), new MPTemplate( "axxxxxxxxxxxxxxxxxno" ) ), //
MPSiteTypeClass.Generated, 0x0 ),
GeneratedLong( "Copy-friendly, 14 characters, contains symbols.", //
ImmutableList.of( "l", "long" ), //
ImmutableList.of( new MPTemplate( "CvcvnoCvcvCvcv" ), new MPTemplate( "CvcvCvcvnoCvcv" ),
new MPTemplate( "CvcvCvcvCvcvno" ), new MPTemplate( "CvccnoCvcvCvcv" ),
new MPTemplate( "CvccCvcvnoCvcv" ), new MPTemplate( "CvccCvcvCvcvno" ),
new MPTemplate( "CvcvnoCvccCvcv" ), new MPTemplate( "CvcvCvccnoCvcv" ),
new MPTemplate( "CvcvCvccCvcvno" ), new MPTemplate( "CvcvnoCvcvCvcc" ),
new MPTemplate( "CvcvCvcvnoCvcc" ), new MPTemplate( "CvcvCvcvCvccno" ),
new MPTemplate( "CvccnoCvccCvcv" ), new MPTemplate( "CvccCvccnoCvcv" ),
new MPTemplate( "CvccCvccCvcvno" ), new MPTemplate( "CvcvnoCvccCvcc" ),
new MPTemplate( "CvcvCvccnoCvcc" ), new MPTemplate( "CvcvCvccCvccno" ),
new MPTemplate( "CvccnoCvcvCvcc" ), new MPTemplate( "CvccCvcvnoCvcc" ),
new MPTemplate( "CvccCvcvCvccno" ) ), //
MPSiteTypeClass.Generated, 0x1 ),
GeneratedMedium( "Copy-friendly, 8 characters, contains symbols.", //
ImmutableList.of( "m", "med", "medium" ), //
ImmutableList.of( new MPTemplate( "CvcnoCvc" ), new MPTemplate( "CvcCvcno" ) ), //
MPSiteTypeClass.Generated, 0x2 ),
GeneratedBasic( "8 characters, no symbols.", //
ImmutableList.of( "b", "basic" ), //
ImmutableList.of( new MPTemplate( "aaanaaan" ), new MPTemplate( "aannaaan" ), new MPTemplate( "aaannaaa" ) ), //
MPSiteTypeClass.Generated, 0x3 ),
GeneratedShort( "Copy-friendly, 4 characters, no symbols.", //
ImmutableList.of( "s", "short" ), //
ImmutableList.of( new MPTemplate( "Cvcn" ) ), //
MPSiteTypeClass.Generated, 0x4 ),
GeneratedPIN( "4 numbers.", //
ImmutableList.of( "i", "pin" ), //
ImmutableList.of( new MPTemplate( "nnnn" ) ), //
MPSiteTypeClass.Generated, 0x5 ),
GeneratedName( "9 letter name.", //
ImmutableList.of( "n", "name" ), //
ImmutableList.of( new MPTemplate( "cvccvcvcv" ) ), //
MPSiteTypeClass.Generated, 0xE ),
GeneratedPhrase( "20 character sentence.", //
ImmutableList.of( "p", "phrase" ), //
ImmutableList.of( new MPTemplate( "cvcc cvc cvccvcv cvc" ), new MPTemplate( "cvc cvccvcvcv cvcv" ),
new MPTemplate( "cv cvccv cvc cvcvccv" ) ), //
MPSiteTypeClass.Generated, 0xF ),
StoredPersonal( "AES-encrypted, exportable.", //
ImmutableList.of( "personal" ), //
ImmutableList.<MPTemplate>of(), //
MPSiteTypeClass.Stored, 0x0, MPSiteFeature.ExportContent ),
StoredDevicePrivate( "AES-encrypted, not exported.", //
ImmutableList.of( "device" ), //
ImmutableList.<MPTemplate>of(), //
MPSiteTypeClass.Stored, 0x1, MPSiteFeature.DevicePrivate );
static final Logger logger = Logger.get( MPSiteType.class );
private final String description;
private final List<String> options;
private final List<MPTemplate> templates;
private final MPSiteTypeClass typeClass;
private final int typeIndex;
private final Set<MPSiteFeature> typeFeatures;
MPSiteType(final String description, final List<String> options, final List<MPTemplate> templates, final MPSiteTypeClass typeClass,
final int typeIndex, final MPSiteFeature... typeFeatures) {
this.description = description;
this.options = options;
this.templates = templates;
this.typeClass = typeClass;
this.typeIndex = typeIndex;
ImmutableSet.Builder<MPSiteFeature> typeFeaturesBuilder = ImmutableSet.builder();
for (final MPSiteFeature typeFeature : typeFeatures) {
typeFeaturesBuilder.add( typeFeature );
}
this.typeFeatures = typeFeaturesBuilder.build();
}
public String getDescription() {
return description;
}
public List<String> getOptions() {
return options;
}
public MPSiteTypeClass getTypeClass() {
return typeClass;
}
public Set<MPSiteFeature> getTypeFeatures() {
return typeFeatures;
}
public int getType() {
int mask = typeIndex | typeClass.getMask();
for (MPSiteFeature typeFeature : typeFeatures)
mask |= typeFeature.getMask();
return mask;
}
/**
* @param option The option to select a type with. It is matched case insensitively.
*
* @return The type registered for the given option.
*/
public static MPSiteType forOption(final String option) {
for (final MPSiteType type : values())
if (type.getOptions().contains( option.toLowerCase() ))
return type;
throw logger.bug( "No type for option: %s", option );
}
/**
* @param name The name of the type to look up. It is matched case insensitively.
*
* @return The type registered with the given name.
*/
public static MPSiteType forName(final String name) {
if (name == null)
return null;
for (final MPSiteType type : values())
if (type.name().equalsIgnoreCase( name ))
return type;
throw logger.bug( "No type for name: %s", name );
}
/**
* @param typeClass The class for which we look up types.
*
* @return All types that support the given class.
*/
public static ImmutableList<MPSiteType> forClass(final MPSiteTypeClass typeClass) {
ImmutableList.Builder<MPSiteType> types = ImmutableList.builder();
for (final MPSiteType type : values())
if (type.getTypeClass() == typeClass)
types.add( type );
return types.build();
}
/**
* @param type The type for which we look up types.
*
* @return The type registered with the given type.
*/
public static MPSiteType forType(final int type) {
for (MPSiteType siteType : values())
if (siteType.getType() == type)
return siteType;
throw logger.bug( "No type: %s", type );
}
/**
* @param mask The mask for which we look up types.
*
* @return All types that support the given mask.
*/
public static ImmutableList<MPSiteType> forMask(final int mask) {
int typeMask = mask & ~0xF;
ImmutableList.Builder<MPSiteType> types = ImmutableList.builder();
for (MPSiteType siteType : values())
if (((siteType.getType() & ~0xF) & typeMask) != 0)
types.add( siteType );
return types.build();
}
public MPTemplate getTemplateAtRollingIndex(final int templateIndex) {
return templates.get( templateIndex % templates.size() );
}
}

View File

@@ -0,0 +1,21 @@
package com.lyndir.masterpassword;
/**
* <i>07 04, 2012</i>
*
* @author lhunath
*/
public enum MPSiteTypeClass {
Generated( 1 << 4 ),
Stored( 1 << 5 );
private final int mask;
MPSiteTypeClass(final int mask) {
this.mask = mask;
}
public int getMask() {
return mask;
}
}

View File

@@ -0,0 +1,80 @@
package com.lyndir.masterpassword;
import com.google.common.collect.ImmutableList;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.util.List;
/**
* @author lhunath, 14-12-02
*/
public enum MPSiteVariant {
Password( "The password to log in with.", "Doesn't currently use a context.", //
ImmutableList.of( "p", "password" ), "com.lyndir.masterpassword" ),
Login( "The username to log in as.", "Doesn't currently use a context.", //
ImmutableList.of( "l", "login" ), "com.lyndir.masterpassword.login" ),
Answer( "The answer to a security question.", "Empty for a universal site answer or\nthe most significant word(s) of the question.", //
ImmutableList.of( "a", "answer" ), "com.lyndir.masterpassword.answer" );
static final Logger logger = Logger.get( MPSiteType.class );
private final String description;
private final String contextDescription;
private final List<String> options;
private final String scope;
MPSiteVariant(final String description, final String contextDescription, final List<String> options, final String scope) {
this.contextDescription = contextDescription;
this.options = options;
this.description = description;
this.scope = scope;
}
public String getDescription() {
return description;
}
public String getContextDescription() {
return contextDescription;
}
public List<String> getOptions() {
return options;
}
public String getScope() {
return scope;
}
/**
* @param option The option to select a variant with. It is matched case insensitively.
*
* @return The variant registered for the given option.
*/
public static MPSiteVariant forOption(final String option) {
for (final MPSiteVariant variant : values())
if (variant.getOptions().contains( option.toLowerCase() ))
return variant;
throw logger.bug( "No variant for option: %s", option );
}
/**
* @param name The name of the variant to look up. It is matched case insensitively.
*
* @return The variant registered with the given name.
*/
public static MPSiteVariant forName(final String name) {
if (name == null)
return null;
for (final MPSiteVariant type : values())
if (type.name().equalsIgnoreCase( name ))
return type;
throw logger.bug( "No variant for name: %s", name );
}
}

View File

@@ -1,6 +1,9 @@
package com.lyndir.masterpassword; package com.lyndir.masterpassword;
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; import java.util.Map;
@@ -13,20 +16,21 @@ import java.util.Map;
*/ */
public class MPTemplate extends MetaObject { public class MPTemplate extends MetaObject {
private final String templateString;
private final List<MPTemplateCharacterClass> template; private final List<MPTemplateCharacterClass> template;
public MPTemplate(final String template, final Map<Character, MPTemplateCharacterClass> characterClasses) { MPTemplate(final String templateString) {
ImmutableList.Builder<MPTemplateCharacterClass> builder = ImmutableList.<MPTemplateCharacterClass>builder(); ImmutableList.Builder<MPTemplateCharacterClass> builder = ImmutableList.builder();
for (int i = 0; i < template.length(); ++i) for (int i = 0; i < templateString.length(); ++i)
builder.add( characterClasses.get( template.charAt( i ) ) ); builder.add( MPTemplateCharacterClass.forIdentifier( templateString.charAt( i ) ) );
this.template = builder.build(); this.templateString = templateString;
template = builder.build();
} }
public MPTemplate(final List<MPTemplateCharacterClass> template) { public String getTemplateString() {
return templateString;
this.template = template;
} }
public MPTemplateCharacterClass getCharacterClassAtIndex(final int index) { public MPTemplateCharacterClass getCharacterClassAtIndex(final int index) {
@@ -38,4 +42,9 @@ public class MPTemplate extends MetaObject {
return template.size(); return template.size();
} }
@Override
public String toString() {
return strf( "{MPTemplate: %s}", templateString );
}
} }

View File

@@ -1,5 +1,6 @@
package com.lyndir.masterpassword; package com.lyndir.masterpassword;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.MetaObject; import com.lyndir.lhunath.opal.system.util.MetaObject;
import com.lyndir.lhunath.opal.system.util.ObjectMeta; import com.lyndir.lhunath.opal.system.util.ObjectMeta;
@@ -9,16 +10,29 @@ import com.lyndir.lhunath.opal.system.util.ObjectMeta;
* *
* @author lhunath * @author lhunath
*/ */
public class MPTemplateCharacterClass extends MetaObject { public enum MPTemplateCharacterClass {
UpperVowel( 'V', "AEIOU" ),
UpperConsonant( 'C', "BCDFGHJKLMNPQRSTVWXYZ" ),
LowerVowel( 'v', "aeiou" ),
LowerConsonant( 'c', "bcdfghjklmnpqrstvwxyz" ),
UpperAlphanumeric( 'A', "AEIOUBCDFGHJKLMNPQRSTVWXYZ" ),
Alphanumeric( 'a', "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz" ),
Numeric( 'n', "0123456789" ),
Other( 'o', "@&%?,=[]_:-+*$#!'^~;()/." ),
Any( 'x', "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz0123456789!@#$%^&*()" ),
Space( ' ', " " );
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MPTemplateCharacterClass.class );
private final char identifier; private final char identifier;
@ObjectMeta(useFor = { })
private final char[] characters; private final char[] characters;
public MPTemplateCharacterClass(final char identifier, final char[] characters) { MPTemplateCharacterClass(final char identifier, final String characters) {
this.identifier = identifier; this.identifier = identifier;
this.characters = characters; this.characters = characters.toCharArray();
} }
public char getIdentifier() { public char getIdentifier() {
@@ -30,4 +44,12 @@ public class MPTemplateCharacterClass extends MetaObject {
return characters[index % characters.length]; return characters[index % characters.length];
} }
public static MPTemplateCharacterClass forIdentifier(final char identifier) {
for (MPTemplateCharacterClass characterClass : values())
if (characterClass.getIdentifier() == identifier)
return characterClass;
throw logger.bug( "No character class defined for identifier: %s", identifier );
}
} }

View File

@@ -1,109 +0,0 @@
package com.lyndir.masterpassword;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.Closeables;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.MetaObject;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import net.sf.plist.*;
import net.sf.plist.io.PropertyListException;
import net.sf.plist.io.PropertyListParser;
/**
* <i>07 04, 2012</i>
*
* @author lhunath
*/
public class MPTemplates extends MetaObject {
static final Logger logger = Logger.get( MPTemplates.class );
private final Map<MPElementType, List<MPTemplate>> templates;
public MPTemplates(final Map<MPElementType, List<MPTemplate>> templates) {
this.templates = templates;
}
public static MPTemplates load() {
return loadFromPList( "ciphers.plist" );
}
public static MPTemplates loadFromPList(final String templateResource) {
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
InputStream templateStream = Thread.currentThread().getContextClassLoader().getResourceAsStream( templateResource );
Preconditions.checkNotNull( templateStream, "Not found: %s", templateResource );
try {
NSObject plistObject = PropertyListParser.parse( templateStream );
Preconditions.checkState( NSDictionary.class.isAssignableFrom( plistObject.getClass() ) );
NSDictionary plist = (NSDictionary) plistObject;
NSDictionary characterClassesDict = (NSDictionary) plist.get( "MPCharacterClasses" );
NSDictionary templatesDict = (NSDictionary) plist.get( "MPElementGeneratedEntity" );
ImmutableMap.Builder<Character, MPTemplateCharacterClass> characterClassesBuilder = ImmutableMap.builder();
for (final Map.Entry<String, NSObject> characterClassEntry : characterClassesDict.entrySet()) {
String key = characterClassEntry.getKey();
NSObject value = characterClassEntry.getValue();
Preconditions.checkState( key.length() == 1 );
Preconditions.checkState( NSString.class.isAssignableFrom( value.getClass() ));
char character = key.charAt( 0 );
char[] characterClass = ((NSString)value).getValue().toCharArray();
characterClassesBuilder.put( character, new MPTemplateCharacterClass( character, characterClass ) );
}
ImmutableMap<Character, MPTemplateCharacterClass> characterClasses = characterClassesBuilder.build();
ImmutableMap.Builder<MPElementType, List<MPTemplate>> templatesBuilder = ImmutableMap.builder();
for (final Map.Entry<String, NSObject> template : templatesDict.entrySet()) {
String key = template.getKey();
NSObject value = template.getValue();
Preconditions.checkState( NSArray.class.isAssignableFrom( value.getClass() ) );
MPElementType type = MPElementType.forName( key );
List<NSObject> templateStrings = ((NSArray) value).getValue();
ImmutableList.Builder<MPTemplate> typeTemplatesBuilder = ImmutableList.<MPTemplate>builder();
for (final NSObject templateString : templateStrings)
typeTemplatesBuilder.add( new MPTemplate( ((NSString) templateString).getValue(), characterClasses ) );
templatesBuilder.put( type, typeTemplatesBuilder.build() );
}
ImmutableMap<MPElementType, List<MPTemplate>> templates = templatesBuilder.build();
return new MPTemplates( templates );
}
catch (PropertyListException e) {
logger.err( e, "Could not parse templates from: %s", templateResource );
throw Throwables.propagate( e );
}
catch (IOException e) {
logger.err( e, "Could not read templates from: %s", templateResource );
throw Throwables.propagate( e );
}
finally {
Closeables.closeQuietly( templateStream );
}
}
public MPTemplate getTemplateForTypeAtRollingIndex(final MPElementType type, final int templateIndex) {
List<MPTemplate> typeTemplates = templates.get( type );
return typeTemplates.get( templateIndex % typeTemplates.size() );
}
public static void main(final String... arguments) {
load();
}
}

View File

@@ -2,11 +2,8 @@ package com.lyndir.masterpassword;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.io.CharSource;
import com.google.common.io.CharStreams;
import com.google.common.primitives.Bytes; import com.google.common.primitives.Bytes;
import com.lambdaworks.crypto.SCrypt; import com.lambdaworks.crypto.SCrypt;
import com.lyndir.lhunath.opal.crypto.CryptUtils;
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.ByteBuffer;
@@ -14,7 +11,7 @@ import java.nio.ByteOrder;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.Arrays; import java.util.Arrays;
import javax.xml.stream.events.Characters; import javax.annotation.Nullable;
/** /**
@@ -22,98 +19,118 @@ import javax.xml.stream.events.Characters;
*/ */
public class MasterKey { public 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_N = 32768;
private static final int MP_r = 8; private static final int MP_r = 8;
private static final int MP_p = 2; private static final int MP_p = 2;
private static final int MP_dkLen = 64; 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 Charset MP_charset = Charsets.UTF_8;
private static final ByteOrder MP_byteOrder = ByteOrder.BIG_ENDIAN; private static final ByteOrder MP_byteOrder = ByteOrder.BIG_ENDIAN;
private static final MessageDigests MP_hash = MessageDigests.SHA256; private static final MessageDigests MP_hash = MessageDigests.SHA256;
private static final MessageAuthenticationDigests MP_mac = MessageAuthenticationDigests.HmacSHA256; private static final MessageAuthenticationDigests MP_mac = MessageAuthenticationDigests.HmacSHA256;
private static final MPTemplates templates = MPTemplates.load();
private final String userName; private final String fullName;
private final byte[] key; private final byte[] masterKey;
private boolean valid; private boolean valid;
public MasterKey(final String userName, final String masterPassword) { public MasterKey(final String fullName, final String masterPassword) {
this.userName = userName; this.fullName = fullName;
logger.trc( "fullName: %s", fullName );
logger.trc( "masterPassword: %s", masterPassword );
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
byte[] userNameLengthBytes = ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ) byte[] userNameBytes = fullName.getBytes( MP_charset );
.order( MP_byteOrder ) byte[] userNameLengthBytes = bytesForInt( userNameBytes.length );
.putInt( userName.length() )
.array(); String mpKeyScope = MPSiteVariant.Password.getScope();
byte[] salt = Bytes.concat( "com.lyndir.masterpassword".getBytes( MP_charset ), // byte[] masterKeySalt = Bytes.concat( mpKeyScope.getBytes( MP_charset ), userNameLengthBytes, userNameBytes );
userNameLengthBytes, userName.getBytes( MP_charset ) ); logger.trc( "key scope: %s", mpKeyScope );
logger.trc( "masterKeySalt ID: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) );
try { try {
key = SCrypt.scrypt( masterPassword.getBytes( MP_charset ), salt, MP_N, MP_r, MP_p, MP_dkLen ); masterKey = SCrypt.scrypt( masterPassword.getBytes( MP_charset ), masterKeySalt, MP_N, MP_r, MP_p, MP_dkLen );
valid = true; valid = true;
logger.trc( "User: %s, master password derives to key ID: %s (took %.2fs)", // logger.trc( "masterKey ID: %s (derived in %.2fs)", CodeUtils.encodeHex( idForBytes( masterKey ) ),
userName, getKeyID(), (double) (System.currentTimeMillis() - start) / 1000 ); (System.currentTimeMillis() - start) / 1000D );
} }
catch (GeneralSecurityException e) { catch (GeneralSecurityException e) {
throw logger.bug( e ); throw logger.bug( e );
} }
} }
public String getUserName() { public String getFullName() {
return userName; return fullName;
} }
public String getKeyID() { public byte[] getKeyID() {
Preconditions.checkState( valid ); Preconditions.checkState( valid );
return CodeUtils.encodeHex( MP_hash.of( key ) ); return idForBytes( masterKey );
} }
private byte[] getSubkey(final int subkeyLength) { private byte[] getSubKey(final int subkeyLength) {
Preconditions.checkState( valid ); Preconditions.checkState( valid );
byte[] subkey = new byte[Math.min( subkeyLength, key.length )]; byte[] subkey = new byte[Math.min( subkeyLength, masterKey.length )];
System.arraycopy( key, 0, subkey, 0, subkey.length ); System.arraycopy( masterKey, 0, subkey, 0, subkey.length );
return subkey; return subkey;
} }
public String encode(final String name, final MPElementType type, int counter) { public String encode(final String siteName, final MPSiteType siteType, int siteCounter, final MPSiteVariant siteVariant,
@Nullable final String siteContext) {
Preconditions.checkState( valid ); Preconditions.checkState( valid );
Preconditions.checkArgument( type.getTypeClass() == MPElementTypeClass.Generated ); Preconditions.checkArgument( siteType.getTypeClass() == MPSiteTypeClass.Generated );
Preconditions.checkArgument( !name.isEmpty() ); Preconditions.checkArgument( !siteName.isEmpty() );
if (counter == 0) logger.trc( "siteName: %s", siteName );
counter = (int) (System.currentTimeMillis() / (300 * 1000)) * 300; logger.trc( "siteCounter: %d", siteCounter );
logger.trc( "siteVariant: %d (%s)", siteVariant.ordinal(), siteVariant );
logger.trc( "siteType: %d (%s)", siteType.ordinal(), siteType );
byte[] nameLengthBytes = ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( MP_byteOrder ).putInt( name.length() ).array(); if (siteCounter == 0)
byte[] counterBytes = ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( MP_byteOrder ).putInt( counter ).array(); siteCounter = (int) (System.currentTimeMillis() / (300 * 1000)) * 300;
logger.trc( "seed from: hmac-sha256(%s, 'com.lyndir.masterpassword' | %s | %s | %s)", CryptUtils.encodeBase64( key ),
CodeUtils.encodeHex( nameLengthBytes ), name, CodeUtils.encodeHex( counterBytes ) );
byte[] seed = MP_mac.of( key, Bytes.concat( "com.lyndir.masterpassword".getBytes( MP_charset ), //
nameLengthBytes, //
name.getBytes( MP_charset ), //
counterBytes ) );
logger.trc( "seed is: %s", CryptUtils.encodeBase64( seed ) );
Preconditions.checkState( seed.length > 0 ); String siteScope = siteVariant.getScope();
int templateIndex = seed[0] & 0xFF; // Mask the integer's sign. byte[] siteNameBytes = siteName.getBytes( MP_charset );
MPTemplate template = templates.getTemplateForTypeAtRollingIndex( type, templateIndex ); byte[] siteNameLengthBytes = bytesForInt( siteNameBytes.length );
logger.trc( "type: %s, template: %s", type, template ); 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() ); StringBuilder password = new StringBuilder( template.length() );
for (int i = 0; i < template.length(); ++i) { for (int i = 0; i < template.length(); ++i) {
int characterIndex = seed[i + 1] & 0xFF; // Mask the integer's sign. int characterIndex = sitePasswordSeed[i + 1] & 0xFF; // Mask the integer's sign.
MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i ); MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i );
char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex ); char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex );
logger.trc( "class: %s, index: %d, byte: 0x%02X, chosen password character: %s", characterClass, characterIndex, seed[i + 1], logger.trc( "class %c, index %d (0x%02X) -> character: %c", characterClass.getIdentifier(), characterIndex,
passwordCharacter ); sitePasswordSeed[i + 1], passwordCharacter );
password.append( passwordCharacter ); password.append( passwordCharacter );
} }
@@ -124,6 +141,14 @@ public class MasterKey {
public void invalidate() { public void invalidate() {
valid = false; valid = false;
Arrays.fill( key, (byte) 0 ); Arrays.fill( masterKey, (byte) 0 );
}
private static byte[] bytesForInt(final int integer) {
return ByteBuffer.allocate( MP_intLen / Byte.SIZE ).order( MP_byteOrder ).putInt( integer ).array();
}
private static byte[] idForBytes(final byte[] bytes) {
return MP_hash.of( bytes );
} }
} }

View File

@@ -1,10 +0,0 @@
package com.lyndir.masterpassword.entity;
/**
* <i>07 04, 2012</i>
*
* @author lhunath
*/
public class MPElementEntity {
}

View File

@@ -1,10 +0,0 @@
package com.lyndir.masterpassword.entity;
/**
* <i>07 04, 2012</i>
*
* @author lhunath
*/
public class MPElementGeneratedEntity extends MPElementEntity {
}

View File

@@ -1,10 +0,0 @@
package com.lyndir.masterpassword.entity;
/**
* <i>07 04, 2012</i>
*
* @author lhunath
*/
public class MPElementStoredEntity extends MPElementEntity {
}

View File

@@ -1 +0,0 @@
../../../../../../MasterPassword/Resources/Data/ciphers.plist

View File

@@ -0,0 +1,188 @@
package com.lyndir.masterpassword;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.NNSupplier;
import com.lyndir.lhunath.opal.system.util.NSupplier;
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.xml.bind.annotation.*;
/**
* @author lhunath, 14-12-05
*/
@XmlRootElement(name = "tests")
public class MPWTests {
public static final String ID_DEFAULT = "default";
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MPWTests.class );
@XmlElement(name = "case")
private List<Case> cases;
public List<Case> getCases() {
return cases;
}
public Case getCase(String identifier) {
for (Case testCase : getCases())
if (identifier.equals( testCase.getIdentifier() ))
return testCase;
throw new IllegalArgumentException( "No case for identifier: " + identifier );
}
@XmlRootElement(name = "case")
public static class Case {
@XmlAttribute(name = "id")
private String identifier;
@XmlAttribute
private String parent;
@XmlElement
private String fullName;
@XmlElement
private String masterPassword;
@XmlElement
private String keyID;
@XmlElement
private String siteName;
@XmlElement
private Integer siteCounter;
@XmlElement
private String siteType;
@XmlElement
private String siteVariant;
@XmlElement
private String siteContext;
@XmlElement
private String result;
private transient Case parentCase;
public void setTests(MPWTests tests) {
if (parent != null) {
parentCase = tests.getCase( parent );
fullName = ifNotNullElse( fullName, new NNSupplier<String>() {
@Nonnull
@Override
public String get() {
return parentCase.getFullName();
}
} );
masterPassword = ifNotNullElse( masterPassword, new NNSupplier<String>() {
@Nonnull
@Override
public String get() {
return parentCase.getMasterPassword();
}
} );
keyID = ifNotNullElse( keyID, new NNSupplier<String>() {
@Nonnull
@Override
public String get() {
return parentCase.getKeyID();
}
} );
siteName = ifNotNullElse( siteName, new NNSupplier<String>() {
@Nonnull
@Override
public String get() {
return parentCase.getSiteName();
}
} );
siteCounter = ifNotNullElse( siteCounter, new NNSupplier<Integer>() {
@Nonnull
@Override
public Integer get() {
return parentCase.getSiteCounter();
}
} );
siteType = ifNotNullElse( siteType, new NNSupplier<String>() {
@Nonnull
@Override
public String get() {
return parentCase.getSiteType().name();
}
} );
siteVariant = ifNotNullElse( siteVariant, new NNSupplier<String>() {
@Nonnull
@Override
public String get() {
return parentCase.getSiteVariant().name();
}
} );
siteContext = ifNotNullElseNullable( siteContext, new NSupplier<String>() {
@Nonnull
@Override
public String get() {
return parentCase.getSiteContext();
}
} );
result = ifNotNullElse( result, new NNSupplier<String>() {
@Nonnull
@Override
public String get() {
return parentCase.getResult();
}
} );
}
}
public String getIdentifier() {
return identifier;
}
@Nullable
public Case getParentCase() {
return parentCase;
}
public String getFullName() {
return fullName;
}
public String getMasterPassword() {
return masterPassword;
}
public String getKeyID() {
return keyID;
}
public String getSiteName() {
return siteName;
}
public int getSiteCounter() {
return siteCounter;
}
public MPSiteType getSiteType() {
return MPSiteType.forName( siteType );
}
public MPSiteVariant getSiteVariant() {
return MPSiteVariant.forName( siteVariant );
}
public String getSiteContext() {
return siteContext;
}
public String getResult() {
return result;
}
@Override
public String toString() {
return identifier;
}
}
}

View File

@@ -0,0 +1,77 @@
package com.lyndir.masterpassword;
import static org.testng.Assert.*;
import com.google.common.io.Resources;
import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.net.URL;
import javax.xml.bind.JAXBContext;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
public class MasterKeyTest {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MasterKeyTest.class );
private MPWTests tests;
private MPWTests.Case defaultCase;
@BeforeMethod
public void setUp()
throws Exception {
URL testCasesResource = Resources.getResource( "mpw_tests.xml" );
tests = (MPWTests) JAXBContext.newInstance( MPWTests.class ).createUnmarshaller().unmarshal( testCasesResource );
for (MPWTests.Case testCase : tests.getCases())
testCase.setTests( tests );
defaultCase = tests.getCase( MPWTests.ID_DEFAULT );
}
@Test
public void testEncode()
throws Exception {
for (MPWTests.Case testCase : tests.getCases()) {
MasterKey masterKey = new MasterKey( testCase.getFullName(), testCase.getMasterPassword() );
assertEquals(
masterKey.encode( testCase.getSiteName(), testCase.getSiteType(), testCase.getSiteCounter(), testCase.getSiteVariant(),
testCase.getSiteContext() ), testCase.getResult(), "Failed test case: " + testCase );
}
}
@Test
public void testGetUserName()
throws Exception {
assertEquals( new MasterKey( defaultCase.getFullName(), defaultCase.getMasterPassword() ).getFullName(),
defaultCase.getFullName() );
}
@Test
public void testGetKeyID()
throws Exception {
for (MPWTests.Case testCase : tests.getCases()) {
MasterKey masterKey = new MasterKey( testCase.getFullName(), testCase.getMasterPassword() );
assertEquals( CodeUtils.encodeHex( masterKey.getKeyID() ), testCase.getKeyID(), "Failed test case: " + testCase );
}
}
@Test
public void testInvalidate()
throws Exception {
try {
MasterKey masterKey = new MasterKey( defaultCase.getFullName(), defaultCase.getMasterPassword() );
masterKey.invalidate();
masterKey.encode( defaultCase.getSiteName(), defaultCase.getSiteType(), defaultCase.getSiteCounter(),
defaultCase.getSiteVariant(), defaultCase.getSiteContext() );
assertTrue( false, "Master key should have been invalidated, but was still usable." );
}
catch (IllegalStateException ignored) {
}
}
}

View File

@@ -0,0 +1,15 @@
<configuration scan="false">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>%-8relative %22c{0} [%-5level] %msg%n</Pattern>
</layout>
</appender>
<logger name="com.lyndir.masterpassword" level="${mp.log.level:-TRACE}" />
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>

View File

@@ -0,0 +1,73 @@
<tests>
<case id="default">
<fullName>Robert Lee Mitchell</fullName>
<masterPassword>banana colored duckling</masterPassword>
<keyID>98EEF4D1DF46D849574A82A03C3177056B15DFFCA29BB3899DE4628453675302</keyID>
<siteName>masterpasswordapp.com</siteName>
<siteCounter>1</siteCounter>
<siteType>GeneratedLong</siteType>
<siteVariant>Password</siteVariant>
<result>Jejr5[RepuSosp</result>
</case>
<case id="mb_fullName" parent="default">
<fullName></fullName>
<keyID>1717AA1F9BF5BA56CD0965CDA3D78E6D2E6A1EA8C067A8EA621F3DDAD4A87EB8</keyID>
<result>NopaDajh8=Fene</result>
</case>
<case id="mb_masterPassword" parent="default">
<masterPassword></masterPassword>
<keyID>351432B8528A5ABECAB768CA95015097DE76FE14C41E10AF36C67DCFB8917E08</keyID>
<result>QesuHirv5-Xepl</result>
</case>
<case id="mb_siteName" parent="default">
<siteName></siteName>
<result>LiheCuwhSerz6)</result>
</case>
<case id="loginName" parent="default">
<siteVariant>Login</siteVariant>
<siteType>GeneratedName</siteType>
<result>wohzaqage</result>
</case>
<case id="securityAnswer" parent="default">
<siteVariant>Answer</siteVariant>
<siteType>GeneratedPhrase</siteType>
<result>xin diyjiqoja hubu</result>
</case>
<case id="securityAnswer_context" parent="securityAnswer">
<siteContext>question</siteContext>
<result>xogx tem cegyiva jab</result>
</case>
<case id="type_maximum" parent="default">
<siteType>GeneratedMaximum</siteType>
<result>W6@692^B1#&amp;@gVdSdLZ@</result>
</case>
<case id="type_medium" parent="default">
<siteType>GeneratedMedium</siteType>
<result>Jej2$Quv</result>
</case>
<case id="type_basic" parent="default">
<siteType>GeneratedBasic</siteType>
<result>WAo2xIg6</result>
</case>
<case id="type_short" parent="default">
<siteType>GeneratedShort</siteType>
<result>Jej2</result>
</case>
<case id="type_pin" parent="default">
<siteType>GeneratedPIN</siteType>
<result>7662</result>
</case>
<case id="type_name" parent="default">
<siteType>GeneratedName</siteType>
<result>jejraquvo</result>
</case>
<case id="type_phrase" parent="default">
<siteType>GeneratedPhrase</siteType>
<result>jejr quv cabsibu tam</result>
</case>
<case id="counter_ceiling" parent="default">
<siteCounter>4294967295</siteCounter>
<result>XambHoqo6[Peni</result>
</case>
</tests>

View File

@@ -86,9 +86,8 @@ public class EmergencyActivity extends Activity {
sitePasswordField.setTypeface( Res.sourceCodePro_Black ); sitePasswordField.setTypeface( Res.sourceCodePro_Black );
sitePasswordField.setPaintFlags( userNameField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG ); sitePasswordField.setPaintFlags( userNameField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
typeField.setAdapter( typeField.setAdapter( new ArrayAdapter<>( this, R.layout.type_item, MPSiteType.forClass( MPSiteTypeClass.Generated ) ) );
new ArrayAdapter<MPElementType>( this, R.layout.type_item, MPElementType.forClass( MPElementTypeClass.Generated ) ) ); typeField.setSelection( MPSiteType.GeneratedLong.ordinal() );
typeField.setSelection( MPElementType.GeneratedLong.ordinal() );
counterField.setMinValue( 1 ); counterField.setMinValue( 1 );
counterField.setMaxValue( Integer.MAX_VALUE ); counterField.setMaxValue( Integer.MAX_VALUE );
@@ -129,7 +128,7 @@ public class EmergencyActivity extends Activity {
SharedPreferences.Editor pref = getPreferences( MODE_PRIVATE ).edit(); SharedPreferences.Editor pref = getPreferences( MODE_PRIVATE ).edit();
pref.putString( "userName", userName ); pref.putString( "userName", userName );
pref.commit(); pref.apply();
if (masterKeyFuture != null) if (masterKeyFuture != null)
masterKeyFuture.cancel( true ); masterKeyFuture.cancel( true );
@@ -170,7 +169,7 @@ public class EmergencyActivity extends Activity {
private void updateSitePassword() { private void updateSitePassword() {
final String siteName = siteNameField.getText().toString(); final String siteName = siteNameField.getText().toString();
final MPElementType type = (MPElementType) typeField.getSelectedItem(); final MPSiteType type = (MPSiteType) typeField.getSelectedItem();
final int counter = counterField.getValue(); final int counter = counterField.getValue();
if (masterKeyFuture == null || siteName.isEmpty() || type == null) { if (masterKeyFuture == null || siteName.isEmpty() || type == null) {
@@ -184,7 +183,7 @@ public class EmergencyActivity extends Activity {
@Override @Override
public void run() { public void run() {
try { try {
final String sitePassword = masterKeyFuture.get().encode( siteName, type, counter ); final String sitePassword = masterKeyFuture.get().encode( siteName, type, counter, MPSiteVariant.Password, null );
runOnUiThread( new Runnable() { runOnUiThread( new Runnable() {
@Override @Override

View File

@@ -18,12 +18,16 @@
package com.lyndir.masterpassword; package com.lyndir.masterpassword;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse; import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import com.google.common.base.Joiner;
import com.google.common.collect.Maps;
import com.google.common.io.LineReader; import com.google.common.io.LineReader;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.ConversionUtils; import com.lyndir.lhunath.opal.system.util.ConversionUtils;
import com.lyndir.lhunath.opal.system.util.StringUtils;
import java.io.*; import java.io.*;
import java.util.Arrays; import java.util.Arrays;
import java.util.Map;
/** /**
@@ -34,7 +38,6 @@ import java.util.Arrays;
public class CLI { public class CLI {
private static final String ENV_USERNAME = "MP_USERNAME"; private static final String ENV_USERNAME = "MP_USERNAME";
private static final String ENV_PASSWORD = "MP_PASSWORD";
private static final String ENV_SITETYPE = "MP_SITETYPE"; private static final String ENV_SITETYPE = "MP_SITETYPE";
private static final String ENV_SITECOUNTER = "MP_SITECOUNTER"; private static final String ENV_SITECOUNTER = "MP_SITECOUNTER";
@@ -42,71 +45,115 @@ public class CLI {
throws IOException { throws IOException {
// Read information from the environment. // Read information from the environment.
String siteName = null; String siteName = null, masterPassword, context = null;
String userName = System.getenv().get( ENV_USERNAME ); String userName = System.getenv( ENV_USERNAME );
String masterPassword = System.getenv().get( ENV_PASSWORD ); String siteTypeName = ifNotNullElse( System.getenv( ENV_SITETYPE ), "" );
String siteTypeName = ifNotNullElse( System.getenv().get( ENV_SITETYPE ), "" ); MPSiteType siteType = siteTypeName.isEmpty()? MPSiteType.GeneratedLong: MPSiteType.forOption( siteTypeName );
MPElementType siteType = siteTypeName.isEmpty()? MPElementType.GeneratedLong: MPElementType.forName( siteTypeName ); MPSiteVariant variant = MPSiteVariant.Password;
String siteCounterName = ifNotNullElse( System.getenv().get( ENV_SITECOUNTER ), "" ); String siteCounterName = ifNotNullElse( System.getenv( ENV_SITECOUNTER ), "" );
int siteCounter = siteCounterName.isEmpty()? 1: Integer.parseInt( siteCounterName ); int siteCounter = siteCounterName.isEmpty()? 1: Integer.parseInt( siteCounterName );
// Parse information from option arguments. // Parse information from option arguments.
boolean typeArg = false, counterArg = false, userNameArg = false; boolean userNameArg = false, typeArg = false, counterArg = false, variantArg = false, contextArg = false;
for (final String arg : Arrays.asList( args )) for (final String arg : Arrays.asList( args ))
if ("-t".equals( arg ) || "--type".equals( arg )) // Full Name
typeArg = true; if ("-u".equals( arg ) || "--username".equals( arg ))
else if (typeArg) {
if ("list".equalsIgnoreCase( arg )) {
System.out.format( "%30s | %s\n", "type", "description" );
for (final MPElementType aType : MPElementType.values())
System.out.format( "%30s | %s\n", aType.getName(), aType.getDescription() );
System.exit( 0 );
}
siteType = MPElementType.forName( arg );
typeArg = false;
} else if ("-c".equals( arg ) || "--counter".equals( arg ))
counterArg = true;
else if (counterArg) {
siteCounter = ConversionUtils.toIntegerNN( arg );
counterArg = false;
} else if ("-u".equals( arg ) || "--username".equals( arg ))
userNameArg = true; userNameArg = true;
else if (userNameArg) { else if (userNameArg) {
userName = arg; userName = arg;
userNameArg = false; userNameArg = false;
} else if ("-h".equals( arg ) || "--help".equals( arg )) { }
// Type
else if ("-t".equals( arg ) || "--type".equals( arg ))
typeArg = true;
else if (typeArg) {
siteType = MPSiteType.forOption( arg );
typeArg = false;
}
// Counter
else if ("-c".equals( arg ) || "--counter".equals( arg ))
counterArg = true;
else if (counterArg) {
siteCounter = ConversionUtils.toIntegerNN( arg );
counterArg = false;
}
// Variant
else if ("-v".equals( arg ) || "--variant".equals( arg ))
variantArg = true;
else if (variantArg) {
variant = MPSiteVariant.forOption( arg );
variantArg = false;
}
// Context
else if ("-C".equals( arg ) || "--context".equals( arg ))
contextArg = true;
else if (contextArg) {
context = arg;
contextArg = false;
}
// Help
else if ("-h".equals( arg ) || "--help".equals( arg )) {
System.out.println(); System.out.println();
System.out.println( "\tMaster Password CLI" ); System.out.format( "Usage: mpw [-u name] [-t type] [-c counter] site\n\n" );
System.out.println( "\t\tLyndir" ); System.out.format( " -u name Specify the full name of the user.\n" );
System.out.format( " Defaults to %s in env.\n\n", ENV_USERNAME );
System.out.format( " -t type Specify the password's template.\n" );
System.out.format( " Defaults to %s in env or 'long' for password, 'name' for login.\n", ENV_SITETYPE );
System.out.println( "[options] [site name]" ); int optionsLength = 0;
Map<String, MPSiteType> typeMap = Maps.newLinkedHashMap();
for (MPSiteType elementType : MPSiteType.values()) {
String options = Joiner.on( ", " ).join( elementType.getOptions() );
typeMap.put( options, elementType );
optionsLength = Math.max( optionsLength, options.length() );
}
for (Map.Entry<String, MPSiteType> entry : typeMap.entrySet()) {
String infoString = strf( " -v %" + optionsLength + "s | ", entry.getKey() );
String infoNewline = "\n" + StringUtils.repeat( " ", infoString.length() - 3 ) + " | ";
infoString += entry.getValue().getDescription().replaceAll( "\n", infoNewline );
System.out.println( infoString );
}
System.out.println(); System.out.println();
System.out.println( "Available options:" );
System.out.println( "\t-t | --type [site password type]" ); System.out.format( " -c counter The value of the counter.\n" );
System.out.format( "\t\tDefault: %s. The password type to use for this site.\n", siteType.getName() ); System.out.format( " Defaults to %s in env or '1'.\n\n", ENV_SITECOUNTER );
System.out.println( "\t\tUse 'list' to see the available types." ); System.out.format( " -v variant The kind of content to generate.\n" );
System.out.format( " Defaults to 'password'.\n" );
optionsLength = 0;
Map<String, MPSiteVariant> variantMap = Maps.newLinkedHashMap();
for (MPSiteVariant elementVariant : MPSiteVariant.values()) {
String options = Joiner.on( ", " ).join( elementVariant.getOptions() );
variantMap.put( options, elementVariant );
optionsLength = Math.max( optionsLength, options.length() );
}
for (Map.Entry<String, MPSiteVariant> entry : variantMap.entrySet()) {
String infoString = strf( " -v %" + optionsLength + "s | ", entry.getKey() );
String infoNewline = "\n" + StringUtils.repeat( " ", infoString.length() - 3 ) + " | ";
infoString += entry.getValue().getDescription().replaceAll( "\n", infoNewline );
System.out.println( infoString );
}
System.out.println(); System.out.println();
System.out.println( "\t-c | --counter [site counter]" );
System.out.format( "\t\tDefault: %d. The counter to use for this site.\n", siteCounter );
System.out.println( "\t\tIncrement the counter if you need a new password." );
System.out.format( " -C context A variant-specific context.\n" );
System.out.format( " Defaults to empty.\n" );
for (Map.Entry<String, MPSiteVariant> entry : variantMap.entrySet()) {
String infoString = strf( " -v %" + optionsLength + "s | ", entry.getKey() );
String infoNewline = "\n" + StringUtils.repeat( " ", infoString.length() - 3 ) + " | ";
infoString += entry.getValue().getContextDescription().replaceAll( "\n", infoNewline );
System.out.println( infoString );
}
System.out.println(); System.out.println();
System.out.println( "\t-u | --username [user's name]" );
System.out.println( "\t\tDefault: asked. The name of the user." );
System.out.println(); System.out.format( " ENVIRONMENT\n\n" );
System.out.println( "Available environment variables:" ); System.out.format( " MP_USERNAME | The full name of the user.\n" );
System.out.format( " MP_SITETYPE | The default password template.\n" );
System.out.format( "\t%s\n", ENV_USERNAME ); System.out.format( " MP_SITECOUNTER | The default counter value.\n\n" );
System.out.println( "\t\tThe name of the user." );
System.out.format( "\t%s\n", ENV_PASSWORD );
System.out.println( "\t\tThe master password of the user." );
System.out.println();
return; return;
} else } else
siteName = arg; siteName = arg;
@@ -126,18 +173,16 @@ public class CLI {
userName = lineReader.readLine(); userName = lineReader.readLine();
} }
if (masterPassword == null) { if (console != null)
if (console != null) masterPassword = new String( console.readPassword( "%s's master password: ", userName ) );
masterPassword = new String( console.readPassword( "%s's master password: ", userName ) );
else { else {
System.err.format( "%s's master password: ", userName ); System.err.format( "%s's master password: ", userName );
masterPassword = lineReader.readLine(); masterPassword = lineReader.readLine();
}
} }
} }
// Encode and write out the site password. // Encode and write out the site password.
System.out.println( new MasterKey( userName, masterPassword ).encode( siteName, siteType, siteCounter ) ); System.out.println( new MasterKey( userName, masterPassword ).encode( siteName, siteType, siteCounter, variant, context ) );
} }
} }

View File

@@ -47,7 +47,7 @@
<configuration> <configuration>
<transformers> <transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.lyndir.masterpassword.GUI</mainClass> <mainClass>com.lyndir.masterpassword.gui.GUI</mainClass>
</transformer> </transformer>
</transformers> </transformers>
<filters> <filters>
@@ -73,10 +73,11 @@
<!-- PROJECT REFERENCES --> <!-- PROJECT REFERENCES -->
<dependency> <dependency>
<groupId>com.lyndir.masterpassword</groupId> <groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword-algorithm</artifactId> <artifactId>masterpassword-model</artifactId>
<version>GIT-SNAPSHOT</version> <version>GIT-SNAPSHOT</version>
</dependency> </dependency>
<!-- EXTERNAL DEPENDENCIES -->
<dependency> <dependency>
<groupId>ch.qos.logback</groupId> <groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId> <artifactId>logback-classic</artifactId>

View File

@@ -1,164 +0,0 @@
package com.lyndir.masterpassword;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.io.CharStreams;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.Iterator;
import java.util.NoSuchElementException;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
/**
* @author lhunath, 2014-06-11
*/
public class ConfigAuthenticationPanel extends AuthenticationPanel implements ItemListener, ActionListener, DocumentListener {
private final JComboBox userField;
private final JLabel masterPasswordLabel;
private final JPasswordField masterPasswordField;
public ConfigAuthenticationPanel(final UnlockFrame unlockFrame) {
// User
super( unlockFrame );
JLabel userLabel = new JLabel( "User:" );
userLabel.setAlignmentX( LEFT_ALIGNMENT );
userLabel.setHorizontalAlignment( SwingConstants.CENTER );
userLabel.setVerticalAlignment( SwingConstants.BOTTOM );
add( userLabel );
userField = new JComboBox<User>( new DefaultComboBoxModel<>( readConfigUsers() ) ) {
@Override
public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
}
};
userField.setAlignmentX( LEFT_ALIGNMENT );
userField.addItemListener( this );
userField.addActionListener( this );
add( userField );
// Master Password
masterPasswordLabel = new JLabel( "Master Password:" );
masterPasswordLabel.setAlignmentX( Component.LEFT_ALIGNMENT );
masterPasswordLabel.setHorizontalAlignment( SwingConstants.CENTER );
masterPasswordLabel.setVerticalAlignment( SwingConstants.BOTTOM );
add( masterPasswordLabel );
masterPasswordField = new JPasswordField() {
@Override
public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
}
};
masterPasswordField.setAlignmentX( Component.LEFT_ALIGNMENT );
masterPasswordField.addActionListener( this );
masterPasswordField.getDocument().addDocumentListener( this );
add( masterPasswordField );
}
@Override
public Component getFocusComponent() {
return masterPasswordField.isVisible()? masterPasswordField: null;
}
@Override
protected void updateUser(boolean repack) {
boolean masterPasswordMissing = userField.getSelectedItem() == null || !((User) userField.getSelectedItem()).hasKey();
if (masterPasswordField.isVisible() != masterPasswordMissing) {
masterPasswordLabel.setVisible( masterPasswordMissing );
masterPasswordField.setVisible( masterPasswordMissing );
repack = true;
}
super.updateUser( repack );
}
@Override
protected User getUser() {
User selectedUser = (User) userField.getSelectedItem();
if (selectedUser.hasKey()) {
return selectedUser;
}
return new User( selectedUser.getUserName(), new String( masterPasswordField.getPassword() ) );
}
public String getHelpText() {
return "Reads users from ~/.mpw, the following syntax applies:\nUser Name:masterpassword"
+ "\n\nEnsure the file's permissions make it only readable by you!";
}
public static boolean hasConfigUsers() {
return new File( System.getProperty( "user.home" ), ".mpw" ).canRead();
}
private User[] readConfigUsers() {
ImmutableList.Builder<User> users = ImmutableList.builder();
File mpwConfig = new File( System.getProperty( "user.home" ), ".mpw" );
try (FileReader mpwReader = new FileReader( mpwConfig )) {
for (String line : CharStreams.readLines( mpwReader )) {
if (line.startsWith( "#" ) || line.startsWith( "//" ) || line.isEmpty()) {
continue;
}
Iterator<String> fields = Splitter.on( ':' ).limit( 2 ).split( line ).iterator();
String userName = fields.next(), masterPassword = fields.next();
users.add( new User( userName, masterPassword ) );
}
return Iterables.toArray( users.build(), User.class );
}
catch (FileNotFoundException e) {
JOptionPane.showMessageDialog( this, "First create the config file at:\n" + mpwConfig.getAbsolutePath() +
"\n\nIt should contain a line for each user of the following format:" +
"\nUser Name:masterpassword" +
"\n\nEnsure the file's permissions make it only readable by you!", //
"Config File Not Found", JOptionPane.WARNING_MESSAGE );
return new User[0];
}
catch (IOException | NoSuchElementException e) {
e.printStackTrace();
String error = ifNotNullElse( e.getLocalizedMessage(), ifNotNullElse( e.getMessage(), e.toString() ) );
JOptionPane.showMessageDialog( this, //
"Problem reading config file:\n" + mpwConfig.getAbsolutePath() //
+ "\n\n" + error, //
"Config File Not Readable", JOptionPane.WARNING_MESSAGE );
return new User[0];
}
}
@Override
public void itemStateChanged(final ItemEvent e) {
updateUser( false );
}
@Override
public void actionPerformed(final ActionEvent e) {
updateUser( false );
unlockFrame.trySignIn( userField );
}
@Override
public void insertUpdate(final DocumentEvent e) {
updateUser( false );
}
@Override
public void removeUpdate(final DocumentEvent e) {
updateUser( false );
}
@Override
public void changedUpdate(final DocumentEvent e) {
updateUser( false );
}
}

View File

@@ -1,49 +0,0 @@
package com.lyndir.masterpassword;
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
/**
* @author lhunath, 2014-06-08
*/
public class User {
private final String userName;
private final String masterPassword;
private MasterKey key;
public User(final String userName, final String masterPassword) {
this.userName = userName;
this.masterPassword = masterPassword;
}
public String getUserName() {
return userName;
}
public boolean hasKey() {
return key != null || (masterPassword != null && !masterPassword.isEmpty());
}
public MasterKey getKey() {
if (key == null) {
if (!hasKey()) {
throw new IllegalStateException( strf( "Master password unknown for user: %s", userName ) );
} else {
key = new MasterKey( userName, masterPassword );
}
}
return key;
}
@Override
public int hashCode() {
return userName.hashCode();
}
@Override
public String toString() {
return userName;
}
}

View File

@@ -1,4 +1,4 @@
package com.lyndir.masterpassword; package com.lyndir.masterpassword.gui;
import com.apple.eawt.*; import com.apple.eawt.*;
import javax.swing.*; import javax.swing.*;

View File

@@ -1,5 +1,6 @@
package com.lyndir.masterpassword; package com.lyndir.masterpassword.gui;
import com.google.common.collect.ImmutableList;
import java.awt.*; import java.awt.*;
import javax.swing.*; import javax.swing.*;
@@ -10,6 +11,7 @@ import javax.swing.*;
public abstract class AuthenticationPanel extends JPanel { public abstract class AuthenticationPanel extends JPanel {
protected final UnlockFrame unlockFrame; protected final UnlockFrame unlockFrame;
protected final JLabel avatarLabel;
public AuthenticationPanel(final UnlockFrame unlockFrame) { public AuthenticationPanel(final UnlockFrame unlockFrame) {
this.unlockFrame = unlockFrame; this.unlockFrame = unlockFrame;
@@ -18,7 +20,7 @@ public abstract class AuthenticationPanel extends JPanel {
// Avatar // Avatar
add( Box.createVerticalGlue() ); add( Box.createVerticalGlue() );
add( new JLabel( Res.avatar(0) ) { add( avatarLabel = new JLabel( Res.avatar( 0 ) ) {
@Override @Override
public Dimension getMaximumSize() { public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, Integer.MAX_VALUE ); return new Dimension( Integer.MAX_VALUE, Integer.MAX_VALUE );
@@ -28,20 +30,22 @@ public abstract class AuthenticationPanel extends JPanel {
} }
protected void updateUser(boolean repack) { protected void updateUser(boolean repack) {
unlockFrame.setUser( getUser() ); unlockFrame.setUser( getSelectedUser() );
validate(); validate();
if (repack) if (repack)
unlockFrame.repack(); unlockFrame.repack();
} }
protected abstract User getUser(); protected abstract User getSelectedUser();
public Component getFocusComponent() { public Component getFocusComponent() {
return null; return null;
} }
public String getHelpText() { public Iterable<? extends JButton> getButtons() {
return null; return ImmutableList.of();
} }
public abstract void reset();
} }

View File

@@ -1,4 +1,4 @@
package com.lyndir.masterpassword; package com.lyndir.masterpassword.gui;
import com.lyndir.lhunath.opal.system.util.ConversionUtils; import com.lyndir.lhunath.opal.system.util.ConversionUtils;

View File

@@ -15,19 +15,17 @@
*/ */
package com.lyndir.masterpassword; package com.lyndir.masterpassword.gui;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import com.google.common.io.*; import com.google.common.io.*;
import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.lhunath.opal.system.MessageDigests;
import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.TypeUtils; import com.lyndir.lhunath.opal.system.util.TypeUtils;
import com.lyndir.masterpassword.MasterKey;
import java.io.*; import java.io.*;
import java.net.URI; import java.net.URI;
import java.net.URL; import java.net.URL;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.List;
import java.util.jar.*; import java.util.jar.*;
import javax.swing.*; import javax.swing.*;
@@ -76,7 +74,7 @@ public class GUI implements UnlockFrame.SignInCallback {
String upstreamRevision = upstream.readFirstLine(); String upstreamRevision = upstream.readFirstLine();
logger.inf( "Local Revision: <%s>", manifestRevision ); logger.inf( "Local Revision: <%s>", manifestRevision );
logger.inf( "Upstream Revision: <%s>", upstreamRevision ); logger.inf( "Upstream Revision: <%s>", upstreamRevision );
if (!manifestRevision.equalsIgnoreCase( upstreamRevision )) { if (manifestRevision != null && !manifestRevision.equalsIgnoreCase( upstreamRevision )) {
logger.wrn( "You are not running the current official version. Please update from:\n" logger.wrn( "You are not running the current official version. Please update from:\n"
+ "http://masterpasswordapp.com/masterpassword-gui.jar" ); + "http://masterpasswordapp.com/masterpassword-gui.jar" );
JOptionPane.showMessageDialog( null, "A new version of Master Password is available.\n" JOptionPane.showMessageDialog( null, "A new version of Master Password is available.\n"
@@ -94,26 +92,28 @@ public class GUI implements UnlockFrame.SignInCallback {
SwingUtilities.invokeLater( new Runnable() { SwingUtilities.invokeLater( new Runnable() {
@Override @Override
public void run() { public void run() {
if (passwordFrame == null) { if (passwordFrame == null)
unlockFrame.setVisible( true ); unlockFrame.setVisible( true );
} else { else
passwordFrame.setVisible( true ); passwordFrame.setVisible( true );
}
} }
} ); } );
} }
@Override @Override
public boolean signedIn(final User user) { public boolean signedIn(final User user) {
if (!user.hasKey()) { if (!user.hasKey())
return false;
try {
user.getKey();
passwordFrame = newPasswordFrame( user );
open();
return true;
} catch (MasterKeyException e) {
JOptionPane.showMessageDialog( null, e.getLocalizedMessage(), "Sign In Failed", JOptionPane.ERROR_MESSAGE );
return false; return false;
} }
user.getKey();
passwordFrame = newPasswordFrame( user );
open();
return true;
} }
protected PasswordFrame newPasswordFrame(final User user) { protected PasswordFrame newPasswordFrame(final User user) {

View File

@@ -1,4 +1,4 @@
package com.lyndir.masterpassword; package com.lyndir.masterpassword.gui;
import java.awt.*; import java.awt.*;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
@@ -11,35 +11,38 @@ import javax.swing.event.DocumentListener;
/** /**
* @author lhunath, 2014-06-11 * @author lhunath, 2014-06-11
*/ */
public class TextAuthenticationPanel extends AuthenticationPanel implements DocumentListener, ActionListener { public class IncognitoAuthenticationPanel extends AuthenticationPanel implements DocumentListener, ActionListener {
private final JTextField userNameField; private final JTextField fullNameField;
private final JPasswordField masterPasswordField; private final JPasswordField masterPasswordField;
public TextAuthenticationPanel(final UnlockFrame unlockFrame) { public IncognitoAuthenticationPanel(final UnlockFrame unlockFrame) {
// User Name // Full Name
super( unlockFrame ); super( unlockFrame );
JLabel userNameLabel = new JLabel( "User Name:" ); JLabel fullNameLabel = new JLabel( "Full Name:" );
userNameLabel.setAlignmentX( Component.LEFT_ALIGNMENT ); fullNameLabel.setFont( Res.exoRegular().deriveFont( 12f ) );
userNameLabel.setHorizontalAlignment( SwingConstants.CENTER ); fullNameLabel.setAlignmentX( LEFT_ALIGNMENT );
userNameLabel.setVerticalAlignment( SwingConstants.BOTTOM ); fullNameLabel.setHorizontalAlignment( SwingConstants.CENTER );
add( userNameLabel ); fullNameLabel.setVerticalAlignment( SwingConstants.BOTTOM );
add( fullNameLabel );
userNameField = new JTextField() { fullNameField = new JTextField() {
@Override @Override
public Dimension getMaximumSize() { public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height ); return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
} }
}; };
userNameField.setAlignmentX( Component.LEFT_ALIGNMENT ); fullNameField.setFont( Res.sourceCodeProRegular().deriveFont( 12f ) );
userNameField.getDocument().addDocumentListener( this ); fullNameField.setAlignmentX( LEFT_ALIGNMENT );
userNameField.addActionListener( this ); fullNameField.getDocument().addDocumentListener( this );
add( userNameField ); fullNameField.addActionListener( this );
add( fullNameField );
// Master Password // Master Password
JLabel masterPasswordLabel = new JLabel( "Master Password:" ); JLabel masterPasswordLabel = new JLabel( "Master Password:" );
masterPasswordLabel.setAlignmentX( Component.LEFT_ALIGNMENT ); masterPasswordLabel.setFont( Res.exoRegular().deriveFont( 12f ) );
masterPasswordLabel.setAlignmentX( LEFT_ALIGNMENT );
masterPasswordLabel.setHorizontalAlignment( SwingConstants.CENTER ); masterPasswordLabel.setHorizontalAlignment( SwingConstants.CENTER );
masterPasswordLabel.setVerticalAlignment( SwingConstants.BOTTOM ); masterPasswordLabel.setVerticalAlignment( SwingConstants.BOTTOM );
add( masterPasswordLabel ); add( masterPasswordLabel );
@@ -50,7 +53,7 @@ public class TextAuthenticationPanel extends AuthenticationPanel implements Docu
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height ); return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
} }
}; };
masterPasswordField.setAlignmentX( Component.LEFT_ALIGNMENT ); masterPasswordField.setAlignmentX( LEFT_ALIGNMENT );
masterPasswordField.addActionListener( this ); masterPasswordField.addActionListener( this );
masterPasswordField.getDocument().addDocumentListener( this ); masterPasswordField.getDocument().addDocumentListener( this );
add( masterPasswordField ); add( masterPasswordField );
@@ -58,12 +61,17 @@ public class TextAuthenticationPanel extends AuthenticationPanel implements Docu
@Override @Override
public Component getFocusComponent() { public Component getFocusComponent() {
return userNameField; return fullNameField;
} }
@Override @Override
protected User getUser() { public void reset() {
return new User( userNameField.getText(), new String( masterPasswordField.getPassword() ) ); masterPasswordField.setText( "" );
}
@Override
protected User getSelectedUser() {
return new IncognitoUser( fullNameField.getText(), new String( masterPasswordField.getPassword() ) );
} }
@Override @Override
@@ -84,6 +92,6 @@ public class TextAuthenticationPanel extends AuthenticationPanel implements Docu
@Override @Override
public void actionPerformed(final ActionEvent e) { public void actionPerformed(final ActionEvent e) {
updateUser( false ); updateUser( false );
unlockFrame.trySignIn( userNameField, masterPasswordField ); unlockFrame.trySignIn( fullNameField, masterPasswordField );
} }
} }

View File

@@ -0,0 +1,44 @@
package com.lyndir.masterpassword.gui;
import com.lyndir.masterpassword.MPSiteType;
/**
* @author lhunath, 14-12-16
*/
public class IncognitoSite extends Site {
private String siteName;
private MPSiteType siteType;
private int siteCounter;
public IncognitoSite(final String siteName, final MPSiteType siteType, final int siteCounter) {
this.siteName = siteName;
this.siteType = siteType;
this.siteCounter = siteCounter;
}
public String getSiteName() {
return siteName;
}
public void setSiteName(final String siteName) {
this.siteName = siteName;
}
public MPSiteType getSiteType() {
return siteType;
}
public void setSiteType(final MPSiteType siteType) {
this.siteType = siteType;
}
public int getSiteCounter() {
return siteCounter;
}
public void setSiteCounter(final int siteCounter) {
this.siteCounter = siteCounter;
}
}

View File

@@ -0,0 +1,36 @@
package com.lyndir.masterpassword.gui;
import com.google.common.collect.ImmutableList;
/**
* @author lhunath, 2014-06-08
*/
public class IncognitoUser extends User {
private final String fullName;
private final String masterPassword;
public IncognitoUser(final String fullName, final String masterPassword) {
this.fullName = fullName;
this.masterPassword = masterPassword;
}
public String getFullName() {
return fullName;
}
@Override
protected String getMasterPassword() {
return masterPassword;
}
@Override
public Iterable<Site> findSitesByName(final String siteName) {
return ImmutableList.of();
}
@Override
public void addSite(final Site site) {
}
}

View File

@@ -0,0 +1,11 @@
package com.lyndir.masterpassword.gui;
/**
* @author lhunath, 14-12-17
*/
public class MasterKeyException extends Exception {
public MasterKeyException(final String message) {
super( message );
}
}

View File

@@ -0,0 +1,187 @@
package com.lyndir.masterpassword.gui;
import com.google.common.base.Function;
import com.google.common.collect.*;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.masterpassword.model.MPUser;
import com.lyndir.masterpassword.model.MPUserFileManager;
import java.awt.*;
import java.awt.event.*;
import javax.annotation.Nullable;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
/**
* @author lhunath, 2014-06-11
*/
public class ModelAuthenticationPanel extends AuthenticationPanel implements ItemListener, ActionListener, DocumentListener {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( ModelAuthenticationPanel.class );
private final JComboBox<ModelUser> userField;
private final JLabel masterPasswordLabel;
private final JPasswordField masterPasswordField;
public ModelAuthenticationPanel(final UnlockFrame unlockFrame) {
super( unlockFrame );
// Avatar
avatarLabel.addMouseListener( new MouseAdapter() {
@Override
public void mouseClicked(final MouseEvent e) {
ModelUser selectedUser = getSelectedUser();
if (selectedUser != null) {
selectedUser.setAvatar( selectedUser.getAvatar() + 1 );
updateUser( false );
}
}
} );
// User
JLabel userLabel = new JLabel( "User:" );
userLabel.setFont( Res.exoRegular().deriveFont( 12f ) );
userLabel.setAlignmentX( LEFT_ALIGNMENT );
userLabel.setHorizontalAlignment( SwingConstants.CENTER );
userLabel.setVerticalAlignment( SwingConstants.BOTTOM );
add( userLabel );
userField = new JComboBox<ModelUser>( new DefaultComboBoxModel<>( readConfigUsers() ) ) {
@Override
public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
}
};
userField.setFont( Res.sourceCodeProRegular().deriveFont( 12f ) );
userField.setAlignmentX( LEFT_ALIGNMENT );
userField.addItemListener( this );
userField.addActionListener( this );
add( userField );
// Master Password
masterPasswordLabel = new JLabel( "Master Password:" );
masterPasswordLabel.setFont( Res.exoRegular().deriveFont( 12f ) );
masterPasswordLabel.setAlignmentX( LEFT_ALIGNMENT );
masterPasswordLabel.setHorizontalAlignment( SwingConstants.CENTER );
masterPasswordLabel.setVerticalAlignment( SwingConstants.BOTTOM );
add( masterPasswordLabel );
masterPasswordField = new JPasswordField() {
@Override
public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
}
};
masterPasswordField.setAlignmentX( LEFT_ALIGNMENT );
masterPasswordField.addActionListener( this );
masterPasswordField.getDocument().addDocumentListener( this );
add( masterPasswordField );
}
@Override
public Component getFocusComponent() {
return masterPasswordField.isVisible()? masterPasswordField: null;
}
@Override
protected void updateUser(boolean repack) {
ModelUser selectedUser = getSelectedUser();
if (selectedUser != null) {
avatarLabel.setIcon( Res.avatar( selectedUser.getAvatar() ) );
boolean showPasswordField = !selectedUser.keySaved();
if (masterPasswordField.isVisible() != showPasswordField) {
masterPasswordLabel.setVisible( showPasswordField );
masterPasswordField.setVisible( showPasswordField );
repack = true;
}
}
super.updateUser( repack );
}
@Override
protected ModelUser getSelectedUser() {
int selectedIndex = userField.getSelectedIndex();
if (selectedIndex < 0)
return null;
ModelUser selectedUser = userField.getModel().getElementAt( selectedIndex );
if (selectedUser != null)
selectedUser.setMasterPassword( new String( masterPasswordField.getPassword() ) );
return selectedUser;
}
@Override
public Iterable<? extends JButton> getButtons() {
return ImmutableList.of( new JButton( Res.iconAdd() ) {
{
addActionListener( new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
String fullName = JOptionPane.showInputDialog( ModelAuthenticationPanel.this, //
"Enter your full name, ensuring it is correctly spelled and capitalized:",
"New User", JOptionPane.QUESTION_MESSAGE );
MPUserFileManager.get().addUser( new MPUser( fullName ) );
userField.setModel( new DefaultComboBoxModel<>( readConfigUsers() ) );
updateUser( true );
}
} );
}
}, new JButton( Res.iconQuestion() ) {
{
addActionListener( new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
JOptionPane.showMessageDialog( ModelAuthenticationPanel.this, //
"Reads users and sites from the directory at ~/.mpw.", //
"Help", JOptionPane.INFORMATION_MESSAGE );
}
} );
}
} );
}
@Override
public void reset() {
masterPasswordField.setText( "" );
}
private ModelUser[] readConfigUsers() {
return FluentIterable.from( MPUserFileManager.get().getUsers() ).transform( new Function<MPUser, ModelUser>() {
@Nullable
@Override
public ModelUser apply(final MPUser model) {
return new ModelUser( model );
}
} ).toArray( ModelUser.class );
}
@Override
public void itemStateChanged(final ItemEvent e) {
updateUser( false );
}
@Override
public void actionPerformed(final ActionEvent e) {
updateUser( false );
unlockFrame.trySignIn( userField );
}
@Override
public void insertUpdate(final DocumentEvent e) {
updateUser( false );
}
@Override
public void removeUpdate(final DocumentEvent e) {
updateUser( false );
}
@Override
public void changedUpdate(final DocumentEvent e) {
updateUser( false );
}
}

View File

@@ -0,0 +1,51 @@
package com.lyndir.masterpassword.gui;
import com.lyndir.masterpassword.MPSiteType;
import com.lyndir.masterpassword.model.*;
/**
* @author lhunath, 14-12-16
*/
public class ModelSite extends Site {
private final MPSite model;
public ModelSite(final MPSiteResult result) {
this.model = result.getSite();
}
public String getSiteName() {
return model.getSiteName();
}
@Override
public void setSiteName(final String siteName) {
model.setSiteName( siteName );
MPUserFileManager.get().save();
}
public MPSiteType getSiteType() {
return model.getSiteType();
}
@Override
public void setSiteType(final MPSiteType siteType) {
if (siteType != getSiteType()) {
model.setSiteType( siteType );
MPUserFileManager.get().save();
}
}
public int getSiteCounter() {
return model.getSiteCounter();
}
@Override
public void setSiteCounter(final int siteCounter) {
if (siteCounter != getSiteCounter()) {
model.setSiteCounter( siteCounter );
MPUserFileManager.get().save();
}
}
}

View File

@@ -0,0 +1,86 @@
package com.lyndir.masterpassword.gui;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import com.google.common.base.Function;
import com.google.common.collect.FluentIterable;
import com.lyndir.masterpassword.MasterKey;
import com.lyndir.masterpassword.model.*;
import javax.annotation.Nullable;
import org.jetbrains.annotations.NotNull;
/**
* @author lhunath, 14-12-08
*/
public class ModelUser extends User {
private final MPUser model;
private String masterPassword;
public ModelUser(MPUser model) {
this.model = model;
}
public MPUser getModel() {
return model;
}
@Override
public String getFullName() {
return model.getFullName();
}
@Override
protected String getMasterPassword() {
return masterPassword;
}
@Override
public int getAvatar() {
return model.getAvatar();
}
public void setAvatar(final int avatar) {
model.setAvatar( avatar % Res.avatars() );
MPUserFileManager.get().save();
}
public void setMasterPassword(final String masterPassword) {
this.masterPassword = masterPassword;
}
@NotNull
@Override
public MasterKey getKey() throws MasterKeyException {
MasterKey key = super.getKey();
if (!model.hasKeyID()) {
model.setKeyID( key.getKeyID() );
MPUserFileManager.get().save();
} else if (!model.hasKeyID( key.getKeyID() ))
throw new MasterKeyException( strf( "Incorrect master password for user: %s", getFullName() ) );
return key;
}
@Override
public Iterable<Site> findSitesByName(final String query) {
return FluentIterable.from( model.findSitesByName( query ) ).transform( new Function<MPSiteResult, Site>() {
@Nullable
@Override
public Site apply(final MPSiteResult result) {
return new ModelSite( result );
}
} );
}
@Override
public void addSite(final Site site) {
model.addSite( new MPSite( site.getSiteName(), site.getSiteType(), site.getSiteCounter() ) );
MPUserFileManager.get().save();
}
public boolean keySaved() {
return false;
}
}

View File

@@ -1,12 +1,17 @@
package com.lyndir.masterpassword; package com.lyndir.masterpassword.gui;
import static com.lyndir.lhunath.opal.system.util.StringUtils.*; import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.*;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.masterpassword.*;
import com.lyndir.masterpassword.util.Components; import com.lyndir.masterpassword.util.Components;
import java.awt.*; import java.awt.*;
import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.StringSelection;
import java.awt.event.*; import java.awt.event.*;
import java.util.concurrent.Callable;
import javax.annotation.Nonnull;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.*; import javax.swing.border.*;
import javax.swing.event.*; import javax.swing.event.*;
@@ -17,12 +22,15 @@ import javax.swing.event.*;
*/ */
public class PasswordFrame extends JFrame implements DocumentListener { public class PasswordFrame extends JFrame implements DocumentListener {
private final User user; private final User user;
private final JTextField siteNameField; private final JTextField siteNameField;
private final JComboBox<MPElementType> siteTypeField; private final JButton siteAddButton;
private final JSpinner siteCounterField; private final JComboBox<MPSiteType> siteTypeField;
private final JTextField passwordField; private final JSpinner siteCounterField;
private final JLabel tipLabel; private final JTextField passwordField;
private final JLabel tipLabel;
private boolean updatingUI;
private Site currentSite;
public PasswordFrame(User user) public PasswordFrame(User user)
throws HeadlessException { throws HeadlessException {
@@ -38,7 +46,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
} ); } );
// User // User
add( label = new JLabel( strf( "Generating passwords for: %s", user.getUserName() ) ), BorderLayout.NORTH ); add( label = new JLabel( strf( "Generating passwords for: %s", user.getFullName() ) ), BorderLayout.NORTH );
label.setFont( Res.exoRegular().deriveFont( 12f ) ); label.setFont( Res.exoRegular().deriveFont( 12f ) );
label.setAlignmentX( LEFT_ALIGNMENT ); label.setAlignmentX( LEFT_ALIGNMENT );
@@ -53,21 +61,40 @@ public class PasswordFrame extends JFrame implements DocumentListener {
label.setFont( Res.exoRegular().deriveFont( 12f ) ); label.setFont( Res.exoRegular().deriveFont( 12f ) );
label.setAlignmentX( LEFT_ALIGNMENT ); label.setAlignmentX( LEFT_ALIGNMENT );
sitePanel.add( siteNameField = new JTextField() { JComponent siteControls = Components.boxLayout( BoxLayout.LINE_AXIS, //
siteNameField = new JTextField() {
@Override
public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
}
}, siteAddButton = new JButton( "Add Site" ) {
@Override
public Dimension getMaximumSize() {
return new Dimension( 20, getPreferredSize().height );
}
} );
siteAddButton.setVisible( false );
siteAddButton.setFont( Res.exoRegular().deriveFont( 12f ) );
siteAddButton.setAlignmentX( RIGHT_ALIGNMENT );
siteAddButton.setAlignmentY( CENTER_ALIGNMENT );
siteAddButton.addActionListener( new ActionListener() {
@Override @Override
public Dimension getMaximumSize() { public void actionPerformed(final ActionEvent e) {
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height ); PasswordFrame.this.user.addSite( currentSite );
siteAddButton.setVisible( false );
} }
} ); } );
siteNameField.setFont( Res.exoRegular().deriveFont( 12f ) ); siteControls.setAlignmentX( LEFT_ALIGNMENT );
sitePanel.add( siteControls );
siteNameField.setFont( Res.sourceCodeProRegular().deriveFont( 12f ) );
siteNameField.setAlignmentX( LEFT_ALIGNMENT ); siteNameField.setAlignmentX( LEFT_ALIGNMENT );
siteNameField.getDocument().addDocumentListener( this ); siteNameField.getDocument().addDocumentListener( this );
siteNameField.addActionListener( new ActionListener() { siteNameField.addActionListener( new ActionListener() {
@Override @Override
public void actionPerformed(final ActionEvent e) { public void actionPerformed(final ActionEvent e) {
updatePassword( new PasswordCallback() { Futures.addCallback( updatePassword(), new FutureCallback<String>() {
@Override @Override
public void passwordGenerated(final String siteName, final String sitePassword) { public void onSuccess(final String sitePassword) {
StringSelection clipboardContents = new StringSelection( sitePassword ); StringSelection clipboardContents = new StringSelection( sitePassword );
Toolkit.getDefaultToolkit().getSystemClipboard().setContents( clipboardContents, null ); Toolkit.getDefaultToolkit().getSystemClipboard().setContents( clipboardContents, null );
@@ -84,12 +111,16 @@ public class PasswordFrame extends JFrame implements DocumentListener {
} }
} ); } );
} }
@Override
public void onFailure(final Throwable t) {
}
} ); } );
} }
} ); } );
// Site Type & Counter // Site Type & Counter
MPElementType[] types = Iterables.toArray( MPElementType.forClass( MPElementTypeClass.Generated ), MPElementType.class ); MPSiteType[] types = Iterables.toArray( MPSiteType.forClass( MPSiteTypeClass.Generated ), MPSiteType.class );
JComponent siteSettings = Components.boxLayout( BoxLayout.LINE_AXIS, // JComponent siteSettings = Components.boxLayout( BoxLayout.LINE_AXIS, //
siteTypeField = new JComboBox<>( types ), // siteTypeField = new JComboBox<>( types ), //
siteCounterField = new JSpinner( siteCounterField = new JSpinner(
@@ -101,24 +132,24 @@ public class PasswordFrame extends JFrame implements DocumentListener {
} ); } );
siteSettings.setAlignmentX( LEFT_ALIGNMENT ); siteSettings.setAlignmentX( LEFT_ALIGNMENT );
sitePanel.add( siteSettings ); sitePanel.add( siteSettings );
siteTypeField.setFont( Res.exoRegular().deriveFont( 12f ) ); siteTypeField.setFont( Res.sourceCodeProRegular().deriveFont( 12f ) );
siteTypeField.setAlignmentX( LEFT_ALIGNMENT ); siteTypeField.setAlignmentX( LEFT_ALIGNMENT );
siteTypeField.setAlignmentY( CENTER_ALIGNMENT ); siteTypeField.setAlignmentY( CENTER_ALIGNMENT );
siteTypeField.setSelectedItem( MPElementType.GeneratedLong ); siteTypeField.setSelectedItem( MPSiteType.GeneratedLong );
siteTypeField.addItemListener( new ItemListener() { siteTypeField.addItemListener( new ItemListener() {
@Override @Override
public void itemStateChanged(final ItemEvent e) { public void itemStateChanged(final ItemEvent e) {
updatePassword( null ); updatePassword();
} }
} ); } );
siteCounterField.setFont( Res.exoRegular().deriveFont( 12f ) ); siteCounterField.setFont( Res.sourceCodeProRegular().deriveFont( 12f ) );
siteCounterField.setAlignmentX( RIGHT_ALIGNMENT ); siteCounterField.setAlignmentX( RIGHT_ALIGNMENT );
siteCounterField.setAlignmentY( CENTER_ALIGNMENT ); siteCounterField.setAlignmentY( CENTER_ALIGNMENT );
siteCounterField.addChangeListener( new ChangeListener() { siteCounterField.addChangeListener( new ChangeListener() {
@Override @Override
public void stateChanged(final ChangeEvent e) { public void stateChanged(final ChangeEvent e) {
updatePassword( null ); updatePassword();
} }
} ); } );
@@ -131,7 +162,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
// Tip // Tip
tipLabel = new JLabel( " ", JLabel.CENTER ); tipLabel = new JLabel( " ", JLabel.CENTER );
tipLabel.setFont( Res.exoThin().deriveFont( 9f ) ); tipLabel.setFont( Res.exoRegular().deriveFont( 9f ) );
tipLabel.setAlignmentX( Component.CENTER_ALIGNMENT ); tipLabel.setAlignmentX( Component.CENTER_ALIGNMENT );
add( Components.boxLayout( BoxLayout.PAGE_AXIS, passwordField, tipLabel ), BorderLayout.SOUTH ); add( Components.boxLayout( BoxLayout.PAGE_AXIS, passwordField, tipLabel ), BorderLayout.SOUTH );
@@ -145,55 +176,76 @@ public class PasswordFrame extends JFrame implements DocumentListener {
setLocationRelativeTo( null ); setLocationRelativeTo( null );
} }
private void updatePassword(final PasswordCallback callback) { @Nonnull
final MPElementType siteType = (MPElementType) siteTypeField.getSelectedItem(); private ListenableFuture<String> updatePassword() {
final String siteName = siteNameField.getText();
final int siteCounter = (Integer) siteCounterField.getValue();
if (siteType.getTypeClass() != MPElementTypeClass.Generated || siteName == null || siteName.isEmpty() || !user.hasKey()) { final String siteNameQuery = siteNameField.getText();
if (updatingUI)
return Futures.immediateCancelledFuture();
if (siteNameQuery == null || siteNameQuery.isEmpty() || !user.hasKey()) {
passwordField.setText( null ); passwordField.setText( null );
tipLabel.setText( null ); tipLabel.setText( null );
return; return Futures.immediateCancelledFuture();
} }
Res.execute( new Runnable() { MPSiteType siteType = siteTypeField.getModel().getElementAt( siteTypeField.getSelectedIndex() );
@Override final int siteCounter = (Integer) siteCounterField.getValue();
public void run() { final Site site = currentSite != null && currentSite.getSiteName().equals( siteNameQuery )? currentSite
final String sitePassword = user.getKey().encode( siteName, siteType, siteCounter ); : Iterables.getFirst( user.findSitesByName( siteNameQuery ), new IncognitoSite( siteNameQuery, siteType, siteCounter ) );
if (callback != null) assert site != null;
callback.passwordGenerated( siteName, sitePassword ); if (site == currentSite) {
site.setSiteType( siteType );
site.setSiteCounter( siteCounter );
}
ListenableFuture<String> passwordFuture = Res.execute( new Callable<String>() {
@Override
public String call()
throws Exception {
return user.getKey().encode( site.getSiteName(), site.getSiteType(), site.getSiteCounter(), MPSiteVariant.Password, null );
}
} );
Futures.addCallback( passwordFuture, new FutureCallback<String>() {
@Override
public void onSuccess(final String sitePassword) {
SwingUtilities.invokeLater( new Runnable() { SwingUtilities.invokeLater( new Runnable() {
@Override @Override
public void run() { public void run() {
if (!siteName.equals( siteNameField.getText() )) updatingUI = true;
return; currentSite = site;
siteAddButton.setVisible( user instanceof ModelUser && !(currentSite instanceof ModelSite) );
siteTypeField.setSelectedItem( currentSite.getSiteType() );
siteCounterField.setValue( currentSite.getSiteCounter() );
siteNameField.setText( currentSite.getSiteName() );
if (siteNameField.getText().startsWith( siteNameQuery ))
siteNameField.select( siteNameQuery.length(), siteNameField.getText().length() );
passwordField.setText( sitePassword ); passwordField.setText( sitePassword );
tipLabel.setText( "Press [Enter] to copy the password." ); tipLabel.setText( "Press [Enter] to copy the password. Then paste it into the password field." );
updatingUI = false;
} }
} ); } );
} }
@Override
public void onFailure(final Throwable t) {
}
} ); } );
return passwordFuture;
} }
@Override @Override
public void insertUpdate(final DocumentEvent e) { public void insertUpdate(final DocumentEvent e) {
updatePassword( null ); updatePassword();
} }
@Override @Override
public void removeUpdate(final DocumentEvent e) { public void removeUpdate(final DocumentEvent e) {
updatePassword( null );
} }
@Override @Override
public void changedUpdate(final DocumentEvent e) { public void changedUpdate(final DocumentEvent e) {
updatePassword( null ); updatePassword();
}
interface PasswordCallback {
void passwordGenerated(String siteName, String sitePassword);
} }
} }

View File

@@ -1,17 +1,17 @@
package com.lyndir.masterpassword; package com.lyndir.masterpassword.gui;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse; import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse;
import static com.lyndir.lhunath.opal.system.util.StringUtils.*; import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
import com.google.common.base.Throwables; import com.google.common.base.Throwables;
import com.google.common.io.Resources; import com.google.common.io.Resources;
import com.google.common.util.concurrent.*;
import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.logging.Logger;
import java.awt.*; import java.awt.*;
import java.awt.image.ImageObserver; import java.awt.image.ImageObserver;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.util.concurrent.ExecutorService; import java.util.concurrent.*;
import java.util.concurrent.Executors;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import javax.swing.*; import javax.swing.*;
@@ -25,14 +25,15 @@ public abstract class Res {
private static final ExecutorService executor = Executors.newSingleThreadExecutor(); private static final ExecutorService executor = Executors.newSingleThreadExecutor();
private static final Logger logger = Logger.get( Res.class ); private static final Logger logger = Logger.get( Res.class );
private static Font sourceCodeProRegular;
private static Font sourceCodeProBlack; private static Font sourceCodeProBlack;
private static Font exoBold; private static Font exoBold;
private static Font exoExtraBold; private static Font exoExtraBold;
private static Font exoRegular; private static Font exoRegular;
private static Font exoThin; private static Font exoThin;
public static void execute(final Runnable job) { public static Future<?> execute(final Runnable job) {
executor.submit( new Runnable() { return executor.submit( new Runnable() {
@Override @Override
public void run() { public void run() {
try { try {
@@ -45,20 +46,52 @@ public abstract class Res {
} ); } );
} }
public static <V> ListenableFuture<V> execute(final Callable<V> job) {
return JdkFutureAdapters.listenInPoolThread( executor.submit( new Callable<V>() {
@Override
public V call()
throws Exception {
try {
return job.call();
}
catch (Throwable t) {
logger.err( t, "Unexpected: %s", t.getLocalizedMessage() );
throw t;
}
}
} ), executor );
}
public static Icon iconAdd() {
return new RetinaIcon( Resources.getResource( "media/icon_add@2x.png" ) );
}
public static Icon iconQuestion() { public static Icon iconQuestion() {
return new RetinaIcon( Resources.getResource( "media/icon_question@2x.png" ) ); return new RetinaIcon( Resources.getResource( "media/icon_question@2x.png" ) );
} }
public static Icon avatar(final int index) { public static Icon avatar(final int index) {
return new RetinaIcon( Resources.getResource( strf( "media/avatar-%d@2x.png", index ) ) ); return new RetinaIcon( Resources.getResource( strf( "media/avatar-%d@2x.png", index % avatars() ) ) );
}
public static int avatars() {
return 19;
}
public static Font sourceCodeProRegular() {
try {
return sourceCodeProRegular != null? sourceCodeProRegular: (sourceCodeProRegular =
Font.createFont( Font.TRUETYPE_FONT, Resources.getResource( "fonts/SourceCodePro-Regular.otf" ).openStream() ));
}
catch (FontFormatException | IOException e) {
throw Throwables.propagate( e );
}
} }
public static Font sourceCodeProBlack() { public static Font sourceCodeProBlack() {
try { try {
URL resource = Resources.getResource( "fonts/SourceCodePro-Bold.otf" ); return sourceCodeProBlack != null? sourceCodeProBlack: (sourceCodeProBlack =
Font font = Font.createFont( Font.TRUETYPE_FONT, resource.openStream() ); Font.createFont( Font.TRUETYPE_FONT, Resources.getResource( "fonts/SourceCodePro-Bold.otf" ).openStream() ));
return sourceCodeProBlack != null? sourceCodeProBlack: //
(sourceCodeProBlack = font);
} }
catch (FontFormatException | IOException e) { catch (FontFormatException | IOException e) {
throw Throwables.propagate( e ); throw Throwables.propagate( e );
@@ -67,10 +100,8 @@ public abstract class Res {
public static Font exoBold() { public static Font exoBold() {
try { try {
URL resource = Resources.getResource( "fonts/Exo2.0-Bold.otf" ); return exoBold != null? exoBold: (exoBold =
Font font = Font.createFont( Font.TRUETYPE_FONT, resource.openStream() ); Font.createFont( Font.TRUETYPE_FONT, Resources.getResource( "fonts/Exo2.0-Bold.otf" ).openStream() ));
return exoBold != null? exoBold: //
(exoBold = font);
} }
catch (FontFormatException | IOException e) { catch (FontFormatException | IOException e) {
throw Throwables.propagate( e ); throw Throwables.propagate( e );
@@ -79,10 +110,8 @@ public abstract class Res {
public static Font exoExtraBold() { public static Font exoExtraBold() {
try { try {
URL resource = Resources.getResource( "fonts/Exo2.0-ExtraBold.otf" ); return exoExtraBold != null? exoExtraBold: (exoExtraBold
Font font = Font.createFont( Font.TRUETYPE_FONT, resource.openStream() ); = Font.createFont( Font.TRUETYPE_FONT, Resources.getResource( "fonts/Exo2.0-ExtraBold.otf" ).openStream() ));
return exoExtraBold != null? exoExtraBold: //
(exoExtraBold = font);
} }
catch (FontFormatException | IOException e) { catch (FontFormatException | IOException e) {
throw Throwables.propagate( e ); throw Throwables.propagate( e );
@@ -91,10 +120,8 @@ public abstract class Res {
public static Font exoRegular() { public static Font exoRegular() {
try { try {
URL resource = Resources.getResource( "fonts/Exo2.0-Regular.otf" ); return exoRegular != null? exoRegular: (exoRegular =
Font font = Font.createFont( Font.TRUETYPE_FONT, resource.openStream() ); Font.createFont( Font.TRUETYPE_FONT, Resources.getResource( "fonts/Exo2.0-Regular.otf" ).openStream() ));
return exoRegular != null? exoRegular: //
(exoRegular = font);
} }
catch (FontFormatException | IOException e) { catch (FontFormatException | IOException e) {
throw Throwables.propagate( e ); throw Throwables.propagate( e );
@@ -103,10 +130,8 @@ public abstract class Res {
public static Font exoThin() { public static Font exoThin() {
try { try {
URL resource = Resources.getResource( "fonts/Exo2.0-Thin.otf" ); return exoThin != null? exoThin: (exoThin =
Font font = Font.createFont( Font.TRUETYPE_FONT, resource.openStream() ); Font.createFont( Font.TRUETYPE_FONT, Resources.getResource( "fonts/Exo2.0-Thin.otf" ).openStream() ));
return exoThin != null? exoThin: //
(exoThin = font);
} }
catch (FontFormatException | IOException e) { catch (FontFormatException | IOException e) {
throw Throwables.propagate( e ); throw Throwables.propagate( e );
@@ -115,7 +140,7 @@ public abstract class Res {
private static final class RetinaIcon extends ImageIcon { private static final class RetinaIcon extends ImageIcon {
private static final Pattern scalePattern = Pattern.compile(".*@(\\d+)x.[^.]+$"); private static final Pattern scalePattern = Pattern.compile( ".*@(\\d+)x.[^.]+$" );
private final float scale; private final float scale;

View File

@@ -0,0 +1,29 @@
package com.lyndir.masterpassword.gui;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import com.lyndir.masterpassword.MPSiteType;
/**
* @author lhunath, 14-12-16
*/
public abstract class Site {
public abstract String getSiteName();
public abstract void setSiteName(final String siteName);
public abstract MPSiteType getSiteType();
public abstract void setSiteType(final MPSiteType siteType);
public abstract int getSiteCounter();
public abstract void setSiteCounter(final int siteCounter);
@Override
public String toString() {
return strf( "{%s: %s}", getClass().getSimpleName(), getSiteName() );
}
}

View File

@@ -1,4 +1,4 @@
package com.lyndir.masterpassword; package com.lyndir.masterpassword.gui;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*; import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
@@ -14,12 +14,13 @@ import javax.swing.border.*;
*/ */
public class UnlockFrame extends JFrame { public class UnlockFrame extends JFrame {
private final SignInCallback signInCallback; private final SignInCallback signInCallback;
private final JPanel root; private final JPanel root;
private final JButton signInButton; private final JButton signInButton;
private final JPanel authenticationContainer; private final JPanel authenticationContainer;
private boolean useConfig; private AuthenticationPanel authenticationPanel;
public User user; private boolean incognito;
public User user;
public UnlockFrame(final SignInCallback signInCallback) public UnlockFrame(final SignInCallback signInCallback)
throws HeadlessException { throws HeadlessException {
@@ -38,6 +39,7 @@ public class UnlockFrame extends JFrame {
// Sign In // Sign In
root.add( Components.boxLayout( BoxLayout.LINE_AXIS, Box.createGlue(), signInButton = new JButton( "Sign In" ), Box.createGlue() ), root.add( Components.boxLayout( BoxLayout.LINE_AXIS, Box.createGlue(), signInButton = new JButton( "Sign In" ), Box.createGlue() ),
BorderLayout.SOUTH ); BorderLayout.SOUTH );
signInButton.setFont( Res.exoRegular().deriveFont( 12f ) );
signInButton.setAlignmentX( LEFT_ALIGNMENT ); signInButton.setAlignmentX( LEFT_ALIGNMENT );
signInButton.addActionListener( new AbstractAction() { signInButton.addActionListener( new AbstractAction() {
@Override @Override
@@ -46,7 +48,6 @@ public class UnlockFrame extends JFrame {
} }
} ); } );
useConfig = ConfigAuthenticationPanel.hasConfigUsers();
createAuthenticationPanel(); createAuthenticationPanel();
setLocationByPlatform( true ); setLocationByPlatform( true );
@@ -64,22 +65,22 @@ public class UnlockFrame extends JFrame {
private void createAuthenticationPanel() { private void createAuthenticationPanel() {
authenticationContainer.removeAll(); authenticationContainer.removeAll();
final AuthenticationPanel authenticationPanel; if (incognito) {
if (useConfig) { authenticationPanel = new IncognitoAuthenticationPanel( this );
authenticationPanel = new ConfigAuthenticationPanel( this );
} else { } else {
authenticationPanel = new TextAuthenticationPanel( this ); authenticationPanel = new ModelAuthenticationPanel( this );
} }
authenticationPanel.updateUser( false ); authenticationPanel.updateUser( false );
authenticationContainer.add( authenticationPanel, BorderLayout.CENTER ); authenticationContainer.add( authenticationPanel, BorderLayout.CENTER );
final JCheckBox typeCheckBox = new JCheckBox( "Use Config File" ); final JCheckBox incognitoCheckBox = new JCheckBox( "Incognito" );
typeCheckBox.setAlignmentX( LEFT_ALIGNMENT ); incognitoCheckBox.setFont( Res.exoRegular().deriveFont( 12f ) );
typeCheckBox.setSelected( useConfig ); incognitoCheckBox.setAlignmentX( LEFT_ALIGNMENT );
typeCheckBox.addItemListener( new ItemListener() { incognitoCheckBox.setSelected( incognito );
incognitoCheckBox.addItemListener( new ItemListener() {
@Override @Override
public void itemStateChanged(final ItemEvent e) { public void itemStateChanged(final ItemEvent e) {
useConfig = typeCheckBox.isSelected(); incognito = incognitoCheckBox.isSelected();
SwingUtilities.invokeLater( new Runnable() { SwingUtilities.invokeLater( new Runnable() {
@Override @Override
public void run() { public void run() {
@@ -89,24 +90,15 @@ public class UnlockFrame extends JFrame {
} }
} ); } );
JButton typeHelp = new JButton( Res.iconQuestion() ); JComponent toolsPanel = Components.boxLayout( BoxLayout.LINE_AXIS, incognitoCheckBox, Box.createGlue() );
typeHelp.setMargin( new Insets( 0, 0, 0, 0 ) ); toolsPanel.setAlignmentX( Component.LEFT_ALIGNMENT );
typeHelp.setBackground( Color.red ); authenticationContainer.add( toolsPanel );
typeHelp.setAlignmentX( RIGHT_ALIGNMENT ); for (JButton button : authenticationPanel.getButtons()) {
typeHelp.setBorder( null ); button.setMargin( new Insets( 0, 0, 0, 0 ) );
typeHelp.addActionListener( new ActionListener() { button.setAlignmentX( RIGHT_ALIGNMENT );
@Override button.setBorder( null );
public void actionPerformed(final ActionEvent e) { toolsPanel.add( button );
JOptionPane.showMessageDialog( UnlockFrame.this, authenticationPanel.getHelpText(), "Help",
JOptionPane.INFORMATION_MESSAGE );
}
} );
if (authenticationPanel.getHelpText() == null) {
typeHelp.setVisible( false );
} }
JComponent typePanel = Components.boxLayout( BoxLayout.LINE_AXIS, typeCheckBox, Box.createGlue(), typeHelp );
typePanel.setAlignmentX( Component.LEFT_ALIGNMENT );
authenticationContainer.add( typePanel );
checkSignIn(); checkSignIn();
validate(); validate();
@@ -126,20 +118,18 @@ public class UnlockFrame extends JFrame {
} }
boolean checkSignIn() { boolean checkSignIn() {
boolean enabled = user != null && !user.getUserName().isEmpty() && user.hasKey(); boolean enabled = user != null && !user.getFullName().isEmpty() && user.hasKey();
signInButton.setEnabled( enabled ); signInButton.setEnabled( enabled );
return enabled; return enabled;
} }
void trySignIn(final JComponent... signInComponents) { void trySignIn(final JComponent... signInComponents) {
if (!checkSignIn()) { if (!checkSignIn())
return; return;
}
for (JComponent signInComponent : signInComponents) { for (JComponent signInComponent : signInComponents)
signInComponent.setEnabled( false ); signInComponent.setEnabled( false );
}
signInButton.setEnabled( false ); signInButton.setEnabled( false );
signInButton.setText( "Signing In..." ); signInButton.setText( "Signing In..." );
@@ -157,10 +147,10 @@ public class UnlockFrame extends JFrame {
return; return;
} }
authenticationPanel.reset();
signInButton.setText( "Sign In" ); signInButton.setText( "Sign In" );
for (JComponent signInComponent : signInComponents) { for (JComponent signInComponent : signInComponents)
signInComponent.setEnabled( true ); signInComponent.setEnabled( true );
}
checkSignIn(); checkSignIn();
} }
} ); } );

View File

@@ -0,0 +1,54 @@
package com.lyndir.masterpassword.gui;
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
import com.lyndir.masterpassword.MasterKey;
import java.security.KeyException;
import javax.annotation.Nonnull;
/**
* @author lhunath, 2014-06-08
*/
public abstract class User {
private MasterKey key;
public abstract String getFullName();
protected abstract String getMasterPassword();
public int getAvatar() {
return 0;
}
public boolean hasKey() {
String masterPassword = getMasterPassword();
return key != null || (masterPassword != null && !masterPassword.isEmpty());
}
@Nonnull
public MasterKey getKey() throws MasterKeyException {
if (key == null) {
if (!hasKey())
throw new MasterKeyException( strf( "Master password unknown for user: %s", getFullName() ) );
key = new MasterKey( getFullName(), getMasterPassword() );
}
return key;
}
@Override
public int hashCode() {
return getFullName().hashCode();
}
@Override
public String toString() {
return getFullName();
}
public abstract Iterable<Site> findSitesByName(final String siteName);
public abstract void addSite(final Site site);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- PROJECT METADATA -->
<parent>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword</artifactId>
<version>GIT-SNAPSHOT</version>
</parent>
<name>Master Password Site Model</name>
<description>A persistence model for Master Password sites.</description>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword-model</artifactId>
<packaging>jar</packaging>
<!-- DEPENDENCY MANAGEMENT -->
<dependencies>
<!-- PROJECT REFERENCES -->
<dependency>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword-algorithm</artifactId>
<version>GIT-SNAPSHOT</version>
</dependency>
<!-- TESTING -->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,119 @@
package com.lyndir.masterpassword.model;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import com.lyndir.masterpassword.*;
import javax.annotation.Nullable;
import org.joda.time.DateTime;
import org.joda.time.Instant;
/**
* @author lhunath, 14-12-05
*/
public class MPSite {
public static final MPSiteType DEFAULT_TYPE = MPSiteType.GeneratedLong;
public static final int DEFAULT_COUNTER = 1;
private int mpVersion;
private Instant lastUsed;
private String siteName;
private MPSiteType siteType;
private int siteCounter;
private int uses;
private String loginName;
public MPSite(final String siteName) {
this( siteName, DEFAULT_TYPE, DEFAULT_COUNTER );
}
public MPSite(final String siteName, final MPSiteType siteType, final int siteCounter) {
this.mpVersion = MasterKey.ALGORITHM;
this.lastUsed = new Instant();
this.siteName = siteName;
this.siteType = siteType;
this.siteCounter = siteCounter;
}
protected MPSite(final int mpVersion, final Instant lastUsed, final String siteName, final MPSiteType siteType, final int siteCounter,
final int uses, final String loginName, final String importContent) {
this.mpVersion = mpVersion;
this.lastUsed = lastUsed;
this.siteName = siteName;
this.siteType = siteType;
this.siteCounter = siteCounter;
this.uses = uses;
this.loginName = loginName;
}
public String resultFor(final MasterKey masterKey) {
return resultFor( masterKey, MPSiteVariant.Password, null );
}
public String resultFor(final MasterKey masterKey, final MPSiteVariant variant, final String context) {
return masterKey.encode( siteName, siteType, siteCounter, variant, context );
}
@Nullable
protected String exportContent() {
return null;
}
public int getMPVersion() {
return mpVersion;
}
public void setMPVersion(final int mpVersion) {
this.mpVersion = mpVersion;
}
public Instant getLastUsed() {
return lastUsed;
}
public void setLastUsed(final Instant lastUsed) {
this.lastUsed = lastUsed;
}
public String getSiteName() {
return siteName;
}
public void setSiteName(final String siteName) {
this.siteName = siteName;
}
public MPSiteType getSiteType() {
return siteType;
}
public void setSiteType(final MPSiteType siteType) {
this.siteType = siteType;
}
public int getSiteCounter() {
return siteCounter;
}
public void setSiteCounter(final int siteCounter) {
this.siteCounter = siteCounter;
}
public int getUses() {
return uses;
}
public void setUses(final int uses) {
this.uses = uses;
}
public String getLoginName() {
return loginName;
}
public void setLoginName(final String loginName) {
this.loginName = loginName;
}
}

View File

@@ -0,0 +1,130 @@
package com.lyndir.masterpassword.model;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import com.google.common.base.Preconditions;
import com.lyndir.masterpassword.MasterKey;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.joda.time.Instant;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
/**
* @author lhunath, 14-12-07
*/
public class MPSiteMarshaller {
private static final DateTimeFormatter rfc3339 = ISODateTimeFormat.dateTimeNoMillis();
private final StringBuilder export = new StringBuilder();
private ContentMode contentMode = ContentMode.PROTECTED;
private MasterKey masterKey;
public static MPSiteMarshaller marshallSafe(final MPUser user) {
MPSiteMarshaller marshaller = new MPSiteMarshaller();
marshaller.marshallHeaderForSafeContent( user );
for (MPSite site : user.getSites())
marshaller.marshallSite( site );
return marshaller;
}
public static MPSiteMarshaller marshallVisible(final MPUser user, final MasterKey masterKey) {
MPSiteMarshaller marshaller = new MPSiteMarshaller();
marshaller.marshallHeaderForVisibleContentWithKey( user, masterKey );
for (MPSite site : user.getSites())
marshaller.marshallSite( site );
return marshaller;
}
private String marshallHeaderForSafeContent(final MPUser user) {
return marshallHeader( ContentMode.PROTECTED, user, null );
}
private String marshallHeaderForVisibleContentWithKey(final MPUser user, final MasterKey masterKey) {
return marshallHeader( ContentMode.VISIBLE, user, masterKey );
}
private String marshallHeader(final ContentMode contentMode, final MPUser user, @Nullable final MasterKey masterKey) {
this.masterKey = masterKey;
StringBuilder header = new StringBuilder();
header.append( "# Master Password site export\n" );
header.append( "# " ).append( contentMode.description() ).append( '\n' );
header.append( "# \n" );
header.append( "##\n" );
header.append( "# Format: 1\n" );
header.append( "# Date: " ).append( rfc3339.print( new Instant() ) ).append( '\n' );
header.append( "# User Name: " ).append( user.getFullName() ).append( '\n' );
header.append( "# Full Name: " ).append( user.getFullName() ).append( '\n' );
header.append( "# Avatar: " ).append( user.getAvatar() ).append( '\n' );
header.append( "# Key ID: " ).append( user.exportKeyID() ).append( '\n' );
header.append( "# Version: " ).append( MasterKey.VERSION ).append( '\n' );
header.append( "# Algorithm: " ).append( MasterKey.ALGORITHM ).append( '\n' );
header.append( "# Default Type: " ).append( user.getDefaultType().getType() ).append( '\n' );
header.append( "# Passwords: " ).append( contentMode.name() ).append( '\n' );
header.append( "##\n" );
header.append( "#\n" );
header.append( "# Last Times Password Login\t Site\tSite\n" );
header.append( "# used used type name\t name\tpassword\n" );
export.append( header );
return header.toString();
}
public String marshallSite(MPSite site) {
String exportLine = strf( "%s %8d %8s %25s\t%25s\t%s", //
rfc3339.print( site.getLastUsed() ), // lastUsed
site.getUses(), // uses
strf( "%d:%d:%d", //
site.getSiteType().getType(), // type
site.getMPVersion(), // algorithm
site.getSiteCounter() ), // counter
ifNotNullElse( site.getLoginName(), "" ), // loginName
site.getSiteName(), // siteName
ifNotNullElse( contentMode.contentForSite( site, masterKey ), "" ) // password
);
export.append( exportLine ).append( '\n' );
return exportLine;
}
public String getExport() {
return export.toString();
}
public ContentMode getContentMode() {
return contentMode;
}
public enum ContentMode {
PROTECTED( "Export of site names and stored passwords (unless device-private) encrypted with the master key." ) {
@Override
public String contentForSite(final MPSite site, @Nullable final MasterKey masterKey) {
return site.exportContent();
}
},
VISIBLE( "Export of site names and passwords in clear-text." ) {
@Override
public String contentForSite(final MPSite site, @Nonnull final MasterKey masterKey) {
return site.resultFor( Preconditions.checkNotNull( masterKey, "Master key is required when content mode is VISIBLE." ) );
}
};
private final String description;
ContentMode(final String description) {
this.description = description;
}
public String description() {
return description;
}
public abstract String contentForSite(final MPSite site, final MasterKey masterKey);
}
}

View File

@@ -0,0 +1,17 @@
package com.lyndir.masterpassword.model;
/**
* @author lhunath, 14-12-07
*/
public class MPSiteResult {
private final MPSite site;
public MPSiteResult(final MPSite site) {
this.site = site;
}
public MPSite getSite() {
return site;
}
}

View File

@@ -0,0 +1,158 @@
package com.lyndir.masterpassword.model;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.io.CharStreams;
import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
import com.lyndir.lhunath.opal.system.util.NNOperation;
import com.lyndir.masterpassword.MPSiteType;
import java.io.*;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
/**
* @author lhunath, 14-12-07
*/
public class MPSiteUnmarshaller {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MPSite.class );
private static final DateTimeFormatter rfc3339 = ISODateTimeFormat.dateTimeNoMillis();
private static final Pattern[] unmarshallFormats = new Pattern[]{
Pattern.compile( "^([^ ]+) +(\\d+) +(\\d+)(:\\d+)? +([^\t]+)\t(.*)" ),
Pattern.compile( "^([^ ]+) +(\\d+) +(\\d+)(:\\d+)?(:\\d+)? +([^\t]*)\t *([^\t]+)\t(.*)" ) };
private static final Pattern headerFormat = Pattern.compile( "^#\\s*([^:]+): (.*)" );
private final int importFormat;
private final int mpVersion;
private final boolean clearContent;
private final MPUser user;
@Nonnull
public static MPSiteUnmarshaller unmarshall(@Nonnull File file)
throws IOException {
try (Reader reader = new FileReader( file )) {
return unmarshall( CharStreams.readLines( reader ) );
}
}
@Nonnull
public static MPSiteUnmarshaller unmarshall(@Nonnull List<String> lines) {
byte[] keyID = null;
String fullName = null;
int mpVersion = 0, importFormat = 0, avatar = 0;
boolean clearContent = false, headerStarted = false;
MPSiteType defaultType = MPSiteType.GeneratedLong;
MPSiteUnmarshaller marshaller = null;
final ImmutableList.Builder<MPSite> sites = ImmutableList.builder();
for (String line : lines)
// Header delimitor.
if (line.startsWith( "##" ))
if (!headerStarted)
// Starts the header.
headerStarted = true;
else
// Ends the header.
marshaller = new MPSiteUnmarshaller( importFormat, mpVersion, fullName, keyID, avatar, defaultType, clearContent );
// Comment.
else if (line.startsWith( "#" )) {
if (headerStarted && marshaller == null) {
// In header.
Matcher headerMatcher = headerFormat.matcher( line );
if (headerMatcher.matches()) {
String name = headerMatcher.group( 1 ), value = headerMatcher.group( 2 );
if ("Full Name".equalsIgnoreCase( name ) || "User Name".equalsIgnoreCase( name ))
fullName = value;
else if ("Key ID".equalsIgnoreCase( name ))
keyID = CodeUtils.decodeHex( value );
else if ("Algorithm".equalsIgnoreCase( name ))
mpVersion = ConversionUtils.toIntegerNN( value );
else if ("Format".equalsIgnoreCase( name ))
importFormat = ConversionUtils.toIntegerNN( value );
else if ("Avatar".equalsIgnoreCase( name ))
avatar = ConversionUtils.toIntegerNN( value );
else if ("Passwords".equalsIgnoreCase( name ))
clearContent = value.equalsIgnoreCase( "visible" );
else if ("Default Type".equalsIgnoreCase( name ))
defaultType = MPSiteType.forType( ConversionUtils.toIntegerNN( value ) );
}
}
}
// No comment.
else if (marshaller != null)
ifNotNull( marshaller.unmarshallSite( line ), new NNOperation<MPSite>() {
@Override
public void apply(@Nonnull final MPSite site) {
sites.add( site );
}
} );
return Preconditions.checkNotNull( marshaller, "No full header found in import file." );
}
protected MPSiteUnmarshaller(final int importFormat, final int mpVersion, final String fullName, final byte[] keyID, final int avatar,
final MPSiteType defaultType, final boolean clearContent) {
this.importFormat = importFormat;
this.mpVersion = mpVersion;
this.clearContent = clearContent;
user = new MPUser( fullName, keyID, avatar, defaultType, new DateTime( 0 ) );
}
@Nullable
public MPSite unmarshallSite(@Nonnull String siteLine) {
Matcher siteMatcher = unmarshallFormats[importFormat].matcher( siteLine );
if (!siteMatcher.matches())
return null;
MPSite site;
switch (importFormat) {
case 0:
site = new MPSite( ConversionUtils.toIntegerNN( siteMatcher.group( 4 ).replace( ":", "" ) ), //
rfc3339.parseDateTime( siteMatcher.group( 1 ) ).toInstant(), //
siteMatcher.group( 5 ), //
MPSiteType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),
MPSite.DEFAULT_COUNTER, //
ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ), //
null, //
siteMatcher.group( 6 ) );
break;
case 1:
site = new MPSite( ConversionUtils.toIntegerNN( siteMatcher.group( 4 ).replace( ":", "" ) ), //
rfc3339.parseDateTime( siteMatcher.group( 1 ) ).toInstant(), //
siteMatcher.group( 7 ), //
MPSiteType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),
ConversionUtils.toIntegerNN( siteMatcher.group( 5 ).replace( ":", "" ) ), //
ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ), //
siteMatcher.group( 6 ), //
siteMatcher.group( 8 ) );
break;
default:
throw logger.bug( "Unexpected format: %d", importFormat );
}
user.addSite( site );
return site;
}
public MPUser getUser() {
return user;
}
}

View File

@@ -0,0 +1,101 @@
package com.lyndir.masterpassword.model;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.masterpassword.MPSiteType;
import java.util.*;
import org.joda.time.*;
/**
* @author lhunath, 14-12-07
*/
public class MPUser {
private final String fullName;
private final Collection<MPSite> sites = Sets.newHashSet();
private byte[] keyID;
private int avatar;
private MPSiteType defaultType;
private ReadableInstant lastUsed;
public MPUser(final String fullName) {
this( fullName, null );
}
public MPUser(final String fullName, final byte[] keyID) {
this( fullName, keyID, 0, MPSiteType.GeneratedLong, new DateTime() );
}
public MPUser(final String fullName, final byte[] keyID, final int avatar, final MPSiteType defaultType,
final ReadableInstant lastUsed) {
this.fullName = fullName;
this.keyID = keyID;
this.avatar = avatar;
this.defaultType = defaultType;
this.lastUsed = lastUsed;
}
public Collection<MPSiteResult> findSitesByName(String query) {
ImmutableList.Builder<MPSiteResult> results = ImmutableList.builder();
for (MPSite site : getSites())
if (site.getSiteName().startsWith( query ))
results.add( new MPSiteResult( site ) );
return results.build();
}
public void addSite(final MPSite site) {
sites.add( site );
}
public String getFullName() {
return fullName;
}
public boolean hasKeyID() {
return keyID != null;
}
public boolean hasKeyID(final byte[] keyID) {
return Arrays.equals( this.keyID, keyID );
}
public String exportKeyID() {
return CodeUtils.encodeHex( keyID );
}
public void setKeyID(final byte[] keyID) {
this.keyID = keyID;
}
public int getAvatar() {
return avatar;
}
public void setAvatar(final int avatar) {
this.avatar = avatar;
}
public MPSiteType getDefaultType() {
return defaultType;
}
public void setDefaultType(final MPSiteType defaultType) {
this.defaultType = defaultType;
}
public ReadableInstant getLastUsed() {
return lastUsed;
}
public void updateLastUsed() {
this.lastUsed = new Instant();
}
public Iterable<MPSite> getSites() {
return sites;
}
}

View File

@@ -0,0 +1,78 @@
package com.lyndir.masterpassword.model;
import com.google.common.base.*;
import com.google.common.collect.*;
import com.google.common.io.CharSink;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.io.*;
import javax.annotation.Nullable;
/**
* @author lhunath, 14-12-07
*/
public class MPUserFileManager extends MPUserManager {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MPUserFileManager.class );
private static final MPUserFileManager instance = create( new File( System.getProperty( "user.home" ), ".mpwrc" ) );
private final File userFilesDirectory;
public static MPUserFileManager get() {
MPUserManager.instance = instance;
return instance;
}
public static MPUserFileManager create(final File userFilesDirectory) {
return new MPUserFileManager( userFilesDirectory );
}
protected MPUserFileManager(final File userFilesDirectory) {
super( unmarshallUsers( userFilesDirectory ) );
this.userFilesDirectory = userFilesDirectory;
}
private static Iterable<MPUser> unmarshallUsers(final File userFilesDirectory) {
if (!userFilesDirectory.mkdirs() && !userFilesDirectory.isDirectory()) {
logger.err( "Couldn't create directory for user files: %s", userFilesDirectory );
return ImmutableList.of();
}
return FluentIterable.from( ImmutableList.copyOf( userFilesDirectory.listFiles( new FilenameFilter() {
@Override
public boolean accept(final File dir, final String name) {
return name.endsWith( ".mpsites" );
}
} ) ) ).transform( new Function<File, MPUser>() {
@Nullable
@Override
public MPUser apply(final File file) {
try {
return MPSiteUnmarshaller.unmarshall( file ).getUser();
}
catch (IOException e) {
logger.err( e, "Couldn't read user from: %s", file );
return null;
}
}
} ).filter( Predicates.notNull() );
}
public void save() {
for (final MPUser user : getUsers())
try {
new CharSink() {
@Override
public Writer openStream()
throws IOException {
return new FileWriter( new File(userFilesDirectory, user.getFullName() + ".mpsites" ) );
}
}.write( MPSiteMarshaller.marshallSafe( user ).getExport() );
}
catch (IOException e) {
logger.err( e, "Unable to save sites for user: %s", user );
}
}
}

View File

@@ -0,0 +1,37 @@
package com.lyndir.masterpassword.model;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Maps;
import java.util.*;
/**
* @author lhunath, 14-12-05
*/
public abstract class MPUserManager {
private final Map<String, MPUser> usersByName = Maps.newHashMap();
static MPUserManager instance;
public static MPUserManager get() {
return instance;
}
public MPUserManager(final Iterable<MPUser> users) {
for (MPUser user : users)
addUser( user );
}
public SortedSet<MPUser> getUsers() {
return FluentIterable.from( usersByName.values() ).toSortedSet( new Comparator<MPUser>() {
@Override
public int compare(final MPUser user1, final MPUser user2) {
return user1.getLastUsed().compareTo( user2.getLastUsed() );
}
} );
}
public void addUser(final MPUser user) {
usersByName.put( user.getFullName(), user );
}
}

View File

@@ -7,7 +7,7 @@
<parent> <parent>
<groupId>com.lyndir.lhunath</groupId> <groupId>com.lyndir.lhunath</groupId>
<artifactId>lyndir</artifactId> <artifactId>lyndir</artifactId>
<version>1.20</version> <version>1.18</version>
</parent> </parent>
<name>Master Password</name> <name>Master Password</name>
@@ -20,6 +20,7 @@
<modules> <modules>
<module>masterpassword-algorithm</module> <module>masterpassword-algorithm</module>
<module>masterpassword-model</module>
<module>masterpassword-cli</module> <module>masterpassword-cli</module>
<module>masterpassword-gui</module> <module>masterpassword-gui</module>
</modules> </modules>

View File

@@ -75,6 +75,20 @@
DA60717C195D040500CA98B5 /* icon_gear.png in Resources */ = {isa = PBXBuildFile; fileRef = DA607092195D03E200CA98B5 /* icon_gear.png */; }; DA60717C195D040500CA98B5 /* icon_gear.png in Resources */ = {isa = PBXBuildFile; fileRef = DA607092195D03E200CA98B5 /* icon_gear.png */; };
DA60717D195D040500CA98B5 /* icon_gear@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA607093195D03E200CA98B5 /* icon_gear@2x.png */; }; DA60717D195D040500CA98B5 /* icon_gear@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA607093195D03E200CA98B5 /* icon_gear@2x.png */; };
DA6558A419A99609009A0BEB /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DA6558A319A99609009A0BEB /* Images.xcassets */; }; DA6558A419A99609009A0BEB /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DA6558A319A99609009A0BEB /* Images.xcassets */; };
DA6773CA1A4746AF004F356A /* bashlib in Resources */ = {isa = PBXBuildFile; fileRef = DA67732A1A4746AF004F356A /* bashlib */; };
DA6773CB1A4746AF004F356A /* build in Resources */ = {isa = PBXBuildFile; fileRef = DA67732B1A4746AF004F356A /* build */; };
DA6773CC1A4746AF004F356A /* distribute in Resources */ = {isa = PBXBuildFile; fileRef = DA67732C1A4746AF004F356A /* distribute */; };
DA6773CD1A4746AF004F356A /* install in Resources */ = {isa = PBXBuildFile; fileRef = DA67732D1A4746AF004F356A /* install */; };
DA6774291A4746AF004F356A /* mpw-algorithm.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773BB1A4746AF004F356A /* mpw-algorithm.c */; };
DA67742F1A4746AF004F356A /* mpw-types.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773C21A4746AF004F356A /* mpw-types.c */; };
DA6774311A4746AF004F356A /* mpw-util.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773C51A4746AF004F356A /* mpw-util.c */; };
DA6774331A4746AF004F356A /* mpw.bashrc in Resources */ = {isa = PBXBuildFile; fileRef = DA6773C81A4746AF004F356A /* mpw.bashrc */; };
DA6774341A4746AF004F356A /* VERSION in Resources */ = {isa = PBXBuildFile; fileRef = DA6773C91A4746AF004F356A /* VERSION */; };
DA6774431A474A3B004F356A /* mpw-algorithm.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773BB1A4746AF004F356A /* mpw-algorithm.c */; };
DA6774441A474A3B004F356A /* mpw-tests.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773C11A4746AF004F356A /* mpw-tests.c */; };
DA6774451A474A3B004F356A /* mpw-types.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773C21A4746AF004F356A /* mpw-types.c */; };
DA6774461A474A3B004F356A /* mpw-util.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773C51A4746AF004F356A /* mpw-util.c */; };
DA67744A1A47C8F7004F356A /* mpw-tests-util.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6774481A47C8F7004F356A /* mpw-tests-util.c */; };
DA8ED895192906920099B726 /* PearlTween.m in Sources */ = {isa = PBXBuildFile; fileRef = DA8ED891192906920099B726 /* PearlTween.m */; }; DA8ED895192906920099B726 /* PearlTween.m in Sources */ = {isa = PBXBuildFile; fileRef = DA8ED891192906920099B726 /* PearlTween.m */; };
DA8ED896192906920099B726 /* PearlTween.h in Headers */ = {isa = PBXBuildFile; fileRef = DA8ED892192906920099B726 /* PearlTween.h */; }; DA8ED896192906920099B726 /* PearlTween.h in Headers */ = {isa = PBXBuildFile; fileRef = DA8ED892192906920099B726 /* PearlTween.h */; };
DA8ED897192906920099B726 /* map-macro.h in Headers */ = {isa = PBXBuildFile; fileRef = DA8ED894192906920099B726 /* map-macro.h */; }; DA8ED897192906920099B726 /* map-macro.h in Headers */ = {isa = PBXBuildFile; fileRef = DA8ED894192906920099B726 /* map-macro.h */; };
@@ -211,6 +225,15 @@
/* End PBXContainerItemProxy section */ /* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */ /* Begin PBXCopyFilesBuildPhase section */
DA6774391A474A03004F356A /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = /usr/share/man/man1/;
dstSubfolderSpec = 0;
files = (
);
runOnlyForDeploymentPostprocessing = 1;
};
DAADCC4E19FB006500987B1D /* CopyFiles */ = { DAADCC4E19FB006500987B1D /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase; isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@@ -742,6 +765,24 @@
DA6701B716406A4100B61001 /* Accounts.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accounts.framework; path = System/Library/Frameworks/Accounts.framework; sourceTree = SDKROOT; }; DA6701B716406A4100B61001 /* Accounts.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accounts.framework; path = System/Library/Frameworks/Accounts.framework; sourceTree = SDKROOT; };
DA6701DD16406B7300B61001 /* Social.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Social.framework; path = System/Library/Frameworks/Social.framework; sourceTree = SDKROOT; }; DA6701DD16406B7300B61001 /* Social.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Social.framework; path = System/Library/Frameworks/Social.framework; sourceTree = SDKROOT; };
DA672D2E14F92C6B004A189C /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; }; DA672D2E14F92C6B004A189C /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; };
DA67732A1A4746AF004F356A /* bashlib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = bashlib; sourceTree = "<group>"; };
DA67732B1A4746AF004F356A /* build */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = build; sourceTree = "<group>"; };
DA67732C1A4746AF004F356A /* distribute */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = distribute; sourceTree = "<group>"; };
DA67732D1A4746AF004F356A /* install */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = install; sourceTree = "<group>"; };
DA6773BB1A4746AF004F356A /* mpw-algorithm.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-algorithm.c"; sourceTree = "<group>"; };
DA6773BC1A4746AF004F356A /* mpw-algorithm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-algorithm.h"; sourceTree = "<group>"; };
DA6773BF1A4746AF004F356A /* mpw-bench.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-bench.c"; sourceTree = "<group>"; };
DA6773C01A4746AF004F356A /* mpw-cli.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-cli.c"; sourceTree = "<group>"; };
DA6773C11A4746AF004F356A /* mpw-tests.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-tests.c"; sourceTree = "<group>"; };
DA6773C21A4746AF004F356A /* mpw-types.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-types.c"; sourceTree = "<group>"; };
DA6773C31A4746AF004F356A /* mpw-types.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-types.h"; sourceTree = "<group>"; };
DA6773C51A4746AF004F356A /* mpw-util.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-util.c"; sourceTree = "<group>"; };
DA6773C61A4746AF004F356A /* mpw-util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-util.h"; sourceTree = "<group>"; };
DA6773C81A4746AF004F356A /* mpw.bashrc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = mpw.bashrc; sourceTree = "<group>"; };
DA6773C91A4746AF004F356A /* VERSION */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = VERSION; sourceTree = "<group>"; };
DA67743B1A474A03004F356A /* mpw-test */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "mpw-test"; sourceTree = BUILT_PRODUCTS_DIR; };
DA6774481A47C8F7004F356A /* mpw-tests-util.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-tests-util.c"; sourceTree = "<group>"; };
DA6774491A47C8F7004F356A /* mpw-tests-util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-tests-util.h"; sourceTree = "<group>"; };
DA8ED891192906920099B726 /* PearlTween.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlTween.m; sourceTree = "<group>"; }; DA8ED891192906920099B726 /* PearlTween.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlTween.m; sourceTree = "<group>"; };
DA8ED892192906920099B726 /* PearlTween.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlTween.h; sourceTree = "<group>"; }; DA8ED892192906920099B726 /* PearlTween.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlTween.h; sourceTree = "<group>"; };
DA8ED894192906920099B726 /* map-macro.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "map-macro.h"; sourceTree = "<group>"; }; DA8ED894192906920099B726 /* map-macro.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "map-macro.h"; sourceTree = "<group>"; };
@@ -888,6 +929,13 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
DA6774381A474A03004F356A /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
DAADCC4D19FB006500987B1D /* Frameworks */ = { DAADCC4D19FB006500987B1D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@@ -939,6 +987,7 @@
DA5BFA39147E415C00F98B1E = { DA5BFA39147E415C00F98B1E = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DA6773291A4746AF004F356A /* C */,
DA5E5C961724A667003798D8 /* ObjC */, DA5E5C961724A667003798D8 /* ObjC */,
DACA23B41705DF7D002C6C22 /* Resources */, DACA23B41705DF7D002C6C22 /* Resources */,
DACA22121705DDC5002C6C22 /* External */, DACA22121705DDC5002C6C22 /* External */,
@@ -956,6 +1005,7 @@
DAC77CAD148291A600BCF976 /* libPearl.a */, DAC77CAD148291A600BCF976 /* libPearl.a */,
DAC6326C148680650075AEA5 /* libjrswizzle.a */, DAC6326C148680650075AEA5 /* libjrswizzle.a */,
DAADCC5019FB006500987B1D /* libKCOrderedAccessorFix.a */, DAADCC5019FB006500987B1D /* libKCOrderedAccessorFix.a */,
DA67743B1A474A03004F356A /* mpw-test */,
); );
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -1476,6 +1526,31 @@
path = Insignia; path = Insignia;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
DA6773291A4746AF004F356A /* C */ = {
isa = PBXGroup;
children = (
DA67732A1A4746AF004F356A /* bashlib */,
DA67732B1A4746AF004F356A /* build */,
DA67732C1A4746AF004F356A /* distribute */,
DA67732D1A4746AF004F356A /* install */,
DA6773BB1A4746AF004F356A /* mpw-algorithm.c */,
DA6773BC1A4746AF004F356A /* mpw-algorithm.h */,
DA6773BF1A4746AF004F356A /* mpw-bench.c */,
DA6773C01A4746AF004F356A /* mpw-cli.c */,
DA6774481A47C8F7004F356A /* mpw-tests-util.c */,
DA6774491A47C8F7004F356A /* mpw-tests-util.h */,
DA6773C11A4746AF004F356A /* mpw-tests.c */,
DA6773C21A4746AF004F356A /* mpw-types.c */,
DA6773C31A4746AF004F356A /* mpw-types.h */,
DA6773C51A4746AF004F356A /* mpw-util.c */,
DA6773C61A4746AF004F356A /* mpw-util.h */,
DA6773C81A4746AF004F356A /* mpw.bashrc */,
DA6773C91A4746AF004F356A /* VERSION */,
);
name = C;
path = ../../C;
sourceTree = "<group>";
};
DA8ED893192906920099B726 /* include */ = { DA8ED893192906920099B726 /* include */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -1847,6 +1922,23 @@
productReference = DA5BFA44147E415C00F98B1E /* Master Password.app */; productReference = DA5BFA44147E415C00F98B1E /* Master Password.app */;
productType = "com.apple.product-type.application"; productType = "com.apple.product-type.application";
}; };
DA67743A1A474A03004F356A /* mpw-test */ = {
isa = PBXNativeTarget;
buildConfigurationList = DA67743F1A474A03004F356A /* Build configuration list for PBXNativeTarget "mpw-test" */;
buildPhases = (
DA6774371A474A03004F356A /* Sources */,
DA6774381A474A03004F356A /* Frameworks */,
DA6774391A474A03004F356A /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = "mpw-test";
productName = "mpw-test";
productReference = DA67743B1A474A03004F356A /* mpw-test */;
productType = "com.apple.product-type.tool";
};
DAADCC4F19FB006500987B1D /* KCOrderedAccessorFix */ = { DAADCC4F19FB006500987B1D /* KCOrderedAccessorFix */ = {
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = DAADCC5E19FB006500987B1D /* Build configuration list for PBXNativeTarget "KCOrderedAccessorFix" */; buildConfigurationList = DAADCC5E19FB006500987B1D /* Build configuration list for PBXNativeTarget "KCOrderedAccessorFix" */;
@@ -1919,6 +2011,9 @@
}; };
}; };
}; };
DA67743A1A474A03004F356A = {
CreatedOnToolsVersion = 6.1.1;
};
DAADCC4F19FB006500987B1D = { DAADCC4F19FB006500987B1D = {
CreatedOnToolsVersion = 6.0.1; CreatedOnToolsVersion = 6.0.1;
}; };
@@ -1947,6 +2042,7 @@
DAC77CAC148291A600BCF976 /* Pearl */, DAC77CAC148291A600BCF976 /* Pearl */,
DAC6326B148680650075AEA5 /* jrswizzle */, DAC6326B148680650075AEA5 /* jrswizzle */,
DAADCC4F19FB006500987B1D /* KCOrderedAccessorFix */, DAADCC4F19FB006500987B1D /* KCOrderedAccessorFix */,
DA67743A1A474A03004F356A /* mpw-test */,
); );
}; };
/* End PBXProject section */ /* End PBXProject section */
@@ -1972,6 +2068,7 @@
DACA27131705DF81002C6C22 /* avatar-3@2x.png in Resources */, DACA27131705DF81002C6C22 /* avatar-3@2x.png in Resources */,
DACA27141705DF81002C6C22 /* avatar-7.png in Resources */, DACA27141705DF81002C6C22 /* avatar-7.png in Resources */,
DAF4EF57190A828100023C90 /* Exo2.0-Regular.otf in Resources */, DAF4EF57190A828100023C90 /* Exo2.0-Regular.otf in Resources */,
DA6773CA1A4746AF004F356A /* bashlib in Resources */,
DACA27151705DF81002C6C22 /* avatar-0.png in Resources */, DACA27151705DF81002C6C22 /* avatar-0.png in Resources */,
DA6558A419A99609009A0BEB /* Images.xcassets in Resources */, DA6558A419A99609009A0BEB /* Images.xcassets in Resources */,
DACA27161705DF81002C6C22 /* avatar-12.png in Resources */, DACA27161705DF81002C6C22 /* avatar-12.png in Resources */,
@@ -1979,6 +2076,7 @@
DAF4EF59190A828100023C90 /* Exo2.0-Bold.otf in Resources */, DAF4EF59190A828100023C90 /* Exo2.0-Bold.otf in Resources */,
DACA27181705DF81002C6C22 /* avatar-9.png in Resources */, DACA27181705DF81002C6C22 /* avatar-9.png in Resources */,
DAAA81B0195A8D1300FA30D9 /* gradient.png in Resources */, DAAA81B0195A8D1300FA30D9 /* gradient.png in Resources */,
DA6774331A4746AF004F356A /* mpw.bashrc in Resources */,
DACA27191705DF81002C6C22 /* avatar-1@2x.png in Resources */, DACA27191705DF81002C6C22 /* avatar-1@2x.png in Resources */,
DA2508F119511D3600AC23F1 /* MPPasswordWindowController.xib in Resources */, DA2508F119511D3600AC23F1 /* MPPasswordWindowController.xib in Resources */,
DA60717C195D040500CA98B5 /* icon_gear.png in Resources */, DA60717C195D040500CA98B5 /* icon_gear.png in Resources */,
@@ -2001,9 +2099,11 @@
DACA27291705DF81002C6C22 /* avatar-17.png in Resources */, DACA27291705DF81002C6C22 /* avatar-17.png in Resources */,
DACA272A1705DF81002C6C22 /* avatar-5.png in Resources */, DACA272A1705DF81002C6C22 /* avatar-5.png in Resources */,
DACA272B1705DF81002C6C22 /* avatar-2.png in Resources */, DACA272B1705DF81002C6C22 /* avatar-2.png in Resources */,
DA6773CD1A4746AF004F356A /* install in Resources */,
DACA272C1705DF81002C6C22 /* avatar-14@2x.png in Resources */, DACA272C1705DF81002C6C22 /* avatar-14@2x.png in Resources */,
DACA272D1705DF81002C6C22 /* avatar-4@2x.png in Resources */, DACA272D1705DF81002C6C22 /* avatar-4@2x.png in Resources */,
DACA272E1705DF81002C6C22 /* avatar-9@2x.png in Resources */, DACA272E1705DF81002C6C22 /* avatar-9@2x.png in Resources */,
DA6774341A4746AF004F356A /* VERSION in Resources */,
DACA272F1705DF81002C6C22 /* avatar-3.png in Resources */, DACA272F1705DF81002C6C22 /* avatar-3.png in Resources */,
DACA27301705DF81002C6C22 /* avatar-18.png in Resources */, DACA27301705DF81002C6C22 /* avatar-18.png in Resources */,
DACA27311705DF81002C6C22 /* avatar-4.png in Resources */, DACA27311705DF81002C6C22 /* avatar-4.png in Resources */,
@@ -2017,7 +2117,9 @@
DACA27351705DF81002C6C22 /* avatar-11.png in Resources */, DACA27351705DF81002C6C22 /* avatar-11.png in Resources */,
DACA27361705DF81002C6C22 /* avatar-0@2x.png in Resources */, DACA27361705DF81002C6C22 /* avatar-0@2x.png in Resources */,
DACA27371705DF81002C6C22 /* avatar-10@2x.png in Resources */, DACA27371705DF81002C6C22 /* avatar-10@2x.png in Resources */,
DA6773CC1A4746AF004F356A /* distribute in Resources */,
DACA27381705DF81002C6C22 /* menu-icon.png in Resources */, DACA27381705DF81002C6C22 /* menu-icon.png in Resources */,
DA6773CB1A4746AF004F356A /* build in Resources */,
DACA29671705DF81002C6C22 /* SourceCodePro-ExtraLight.otf in Resources */, DACA29671705DF81002C6C22 /* SourceCodePro-ExtraLight.otf in Resources */,
DACA29681705DF81002C6C22 /* SourceCodePro-Black.otf in Resources */, DACA29681705DF81002C6C22 /* SourceCodePro-Black.otf in Resources */,
DACA296F1705DF81002C6C22 /* Crashlytics.plist in Resources */, DACA296F1705DF81002C6C22 /* Crashlytics.plist in Resources */,
@@ -2104,6 +2206,7 @@
DA32CFE519CF1C71004F3F0E /* MPUserEntity.m in Sources */, DA32CFE519CF1C71004F3F0E /* MPUserEntity.m in Sources */,
DA5E5CF71724A667003798D8 /* MPAlgorithmV0.m in Sources */, DA5E5CF71724A667003798D8 /* MPAlgorithmV0.m in Sources */,
DA5E5CF81724A667003798D8 /* MPAlgorithmV1.m in Sources */, DA5E5CF81724A667003798D8 /* MPAlgorithmV1.m in Sources */,
DA6774311A4746AF004F356A /* mpw-util.c in Sources */,
DA5E5CF91724A667003798D8 /* MPAppDelegate_Key.m in Sources */, DA5E5CF91724A667003798D8 /* MPAppDelegate_Key.m in Sources */,
DA5180CE19FF307E00A587E9 /* MPAppDelegate_Store.m in Sources */, DA5180CE19FF307E00A587E9 /* MPAppDelegate_Store.m in Sources */,
DA5E5CFA1724A667003798D8 /* MPAppDelegate_Shared.m in Sources */, DA5E5CFA1724A667003798D8 /* MPAppDelegate_Shared.m in Sources */,
@@ -2119,15 +2222,29 @@
93D39C5789EFA607CF788082 /* MPSiteModel.m in Sources */, 93D39C5789EFA607CF788082 /* MPSiteModel.m in Sources */,
DA5180CA19FF2F9200A587E9 /* MPAlgorithmV2.m in Sources */, DA5180CA19FF2F9200A587E9 /* MPAlgorithmV2.m in Sources */,
93D39F833DEC1C89B2F795AC /* MPPasswordWindowController.m in Sources */, 93D39F833DEC1C89B2F795AC /* MPPasswordWindowController.m in Sources */,
DA67742F1A4746AF004F356A /* mpw-types.c in Sources */,
DA32CFD919CF1C70004F3F0E /* MPGeneratedSiteEntity.m in Sources */, DA32CFD919CF1C70004F3F0E /* MPGeneratedSiteEntity.m in Sources */,
93D390C676DF52DA7E459F19 /* MPPasswordWindow.m in Sources */, 93D390C676DF52DA7E459F19 /* MPPasswordWindow.m in Sources */,
93D39784E725A34D1EE3FB3B /* MPInitialWindowController.m in Sources */, 93D39784E725A34D1EE3FB3B /* MPInitialWindowController.m in Sources */,
DA32CFDF19CF1C70004F3F0E /* MPSiteEntity.m in Sources */, DA32CFDF19CF1C70004F3F0E /* MPSiteEntity.m in Sources */,
93D394C4254EEB45FB335AFB /* MPSitesTableView.m in Sources */, 93D394C4254EEB45FB335AFB /* MPSitesTableView.m in Sources */,
DA6774291A4746AF004F356A /* mpw-algorithm.c in Sources */,
93D395E4830290EBB6E71F34 /* MPNoStateButton.m in Sources */, 93D395E4830290EBB6E71F34 /* MPNoStateButton.m in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
DA6774371A474A03004F356A /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
DA6774441A474A3B004F356A /* mpw-tests.c in Sources */,
DA67744A1A47C8F7004F356A /* mpw-tests-util.c in Sources */,
DA6774451A474A3B004F356A /* mpw-types.c in Sources */,
DA6774461A474A3B004F356A /* mpw-util.c in Sources */,
DA6774431A474A3B004F356A /* mpw-algorithm.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
DAADCC4C19FB006500987B1D /* Sources */ = { DAADCC4C19FB006500987B1D /* Sources */ = {
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@@ -2236,6 +2353,7 @@
CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES; CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;
CLANG_WARN_CXX0X_EXTENSIONS = YES; CLANG_WARN_CXX0X_EXTENSIONS = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
@@ -2287,6 +2405,10 @@
GCC_WARN_UNUSED_LABEL = YES; GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_VALUE = YES; GCC_WARN_UNUSED_VALUE = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
);
LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)"; LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)";
LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks";
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
@@ -2294,6 +2416,7 @@
"\"$(SRCROOT)/../../../External\"/**", "\"$(SRCROOT)/../../../External\"/**",
); );
MACOSX_DEPLOYMENT_TARGET = 10.8; MACOSX_DEPLOYMENT_TARGET = 10.8;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
OTHER_LDFLAGS = "-ObjC"; OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "${TARGET_NAME}"; PRODUCT_NAME = "${TARGET_NAME}";
@@ -2311,6 +2434,7 @@
CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES; CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;
CLANG_WARN_CXX0X_EXTENSIONS = YES; CLANG_WARN_CXX0X_EXTENSIONS = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
@@ -2362,6 +2486,10 @@
GCC_WARN_UNUSED_LABEL = YES; GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_VALUE = YES; GCC_WARN_UNUSED_VALUE = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
);
LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)"; LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)";
LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks";
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
@@ -2369,6 +2497,7 @@
"\"$(SRCROOT)/../../../External\"/**", "\"$(SRCROOT)/../../../External\"/**",
); );
MACOSX_DEPLOYMENT_TARGET = 10.8; MACOSX_DEPLOYMENT_TARGET = 10.8;
MTL_ENABLE_DEBUG_INFO = NO;
OTHER_LDFLAGS = "-ObjC"; OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "${TARGET_NAME}"; PRODUCT_NAME = "${TARGET_NAME}";
RUN_CLANG_STATIC_ANALYZER = YES; RUN_CLANG_STATIC_ANALYZER = YES;
@@ -2426,6 +2555,42 @@
}; };
name = "AdHoc-Mac"; name = "AdHoc-Mac";
}; };
DA6774401A474A03004F356A /* Debug-Mac */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_WARN_DOCUMENTATION_COMMENTS = NO;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
/usr/include/libxml2,
);
};
name = "Debug-Mac";
};
DA6774411A474A03004F356A /* AdHoc-Mac */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_WARN_DOCUMENTATION_COMMENTS = NO;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
/usr/include/libxml2,
);
};
name = "AdHoc-Mac";
};
DA6774421A474A03004F356A /* AppStore-Mac */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_WARN_DOCUMENTATION_COMMENTS = NO;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
/usr/include/libxml2,
);
};
name = "AppStore-Mac";
};
DA95D60914DF3F3B008D1B94 /* AppStore-Mac */ = { DA95D60914DF3F3B008D1B94 /* AppStore-Mac */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
@@ -2435,6 +2600,7 @@
CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES; CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;
CLANG_WARN_CXX0X_EXTENSIONS = YES; CLANG_WARN_CXX0X_EXTENSIONS = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
@@ -2486,6 +2652,10 @@
GCC_WARN_UNUSED_LABEL = YES; GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_VALUE = YES; GCC_WARN_UNUSED_VALUE = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
);
LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)"; LD_DYLIB_INSTALL_NAME = "@rpath/$(EXECUTABLE_PATH)";
LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks";
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
@@ -2493,6 +2663,7 @@
"\"$(SRCROOT)/../../../External\"/**", "\"$(SRCROOT)/../../../External\"/**",
); );
MACOSX_DEPLOYMENT_TARGET = 10.8; MACOSX_DEPLOYMENT_TARGET = 10.8;
MTL_ENABLE_DEBUG_INFO = NO;
OTHER_LDFLAGS = "-ObjC"; OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "${TARGET_NAME}"; PRODUCT_NAME = "${TARGET_NAME}";
RUN_CLANG_STATIC_ANALYZER = YES; RUN_CLANG_STATIC_ANALYZER = YES;
@@ -2672,6 +2843,16 @@
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = "AdHoc-Mac"; defaultConfigurationName = "AdHoc-Mac";
}; };
DA67743F1A474A03004F356A /* Build configuration list for PBXNativeTarget "mpw-test" */ = {
isa = XCConfigurationList;
buildConfigurations = (
DA6774401A474A03004F356A /* Debug-Mac */,
DA6774411A474A03004F356A /* AdHoc-Mac */,
DA6774421A474A03004F356A /* AppStore-Mac */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = "AdHoc-Mac";
};
DAADCC5E19FB006500987B1D /* Build configuration list for PBXNativeTarget "KCOrderedAccessorFix" */ = { DAADCC5E19FB006500987B1D /* Build configuration list for PBXNativeTarget "KCOrderedAccessorFix" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (

View File

@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0610"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA67743A1A474A03004F356A"
BuildableName = "mpw-test"
BlueprintName = "mpw-test"
ReferencedContainer = "container:MasterPassword-Mac.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug-Mac">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA67743A1A474A03004F356A"
BuildableName = "mpw-test"
BlueprintName = "mpw-test"
ReferencedContainer = "container:MasterPassword-Mac.xcodeproj">
</BuildableReference>
</MacroExpansion>
</TestAction>
<LaunchAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
buildConfiguration = "Debug-Mac"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
allowLocationSimulation = "YES">
<BuildableProductRunnable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA67743A1A474A03004F356A"
BuildableName = "mpw-test"
BlueprintName = "mpw-test"
ReferencedContainer = "container:MasterPassword-Mac.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
buildConfiguration = "AdHoc-Mac"
debugDocumentVersioning = "YES">
<BuildableProductRunnable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA67743A1A474A03004F356A"
BuildableName = "mpw-test"
BlueprintName = "mpw-test"
ReferencedContainer = "container:MasterPassword-Mac.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug-Mac">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "AdHoc-Mac"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -4,7 +4,6 @@
#ifdef __OBJC__ #ifdef __OBJC__
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#endif
#define PEARL_WITH_SCRYPT #define PEARL_WITH_SCRYPT
#define PEARL_WITH_MESSAGEUI #define PEARL_WITH_MESSAGEUI
@@ -20,3 +19,4 @@
#ifdef __IPHONE_OS_VERSION_MIN_REQUIRED #ifdef __IPHONE_OS_VERSION_MIN_REQUIRED
#import "Pearl-UIKit.h" #import "Pearl-UIKit.h"
#endif #endif
#endif

View File

@@ -7,6 +7,7 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
93D390228D0B8ED51C0AFDB4 /* bashlib in Resources */ = {isa = PBXBuildFile; fileRef = 93D39E71D6BAECEC4CD886F4 /* bashlib */; };
93D390C1B93F9D3AE37DD0A5 /* MPAnswersViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39C426E03358384018E85 /* MPAnswersViewController.m */; }; 93D390C1B93F9D3AE37DD0A5 /* MPAnswersViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39C426E03358384018E85 /* MPAnswersViewController.m */; };
93D391ECBD9BD2C64115B5DD /* PearlSizedTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39156E806BB78E04F78B9 /* PearlSizedTextView.m */; }; 93D391ECBD9BD2C64115B5DD /* PearlSizedTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39156E806BB78E04F78B9 /* PearlSizedTextView.m */; };
93D3922A53E41A54832E90D9 /* PearlOverlay.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D390FADEB325D8D54A957D /* PearlOverlay.m */; }; 93D3922A53E41A54832E90D9 /* PearlOverlay.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D390FADEB325D8D54A957D /* PearlOverlay.m */; };
@@ -25,17 +26,21 @@
93D3954E96236384AFA00453 /* UIScrollView+PearlAdjustInsets.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D390FB3110DCCE68E600DC /* UIScrollView+PearlAdjustInsets.m */; }; 93D3954E96236384AFA00453 /* UIScrollView+PearlAdjustInsets.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D390FB3110DCCE68E600DC /* UIScrollView+PearlAdjustInsets.m */; };
93D3954FCE045A3CC7E804B7 /* MPUsersViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D399E571F61E50A9BF8FAF /* MPUsersViewController.m */; }; 93D3954FCE045A3CC7E804B7 /* MPUsersViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D399E571F61E50A9BF8FAF /* MPUsersViewController.m */; };
93D3957237D303DE2D38C267 /* MPAvatarCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39B381350802A194BF332 /* MPAvatarCell.m */; }; 93D3957237D303DE2D38C267 /* MPAvatarCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39B381350802A194BF332 /* MPAvatarCell.m */; };
93D3958C13B557F60F63C72B /* distribute in Resources */ = {isa = PBXBuildFile; fileRef = 93D3979016BF0C5B29D1340D /* distribute */; };
93D395B715D15F2B56F2A2EE /* mpw-types.c in Sources */ = {isa = PBXBuildFile; fileRef = 93D392C5A6572DB0EB5B82C8 /* mpw-types.c */; };
93D395F08A087F8A24689347 /* NSArray+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */; }; 93D395F08A087F8A24689347 /* NSArray+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */; };
93D39673DDC085BE72C34D7C /* MPPopdownSegue.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39B050DD5F55E9794EFD4 /* MPPopdownSegue.m */; }; 93D39673DDC085BE72C34D7C /* MPPopdownSegue.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39B050DD5F55E9794EFD4 /* MPPopdownSegue.m */; };
93D396AA30690B256F30378A /* PearlNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3956915634581E737B38C /* PearlNavigationController.m */; }; 93D396AA30690B256F30378A /* PearlNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3956915634581E737B38C /* PearlNavigationController.m */; };
93D396BA1C74C4A06FD86437 /* PearlOverlay.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D3942A356B639724157982 /* PearlOverlay.h */; }; 93D396BA1C74C4A06FD86437 /* PearlOverlay.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D3942A356B639724157982 /* PearlOverlay.h */; };
93D396D8B67DA6522CDBA142 /* MPCoachmarkViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3995B1D4DCE5A30D882BA /* MPCoachmarkViewController.m */; }; 93D396D8B67DA6522CDBA142 /* MPCoachmarkViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3995B1D4DCE5A30D882BA /* MPCoachmarkViewController.m */; };
93D39711B6D873F37E8B9B2D /* VERSION in Resources */ = {isa = PBXBuildFile; fileRef = 93D3944F4D55D2A37EF6021B /* VERSION */; };
93D397952F5635C793C24DF1 /* NSError+PearlFullDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39F9106F2CCFB94283188 /* NSError+PearlFullDescription.m */; }; 93D397952F5635C793C24DF1 /* NSError+PearlFullDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39F9106F2CCFB94283188 /* NSError+PearlFullDescription.m */; };
93D3980046016EFD05B35BC5 /* PearlUICollectionView.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39B1D8177A86C5B9EDDE3 /* PearlUICollectionView.h */; }; 93D3980046016EFD05B35BC5 /* PearlUICollectionView.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39B1D8177A86C5B9EDDE3 /* PearlUICollectionView.h */; };
93D398ECD7D1A0DEDDADF516 /* MPEmergencyViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39ACBA9F4878B6A1CC33B /* MPEmergencyViewController.m */; }; 93D398ECD7D1A0DEDDADF516 /* MPEmergencyViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39ACBA9F4878B6A1CC33B /* MPEmergencyViewController.m */; };
93D399246DC90F50913A1287 /* UIResponder+PearlFirstResponder.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39A1DDFA09AE2E14D26DC /* UIResponder+PearlFirstResponder.m */; }; 93D399246DC90F50913A1287 /* UIResponder+PearlFirstResponder.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39A1DDFA09AE2E14D26DC /* UIResponder+PearlFirstResponder.m */; };
93D3992FA1546E01F498F665 /* PearlNavigationController.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D398567FD02DB2647B8CF3 /* PearlNavigationController.h */; }; 93D3992FA1546E01F498F665 /* PearlNavigationController.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D398567FD02DB2647B8CF3 /* PearlNavigationController.h */; };
93D399433EA75E50656040CB /* Twitter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93D394077F8FAB8167647187 /* Twitter.framework */; }; 93D399433EA75E50656040CB /* Twitter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93D394077F8FAB8167647187 /* Twitter.framework */; };
93D39943D01E70DAC3B0DF76 /* mpw-util.c in Sources */ = {isa = PBXBuildFile; fileRef = 93D396C311C3725870343EE0 /* mpw-util.c */; };
93D399D7E08A142776A74CB8 /* MPOverlayViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D395105935859D71679931 /* MPOverlayViewController.m */; }; 93D399D7E08A142776A74CB8 /* MPOverlayViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D395105935859D71679931 /* MPOverlayViewController.m */; };
93D39A27F2506C6FEEF9C588 /* MPAlgorithmV2.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D399A8E3181B442D347CD7 /* MPAlgorithmV2.m */; }; 93D39A27F2506C6FEEF9C588 /* MPAlgorithmV2.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D399A8E3181B442D347CD7 /* MPAlgorithmV2.m */; };
93D39A53D76CA70786423458 /* UICollectionView+PearlReloadFromArray.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39246FC21C6E63E35D615 /* UICollectionView+PearlReloadFromArray.h */; }; 93D39A53D76CA70786423458 /* UICollectionView+PearlReloadFromArray.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39246FC21C6E63E35D615 /* UICollectionView+PearlReloadFromArray.h */; };
@@ -48,14 +53,17 @@
93D39B8F90F58A5D158DDBA3 /* MPPasswordsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3924EE15017F8A12CB436 /* MPPasswordsViewController.m */; }; 93D39B8F90F58A5D158DDBA3 /* MPPasswordsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3924EE15017F8A12CB436 /* MPPasswordsViewController.m */; };
93D39BA1EA3CAAC8A220B4A6 /* MPAppSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3916C1D8F1427DFBDEBCA /* MPAppSettingsViewController.m */; }; 93D39BA1EA3CAAC8A220B4A6 /* MPAppSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3916C1D8F1427DFBDEBCA /* MPAppSettingsViewController.m */; };
93D39C34FE35830EF5BE1D2A /* NSArray+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D396D04E57792A54D437AC /* NSArray+Indexing.h */; }; 93D39C34FE35830EF5BE1D2A /* NSArray+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D396D04E57792A54D437AC /* NSArray+Indexing.h */; };
93D39CEA71BD460ED4876920 /* build in Resources */ = {isa = PBXBuildFile; fileRef = 93D39B573E4DE98BAE518215 /* build */; };
93D39D38356F59DBEF934D70 /* MPAppDelegate_InApp.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D394C78C7B879C9AD9152C /* MPAppDelegate_InApp.m */; }; 93D39D38356F59DBEF934D70 /* MPAppDelegate_InApp.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D394C78C7B879C9AD9152C /* MPAppDelegate_InApp.m */; };
93D39D47FC623E91FC39D20C /* UICollectionView+PearlReloadFromArray.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3908DF8EABBD952065DC0 /* UICollectionView+PearlReloadFromArray.m */; }; 93D39D47FC623E91FC39D20C /* UICollectionView+PearlReloadFromArray.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3908DF8EABBD952065DC0 /* UICollectionView+PearlReloadFromArray.m */; };
93D39D596A2E376D6F6F5DA1 /* MPCombinedViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D393310223DDB35218467A /* MPCombinedViewController.m */; }; 93D39D596A2E376D6F6F5DA1 /* MPCombinedViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D393310223DDB35218467A /* MPCombinedViewController.m */; };
93D39D8F78978196D6ABDEDE /* MPNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39CC01630D0421205C4C4 /* MPNavigationController.m */; }; 93D39D8F78978196D6ABDEDE /* MPNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39CC01630D0421205C4C4 /* MPNavigationController.m */; };
93D39DF2E77B937D77C10414 /* install in Resources */ = {isa = PBXBuildFile; fileRef = 93D39ACD33E79386E6F33601 /* install */; };
93D39E281E3658B30550CB55 /* NSDictionary+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */; }; 93D39E281E3658B30550CB55 /* NSDictionary+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */; };
93D39E34FD28D24FE3442C48 /* UITextView+PearlAttributes.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3977321EB249981821AB0 /* UITextView+PearlAttributes.m */; }; 93D39E34FD28D24FE3442C48 /* UITextView+PearlAttributes.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3977321EB249981821AB0 /* UITextView+PearlAttributes.m */; };
93D39EAA4D064193074D3021 /* MPFixable.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39A813CA9D7E192261ED2 /* MPFixable.m */; }; 93D39EAA4D064193074D3021 /* MPFixable.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39A813CA9D7E192261ED2 /* MPFixable.m */; };
93D39F8A9254177891F38705 /* MPSetupViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39A28369954D147E239BA /* MPSetupViewController.m */; }; 93D39F8A9254177891F38705 /* MPSetupViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39A28369954D147E239BA /* MPSetupViewController.m */; };
93D39FAE1BB6A393BFE15FD0 /* mpw.bashrc in Resources */ = {isa = PBXBuildFile; fileRef = 93D39245A478883C672818F3 /* mpw.bashrc */; };
DA04E33E14B1E70400ECA4F3 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA04E33D14B1E70400ECA4F3 /* MobileCoreServices.framework */; }; DA04E33E14B1E70400ECA4F3 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA04E33D14B1E70400ECA4F3 /* MobileCoreServices.framework */; };
DA071BF3190187FE00179766 /* empty@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA071BF1190187FE00179766 /* empty@2x.png */; }; DA071BF3190187FE00179766 /* empty@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA071BF1190187FE00179766 /* empty@2x.png */; };
DA071BF4190187FE00179766 /* empty.png in Resources */ = {isa = PBXBuildFile; fileRef = DA071BF2190187FE00179766 /* empty.png */; }; DA071BF4190187FE00179766 /* empty.png in Resources */ = {isa = PBXBuildFile; fileRef = DA071BF2190187FE00179766 /* empty.png */; };
@@ -159,9 +167,9 @@
DA67460E18DE7F0C00DFE240 /* Exo2.0-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = DA67460A18DE7F0C00DFE240 /* Exo2.0-Regular.otf */; }; DA67460E18DE7F0C00DFE240 /* Exo2.0-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = DA67460A18DE7F0C00DFE240 /* Exo2.0-Regular.otf */; };
DA67460F18DE7F0C00DFE240 /* Exo2.0-ExtraBold.otf in Resources */ = {isa = PBXBuildFile; fileRef = DA67460B18DE7F0C00DFE240 /* Exo2.0-ExtraBold.otf */; }; DA67460F18DE7F0C00DFE240 /* Exo2.0-ExtraBold.otf in Resources */ = {isa = PBXBuildFile; fileRef = DA67460B18DE7F0C00DFE240 /* Exo2.0-ExtraBold.otf */; };
DA67461018DE7F0C00DFE240 /* Exo2.0-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = DA67460C18DE7F0C00DFE240 /* Exo2.0-Bold.otf */; }; DA67461018DE7F0C00DFE240 /* Exo2.0-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = DA67460C18DE7F0C00DFE240 /* Exo2.0-Bold.otf */; };
DA6773251A43B660004F356A /* libscryptenc-ios.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAFFC63E17EDDA7C007BB020 /* libscryptenc-ios.a */; };
DA69540617D975D900BF294E /* icon_gears.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD37841711E29500CF925C /* icon_gears.png */; }; DA69540617D975D900BF294E /* icon_gears.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD37841711E29500CF925C /* icon_gears.png */; };
DA69540717D975D900BF294E /* icon_gears@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD37851711E29500CF925C /* icon_gears@2x.png */; }; DA69540717D975D900BF294E /* icon_gears@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD37851711E29500CF925C /* icon_gears@2x.png */; };
DA72BD7519C133BF00E6ACFE /* libscryptenc-ios-dev.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA72BD7419C133BF00E6ACFE /* libscryptenc-ios-dev.a */; };
DA72BD7919C137DE00E6ACFE /* libopensslcrypto-ios-dev.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA72BD7719C137D500E6ACFE /* libopensslcrypto-ios-dev.a */; }; DA72BD7919C137DE00E6ACFE /* libopensslcrypto-ios-dev.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA72BD7719C137D500E6ACFE /* libopensslcrypto-ios-dev.a */; };
DA72BD7B19C1510C00E6ACFE /* UIView+FontScale.m in Sources */ = {isa = PBXBuildFile; fileRef = DACE2F6719BA6A2A0010F92E /* UIView+FontScale.m */; }; DA72BD7B19C1510C00E6ACFE /* UIView+FontScale.m in Sources */ = {isa = PBXBuildFile; fileRef = DACE2F6719BA6A2A0010F92E /* UIView+FontScale.m */; };
DA73049D194E022700E72520 /* ui_spinner.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD36511711E29400CF925C /* ui_spinner.png */; }; DA73049D194E022700E72520 /* ui_spinner.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD36511711E29400CF925C /* ui_spinner.png */; };
@@ -464,10 +472,12 @@
93D3916C1D8F1427DFBDEBCA /* MPAppSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAppSettingsViewController.m; sourceTree = "<group>"; }; 93D3916C1D8F1427DFBDEBCA /* MPAppSettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAppSettingsViewController.m; sourceTree = "<group>"; };
93D391943675426839501BB8 /* MPLogsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPLogsViewController.h; sourceTree = "<group>"; }; 93D391943675426839501BB8 /* MPLogsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPLogsViewController.h; sourceTree = "<group>"; };
93D391AA32F24290C424438E /* NSNotificationCenter+PearlEasyCleanup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSNotificationCenter+PearlEasyCleanup.h"; sourceTree = "<group>"; }; 93D391AA32F24290C424438E /* NSNotificationCenter+PearlEasyCleanup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSNotificationCenter+PearlEasyCleanup.h"; sourceTree = "<group>"; };
93D39245A478883C672818F3 /* mpw.bashrc */ = {isa = PBXFileReference; lastKnownFileType = file.bashrc; path = mpw.bashrc; sourceTree = "<group>"; };
93D39246FC21C6E63E35D615 /* UICollectionView+PearlReloadFromArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UICollectionView+PearlReloadFromArray.h"; sourceTree = "<group>"; }; 93D39246FC21C6E63E35D615 /* UICollectionView+PearlReloadFromArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UICollectionView+PearlReloadFromArray.h"; sourceTree = "<group>"; };
93D3924D6F77E6BF41AC32D3 /* MPRootSegue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPRootSegue.h; sourceTree = "<group>"; }; 93D3924D6F77E6BF41AC32D3 /* MPRootSegue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPRootSegue.h; sourceTree = "<group>"; };
93D3924EE15017F8A12CB436 /* MPPasswordsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordsViewController.m; sourceTree = "<group>"; }; 93D3924EE15017F8A12CB436 /* MPPasswordsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordsViewController.m; sourceTree = "<group>"; };
93D392876BE5C011DE73B43F /* MPPopdownSegue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPopdownSegue.h; sourceTree = "<group>"; }; 93D392876BE5C011DE73B43F /* MPPopdownSegue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPopdownSegue.h; sourceTree = "<group>"; };
93D392C5A6572DB0EB5B82C8 /* mpw-types.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-types.c"; sourceTree = "<group>"; };
93D393310223DDB35218467A /* MPCombinedViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCombinedViewController.m; sourceTree = "<group>"; }; 93D393310223DDB35218467A /* MPCombinedViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCombinedViewController.m; sourceTree = "<group>"; };
93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+Indexing.h"; sourceTree = "<group>"; }; 93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+Indexing.h"; sourceTree = "<group>"; };
93D393BB973253D4BAAC84AA /* PearlEMail.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlEMail.m; sourceTree = "<group>"; }; 93D393BB973253D4BAAC84AA /* PearlEMail.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlEMail.m; sourceTree = "<group>"; };
@@ -475,19 +485,26 @@
93D394077F8FAB8167647187 /* Twitter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Twitter.framework; path = System/Library/Frameworks/Twitter.framework; sourceTree = SDKROOT; }; 93D394077F8FAB8167647187 /* Twitter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Twitter.framework; path = System/Library/Frameworks/Twitter.framework; sourceTree = SDKROOT; };
93D3942A356B639724157982 /* PearlOverlay.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlOverlay.h; sourceTree = "<group>"; }; 93D3942A356B639724157982 /* PearlOverlay.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlOverlay.h; sourceTree = "<group>"; };
93D394482BB07F90E8FD1314 /* UIResponder+PearlFirstResponder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIResponder+PearlFirstResponder.h"; sourceTree = "<group>"; }; 93D394482BB07F90E8FD1314 /* UIResponder+PearlFirstResponder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIResponder+PearlFirstResponder.h"; sourceTree = "<group>"; };
93D3944F4D55D2A37EF6021B /* VERSION */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = VERSION; sourceTree = "<group>"; };
93D394C78C7B879C9AD9152C /* MPAppDelegate_InApp.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAppDelegate_InApp.m; sourceTree = "<group>"; }; 93D394C78C7B879C9AD9152C /* MPAppDelegate_InApp.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAppDelegate_InApp.m; sourceTree = "<group>"; };
93D395105935859D71679931 /* MPOverlayViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPOverlayViewController.m; sourceTree = "<group>"; }; 93D395105935859D71679931 /* MPOverlayViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPOverlayViewController.m; sourceTree = "<group>"; };
93D3956915634581E737B38C /* PearlNavigationController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlNavigationController.m; sourceTree = "<group>"; }; 93D3956915634581E737B38C /* PearlNavigationController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlNavigationController.m; sourceTree = "<group>"; };
93D3957D76F71A652716EECC /* MPStoreViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPStoreViewController.m; sourceTree = "<group>"; }; 93D3957D76F71A652716EECC /* MPStoreViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPStoreViewController.m; sourceTree = "<group>"; };
93D3969393A3A46BD27D7078 /* mpw-algorithm.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-algorithm.c"; sourceTree = "<group>"; };
93D396C311C3725870343EE0 /* mpw-util.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-util.c"; sourceTree = "<group>"; };
93D396D04E57792A54D437AC /* NSArray+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Indexing.h"; sourceTree = "<group>"; }; 93D396D04E57792A54D437AC /* NSArray+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Indexing.h"; sourceTree = "<group>"; };
93D3970502644794E8A027BE /* MPNavigationController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNavigationController.h; sourceTree = "<group>"; }; 93D3970502644794E8A027BE /* MPNavigationController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNavigationController.h; sourceTree = "<group>"; };
93D3971FE104BB4052484151 /* MPUsersViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUsersViewController.h; sourceTree = "<group>"; }; 93D3971FE104BB4052484151 /* MPUsersViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUsersViewController.h; sourceTree = "<group>"; };
93D397288D0655822A73A539 /* mpw-tests-util.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-tests-util.c"; sourceTree = "<group>"; };
93D39730673227EFF6DEFF19 /* MPSetupViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSetupViewController.h; sourceTree = "<group>"; }; 93D39730673227EFF6DEFF19 /* MPSetupViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSetupViewController.h; sourceTree = "<group>"; };
93D3977321EB249981821AB0 /* UITextView+PearlAttributes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UITextView+PearlAttributes.m"; sourceTree = "<group>"; }; 93D3977321EB249981821AB0 /* UITextView+PearlAttributes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UITextView+PearlAttributes.m"; sourceTree = "<group>"; };
93D3979016BF0C5B29D1340D /* distribute */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = distribute; sourceTree = "<group>"; };
93D3979190DACEBD1F6AE9F4 /* MPLogsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPLogsViewController.m; sourceTree = "<group>"; }; 93D3979190DACEBD1F6AE9F4 /* MPLogsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPLogsViewController.m; sourceTree = "<group>"; };
93D397F4BAFFF7CF3F1B21A4 /* NSPersistentStore+PearlMigration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSPersistentStore+PearlMigration.h"; sourceTree = "<group>"; }; 93D397F4BAFFF7CF3F1B21A4 /* NSPersistentStore+PearlMigration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSPersistentStore+PearlMigration.h"; sourceTree = "<group>"; };
93D398121C8F063A3637144E /* mpw-cli.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-cli.c"; sourceTree = "<group>"; };
93D398567FD02DB2647B8CF3 /* PearlNavigationController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlNavigationController.h; sourceTree = "<group>"; }; 93D398567FD02DB2647B8CF3 /* PearlNavigationController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlNavigationController.h; sourceTree = "<group>"; };
93D398C95847261903D781D3 /* NSError+PearlFullDescription.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSError+PearlFullDescription.h"; sourceTree = "<group>"; }; 93D398C95847261903D781D3 /* NSError+PearlFullDescription.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSError+PearlFullDescription.h"; sourceTree = "<group>"; };
93D3990D850D76A94C6B7A4D /* mpw-algorithm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-algorithm.h"; sourceTree = "<group>"; };
93D3990E0CD1B5CF9FBB2C07 /* MPWebViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPWebViewController.m; sourceTree = "<group>"; }; 93D3990E0CD1B5CF9FBB2C07 /* MPWebViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPWebViewController.m; sourceTree = "<group>"; };
93D399493FEDDE74DD1A0C15 /* MPRootSegue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPRootSegue.m; sourceTree = "<group>"; }; 93D399493FEDDE74DD1A0C15 /* MPRootSegue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPRootSegue.m; sourceTree = "<group>"; };
93D3995B1D4DCE5A30D882BA /* MPCoachmarkViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCoachmarkViewController.m; sourceTree = "<group>"; }; 93D3995B1D4DCE5A30D882BA /* MPCoachmarkViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCoachmarkViewController.m; sourceTree = "<group>"; };
@@ -505,10 +522,15 @@
93D39AA10CD00D05937671B1 /* UITextView+PearlAttributes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UITextView+PearlAttributes.h"; sourceTree = "<group>"; }; 93D39AA10CD00D05937671B1 /* UITextView+PearlAttributes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UITextView+PearlAttributes.h"; sourceTree = "<group>"; };
93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+Indexing.m"; sourceTree = "<group>"; }; 93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+Indexing.m"; sourceTree = "<group>"; };
93D39ACBA9F4878B6A1CC33B /* MPEmergencyViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPEmergencyViewController.m; sourceTree = "<group>"; }; 93D39ACBA9F4878B6A1CC33B /* MPEmergencyViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPEmergencyViewController.m; sourceTree = "<group>"; };
93D39ACD33E79386E6F33601 /* install */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = install; sourceTree = "<group>"; };
93D39AFD17CBE324D745DAB0 /* mpw-types.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-types.h"; sourceTree = "<group>"; };
93D39B050DD5F55E9794EFD4 /* MPPopdownSegue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPopdownSegue.m; sourceTree = "<group>"; }; 93D39B050DD5F55E9794EFD4 /* MPPopdownSegue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPopdownSegue.m; sourceTree = "<group>"; };
93D39B1D8177A86C5B9EDDE3 /* PearlUICollectionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlUICollectionView.h; sourceTree = "<group>"; }; 93D39B1D8177A86C5B9EDDE3 /* PearlUICollectionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlUICollectionView.h; sourceTree = "<group>"; };
93D39B381350802A194BF332 /* MPAvatarCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAvatarCell.m; sourceTree = "<group>"; }; 93D39B381350802A194BF332 /* MPAvatarCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAvatarCell.m; sourceTree = "<group>"; };
93D39B455A71EC98C749E623 /* MPOverlayViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPOverlayViewController.h; sourceTree = "<group>"; }; 93D39B455A71EC98C749E623 /* MPOverlayViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPOverlayViewController.h; sourceTree = "<group>"; };
93D39B48CAB328B3E0C0C67D /* mpw-tests-util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-tests-util.h"; sourceTree = "<group>"; };
93D39B573E4DE98BAE518215 /* build */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = build; sourceTree = "<group>"; };
93D39B70138D0E28F7D5E86B /* mpw-bench.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-bench.c"; sourceTree = "<group>"; };
93D39BAA71DE51B4D8A1286C /* MPCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCell.m; sourceTree = "<group>"; }; 93D39BAA71DE51B4D8A1286C /* MPCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCell.m; sourceTree = "<group>"; };
93D39C41A27AA42D044D68AE /* NSString+MPMarkDown.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+MPMarkDown.m"; sourceTree = "<group>"; }; 93D39C41A27AA42D044D68AE /* NSString+MPMarkDown.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+MPMarkDown.m"; sourceTree = "<group>"; };
93D39C426E03358384018E85 /* MPAnswersViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAnswersViewController.m; sourceTree = "<group>"; }; 93D39C426E03358384018E85 /* MPAnswersViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAnswersViewController.m; sourceTree = "<group>"; };
@@ -517,12 +539,14 @@
93D39CC01630D0421205C4C4 /* MPNavigationController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNavigationController.m; sourceTree = "<group>"; }; 93D39CC01630D0421205C4C4 /* MPNavigationController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNavigationController.m; sourceTree = "<group>"; };
93D39CDD434AFD6E1B0DA359 /* MPEmergencyViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPEmergencyViewController.h; sourceTree = "<group>"; }; 93D39CDD434AFD6E1B0DA359 /* MPEmergencyViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPEmergencyViewController.h; sourceTree = "<group>"; };
93D39CECA10BCCB0BA581BF1 /* MPAppDelegate_InApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAppDelegate_InApp.h; sourceTree = "<group>"; }; 93D39CECA10BCCB0BA581BF1 /* MPAppDelegate_InApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAppDelegate_InApp.h; sourceTree = "<group>"; };
93D39CF7DB942C69D1C5D6BE /* mpw-util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-util.h"; sourceTree = "<group>"; };
93D39CF8ADF4542CDC4CD385 /* MPCombinedViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPCombinedViewController.h; sourceTree = "<group>"; }; 93D39CF8ADF4542CDC4CD385 /* MPCombinedViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPCombinedViewController.h; sourceTree = "<group>"; };
93D39D6604447D7708039155 /* MPAnswersViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAnswersViewController.h; sourceTree = "<group>"; }; 93D39D6604447D7708039155 /* MPAnswersViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAnswersViewController.h; sourceTree = "<group>"; };
93D39D8A953779B35403AF6E /* PearlUICollectionView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlUICollectionView.m; sourceTree = "<group>"; }; 93D39D8A953779B35403AF6E /* PearlUICollectionView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlUICollectionView.m; sourceTree = "<group>"; };
93D39DA27D768B53C8B1330C /* MPAvatarCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAvatarCell.h; sourceTree = "<group>"; }; 93D39DA27D768B53C8B1330C /* MPAvatarCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPAvatarCell.h; sourceTree = "<group>"; };
93D39DE2CB351D4E3789462B /* UIScrollView+PearlAdjustInsets.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIScrollView+PearlAdjustInsets.h"; sourceTree = "<group>"; }; 93D39DE2CB351D4E3789462B /* UIScrollView+PearlAdjustInsets.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIScrollView+PearlAdjustInsets.h"; sourceTree = "<group>"; };
93D39DEA995041A13DC9CAF7 /* MPPasswordCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordCell.m; sourceTree = "<group>"; }; 93D39DEA995041A13DC9CAF7 /* MPPasswordCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordCell.m; sourceTree = "<group>"; };
93D39E71D6BAECEC4CD886F4 /* bashlib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = bashlib; sourceTree = "<group>"; };
93D39E7A12CC352B2825AA66 /* MPPasswordsSegue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordsSegue.m; sourceTree = "<group>"; }; 93D39E7A12CC352B2825AA66 /* MPPasswordsSegue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordsSegue.m; sourceTree = "<group>"; };
93D39F556F2F142740A65E59 /* MPWebViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPWebViewController.h; sourceTree = "<group>"; }; 93D39F556F2F142740A65E59 /* MPWebViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPWebViewController.h; sourceTree = "<group>"; };
93D39F7C9F47BF6387FBC5C3 /* PearlEMail.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlEMail.h; sourceTree = "<group>"; }; 93D39F7C9F47BF6387FBC5C3 /* PearlEMail.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlEMail.h; sourceTree = "<group>"; };
@@ -634,8 +658,9 @@
DA67460A18DE7F0C00DFE240 /* Exo2.0-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Exo2.0-Regular.otf"; sourceTree = "<group>"; }; DA67460A18DE7F0C00DFE240 /* Exo2.0-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Exo2.0-Regular.otf"; sourceTree = "<group>"; };
DA67460B18DE7F0C00DFE240 /* Exo2.0-ExtraBold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Exo2.0-ExtraBold.otf"; sourceTree = "<group>"; }; DA67460B18DE7F0C00DFE240 /* Exo2.0-ExtraBold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Exo2.0-ExtraBold.otf"; sourceTree = "<group>"; };
DA67460C18DE7F0C00DFE240 /* Exo2.0-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Exo2.0-Bold.otf"; sourceTree = "<group>"; }; DA67460C18DE7F0C00DFE240 /* Exo2.0-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Exo2.0-Bold.otf"; sourceTree = "<group>"; };
DA6774351A4749CC004F356A /* mpw-tests.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-tests.c"; sourceTree = "<group>"; };
DA70EC7F1811B13C00F65DB2 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; DA70EC7F1811B13C00F65DB2 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; };
DA72BD7419C133BF00E6ACFE /* libscryptenc-ios-dev.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libscryptenc-ios-dev.a"; sourceTree = "<group>"; }; DA72BD7419C133BF00E6ACFE /* libscryptenc-ios-sim.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libscryptenc-ios-sim.a"; sourceTree = "<group>"; };
DA72BD7719C137D500E6ACFE /* libopensslcrypto-ios-dev.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libopensslcrypto-ios-dev.a"; sourceTree = "<group>"; }; DA72BD7719C137D500E6ACFE /* libopensslcrypto-ios-dev.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libopensslcrypto-ios-dev.a"; sourceTree = "<group>"; };
DA854C8118D4CFBF00106317 /* avatar-add@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-add@2x.png"; sourceTree = "<group>"; }; DA854C8118D4CFBF00106317 /* avatar-add@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-add@2x.png"; sourceTree = "<group>"; };
DA854C8218D4CFBF00106317 /* avatar-add.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-add.png"; sourceTree = "<group>"; }; DA854C8218D4CFBF00106317 /* avatar-add.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-add.png"; sourceTree = "<group>"; };
@@ -1566,8 +1591,8 @@
files = ( files = (
DA4DA1DA1564471F00F6F596 /* libuicolor-utilities.a in Frameworks */, DA4DA1DA1564471F00F6F596 /* libuicolor-utilities.a in Frameworks */,
DA4DA1D91564471A00F6F596 /* libjrswizzle.a in Frameworks */, DA4DA1D91564471A00F6F596 /* libjrswizzle.a in Frameworks */,
DA72BD7519C133BF00E6ACFE /* libscryptenc-ios-dev.a in Frameworks */,
DAC77CAE148291A600BCF976 /* Foundation.framework in Frameworks */, DAC77CAE148291A600BCF976 /* Foundation.framework in Frameworks */,
DA6773251A43B660004F356A /* libscryptenc-ios.a in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -1582,6 +1607,31 @@
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
93D39A2239FFFE6BEC83E191 /* C */ = {
isa = PBXGroup;
children = (
93D39E71D6BAECEC4CD886F4 /* bashlib */,
93D39B573E4DE98BAE518215 /* build */,
93D3979016BF0C5B29D1340D /* distribute */,
93D39ACD33E79386E6F33601 /* install */,
93D3969393A3A46BD27D7078 /* mpw-algorithm.c */,
93D3990D850D76A94C6B7A4D /* mpw-algorithm.h */,
93D39B70138D0E28F7D5E86B /* mpw-bench.c */,
93D398121C8F063A3637144E /* mpw-cli.c */,
93D397288D0655822A73A539 /* mpw-tests-util.c */,
93D39B48CAB328B3E0C0C67D /* mpw-tests-util.h */,
DA6774351A4749CC004F356A /* mpw-tests.c */,
93D392C5A6572DB0EB5B82C8 /* mpw-types.c */,
93D39AFD17CBE324D745DAB0 /* mpw-types.h */,
93D396C311C3725870343EE0 /* mpw-util.c */,
93D39CF7DB942C69D1C5D6BE /* mpw-util.h */,
93D39245A478883C672818F3 /* mpw.bashrc */,
93D3944F4D55D2A37EF6021B /* VERSION */,
);
name = C;
path = ../../C;
sourceTree = "<group>";
};
DA24EBB019DAD4D000FF010B /* ios */ = { DA24EBB019DAD4D000FF010B /* ios */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -1672,6 +1722,7 @@
DA5BFA39147E415C00F98B1E = { DA5BFA39147E415C00F98B1E = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
93D39A2239FFFE6BEC83E191 /* C */,
DABD3B9F1711E2DB00CF925C /* ObjC */, DABD3B9F1711E2DB00CF925C /* ObjC */,
DACA23B41705DF7D002C6C22 /* Resources */, DACA23B41705DF7D002C6C22 /* Resources */,
DACA22121705DDC5002C6C22 /* External */, DACA22121705DDC5002C6C22 /* External */,
@@ -1731,7 +1782,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DA72BD7719C137D500E6ACFE /* libopensslcrypto-ios-dev.a */, DA72BD7719C137D500E6ACFE /* libopensslcrypto-ios-dev.a */,
DA72BD7419C133BF00E6ACFE /* libscryptenc-ios-dev.a */, DA72BD7419C133BF00E6ACFE /* libscryptenc-ios-sim.a */,
DAE8E65119867AB500416A0F /* libopensslcrypto-ios.a */, DAE8E65119867AB500416A0F /* libopensslcrypto-ios.a */,
DAFFC63E17EDDA7C007BB020 /* libscryptenc-ios.a */, DAFFC63E17EDDA7C007BB020 /* libscryptenc-ios.a */,
DA5E5C6417248959003798D8 /* include */, DA5E5C6417248959003798D8 /* include */,
@@ -3473,6 +3524,12 @@
DAA1762819D89B610044227B /* thumb_ios_integration.png in Resources */, DAA1762819D89B610044227B /* thumb_ios_integration.png in Resources */,
DA5A09EB171BB0F7005284AB /* unlocked@2x.png in Resources */, DA5A09EB171BB0F7005284AB /* unlocked@2x.png in Resources */,
DAA1764F19D8B82B0044227B /* counter@2x.png in Resources */, DAA1764F19D8B82B0044227B /* counter@2x.png in Resources */,
93D39CEA71BD460ED4876920 /* build in Resources */,
93D390228D0B8ED51C0AFDB4 /* bashlib in Resources */,
93D39DF2E77B937D77C10414 /* install in Resources */,
93D39711B6D873F37E8B9B2D /* VERSION in Resources */,
93D3958C13B557F60F63C72B /* distribute in Resources */,
93D39FAE1BB6A393BFE15FD0 /* mpw.bashrc in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -3583,6 +3640,8 @@
93D39A27F2506C6FEEF9C588 /* MPAlgorithmV2.m in Sources */, 93D39A27F2506C6FEEF9C588 /* MPAlgorithmV2.m in Sources */,
93D39B429C67A62E29DC02DA /* MPRootSegue.m in Sources */, 93D39B429C67A62E29DC02DA /* MPRootSegue.m in Sources */,
93D392FD5E2052F7D7DB3774 /* NSString+MPMarkDown.m in Sources */, 93D392FD5E2052F7D7DB3774 /* NSString+MPMarkDown.m in Sources */,
93D395B715D15F2B56F2A2EE /* mpw-types.c in Sources */,
93D39943D01E70DAC3B0DF76 /* mpw-util.c in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -4088,6 +4147,7 @@
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"\"$(SRCROOT)/../../../External/Pearl/Pearl-Crypto/lib\"", "\"$(SRCROOT)/../../../External/Pearl/Pearl-Crypto/lib\"",
"$(inherited)", "$(inherited)",
"/Users/lhunath/Documents/workspace/lyndir/MasterPassword/External/Pearl/Pearl-Crypto/lib",
); );
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PUBLIC_HEADERS_FOLDER_PATH = "../../BuildProductsPath/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/include"; PUBLIC_HEADERS_FOLDER_PATH = "../../BuildProductsPath/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/include";
@@ -4188,6 +4248,7 @@
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"\"$(SRCROOT)/../../../External/Pearl/Pearl-Crypto/lib\"", "\"$(SRCROOT)/../../../External/Pearl/Pearl-Crypto/lib\"",
"$(inherited)", "$(inherited)",
"/Users/lhunath/Documents/workspace/lyndir/MasterPassword/External/Pearl/Pearl-Crypto/lib",
); );
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PUBLIC_HEADERS_FOLDER_PATH = include; PUBLIC_HEADERS_FOLDER_PATH = include;
@@ -4204,6 +4265,7 @@
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"\"$(SRCROOT)/../../../External/Pearl/Pearl-Crypto/lib\"", "\"$(SRCROOT)/../../../External/Pearl/Pearl-Crypto/lib\"",
"$(inherited)", "$(inherited)",
"/Users/lhunath/Documents/workspace/lyndir/MasterPassword/External/Pearl/Pearl-Crypto/lib",
); );
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PUBLIC_HEADERS_FOLDER_PATH = "../../BuildProductsPath/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/include"; PUBLIC_HEADERS_FOLDER_PATH = "../../BuildProductsPath/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/include";

Binary file not shown.

View File

@@ -1 +1 @@
9d7799c8147b119a175725a7058fa20dcdb3faef 1fbb6b0754c9ea1ecf733c260dc06200d2eeed6c