Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
438daf27ee | ||
|
|
aa6634970a | ||
|
|
9052416786 | ||
|
|
9d19eaf667 | ||
|
|
7ae9afa63a | ||
|
|
3d856b3773 | ||
|
|
7617b2382a | ||
|
|
a03dcf6859 | ||
|
|
57769ba199 | ||
|
|
6304b3a619 | ||
|
|
d1649f3c33 | ||
|
|
80f507b4cc | ||
|
|
f8a665db65 | ||
|
|
b15f2a8a26 | ||
|
|
e9094097a2 | ||
|
|
bea6ac5e68 | ||
|
|
778533ac7f | ||
|
|
83fcde5bd0 | ||
|
|
c9ec5874d3 | ||
|
|
4ce5fd25bc | ||
|
|
1ed28ebc9b | ||
|
|
c03199f7e5 | ||
|
|
9f10bcdec4 | ||
|
|
82c96ddfe3 | ||
|
|
c0fea076b9 | ||
|
|
b779ff5d1c | ||
|
|
73c10906e3 | ||
|
|
0ccd545dd4 | ||
|
|
49da0b47c7 | ||
|
|
672b28a5b7 | ||
|
|
2dbada3c7c | ||
|
|
3dbc105fbd | ||
|
|
43d55211b0 | ||
|
|
f170e9df69 | ||
|
|
1fbb6b0754 | ||
|
|
4c526d6f08 | ||
|
|
a62ae8c757 | ||
|
|
f2eb53569b | ||
|
|
c2a6a3d035 | ||
|
|
97dcc65eac | ||
|
|
1bd76dbb61 | ||
|
|
0fdf894bf0 | ||
|
|
19202e07d4 | ||
|
|
84b624aea2 | ||
|
|
c7ac5087b3 | ||
|
|
4ff8cd6d90 | ||
|
|
3f4558da2b | ||
|
|
b976e79b0f | ||
|
|
3d064fa68d | ||
|
|
1a1e024178 | ||
|
|
4876d62b56 | ||
|
|
8006b7096f | ||
|
|
a82ce7310d | ||
|
|
ae08cb62c5 | ||
|
|
c48fba6c01 | ||
|
|
3db25e7e3b | ||
|
|
1f7a49378b | ||
|
|
37ec21f5be | ||
|
|
2b8498f569 | ||
|
|
5c4fc61a12 | ||
|
|
c0ec65bbae |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -37,5 +37,6 @@ MasterPassword/C/*.o
|
||||
MasterPassword/C/mpw-*.tar.gz
|
||||
MasterPassword/C/mpw
|
||||
MasterPassword/C/mpw-bench
|
||||
MasterPassword/C/mpw-tests
|
||||
MasterPassword/C/lib/*/*
|
||||
!MasterPassword/C/lib/*/.source
|
||||
|
||||
2
.idea/inspectionProfiles/Project_Default.xml
generated
2
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -2,6 +2,7 @@
|
||||
<profile version="1.0" is_locked="false">
|
||||
<option name="myName" value="Project Default" />
|
||||
<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="ImplicitIntegerAndEnumConversion" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<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="OCUnusedMacroInspection" 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="UnavailableInDeploymentTarget" enabled="true" level="INFO" enabled_by_default="true" />
|
||||
<inspection_tool class="UnusedLocalVariable" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
|
||||
2
External/Pearl
vendored
2
External/Pearl
vendored
Submodule External/Pearl updated: 2237aaf429...6abdab7168
@@ -27,7 +27,7 @@
|
||||
|
||||
# ______________________________________________________________________
|
||||
# | |
|
||||
# | .:: TABLE OF CONTENTS ::. |
|
||||
# | .: TABLE OF CONTENTS :. |
|
||||
# |______________________________________________________________________|
|
||||
#
|
||||
# chr decimal
|
||||
@@ -66,12 +66,6 @@
|
||||
# readwhile command [args]
|
||||
# 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 an event at a certain importance level.
|
||||
# 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.
|
||||
@@ -177,7 +171,7 @@ genToc() {
|
||||
|
||||
# ______________________________________________________________________
|
||||
# | |
|
||||
# | .:: GLOBAL DECLARATIONS ::. |
|
||||
# | .: GLOBAL DECLARATIONS :. |
|
||||
# |______________________________________________________________________|
|
||||
|
||||
# Variables for convenience sequences.
|
||||
@@ -190,8 +184,8 @@ runner=( '> >' \
|
||||
|
||||
# Variables for terminal requests.
|
||||
[[ -t 2 && $TERM != dumb ]] && {
|
||||
COLUMNS=$( tput cols || tput co ) # Columns in a line
|
||||
LINES=$( tput lines || tput li ) # Lines on screen
|
||||
COLUMNS=$({ tput cols || tput co;} 2>&3) # Columns in a line
|
||||
LINES=$({ tput lines || tput li;} 2>&3) # Lines on screen
|
||||
alt=$( tput smcup || tput ti ) # Start alt display
|
||||
ealt=$( tput rmcup || tput te ) # End alt display
|
||||
hide=$( tput civis || tput vi ) # Hide cursor
|
||||
@@ -230,7 +224,7 @@ runner=( '> >' \
|
||||
tput eA; tput as;
|
||||
tput ac; tput ae; } ) # Drawing characters
|
||||
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 ____________________________________________________________|
|
||||
#
|
||||
@@ -1566,7 +1543,7 @@ stackTrace() {
|
||||
|
||||
# ______________________________________________________________________
|
||||
# | |
|
||||
# | .:: ENTRY POINT ::. |
|
||||
# | .: ENTRY POINT :. |
|
||||
# |______________________________________________________________________|
|
||||
|
||||
# Make sure this file is sourced and not executed.
|
||||
@@ -1586,6 +1563,6 @@ stackTrace() {
|
||||
}
|
||||
|
||||
:
|
||||
: .:: END SOURCING ::.
|
||||
: .: END SOURCING :.
|
||||
: ______________________________________________________________________
|
||||
:
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
# try ./build -lrt instead.
|
||||
# - 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.
|
||||
# - Take a look at the "Optional features" section. Some features have dependencies,
|
||||
# either make sure you have them or disable those features.
|
||||
#
|
||||
# BUGS
|
||||
# masterpassword@lyndir.com
|
||||
@@ -31,10 +33,14 @@ else
|
||||
# Modify here or override using targets='mpw mpw-bench' ./build
|
||||
targets=(
|
||||
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
|
||||
|
||||
# Optional features.
|
||||
mpw_color=0 # Colorized Identicon, requires libncurses-dev
|
||||
|
||||
|
||||
### DEPENDENCIES
|
||||
|
||||
@@ -128,12 +134,18 @@ fetchSource() (
|
||||
echo >&2 "error: into: $PWD"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for patch in "${patches[@]}"; do
|
||||
echo
|
||||
echo "Patching: ${PWD##*/}, for $patch..."
|
||||
patch -p0 < ../"${PWD##*/}-$patch.patch"
|
||||
done
|
||||
)
|
||||
depend() {
|
||||
|
||||
echo
|
||||
echo "Checking dependency: $1..."
|
||||
[[ -e "lib/$1/.built" ]] && return
|
||||
[[ -e "lib/include/$1" ]] && return
|
||||
|
||||
pushd "lib/$1"
|
||||
fetchSource
|
||||
@@ -169,7 +181,8 @@ depend() {
|
||||
fi
|
||||
|
||||
make
|
||||
date > .built
|
||||
install -d "../include/$1/"
|
||||
find . -name '*.h' -exec install -m 444 {} "../include/$1/" \;
|
||||
else
|
||||
echo >&2 "error: Don't know how to build: $1"
|
||||
exit 1
|
||||
@@ -186,13 +199,9 @@ mpw() {
|
||||
echo "Building target: $target..."
|
||||
CFLAGS=(
|
||||
# include paths
|
||||
-I"lib/scrypt/lib" -I"lib/scrypt/libcperciva"
|
||||
-I"lib/include"
|
||||
)
|
||||
LDFLAGS=(
|
||||
# library paths
|
||||
-L"." -L"lib/scrypt"
|
||||
# link libraries
|
||||
-l"crypto" -l"curses"
|
||||
# scrypt
|
||||
"lib/scrypt/scrypt-crypto_aesctr.o"
|
||||
"lib/scrypt/scrypt-sha256.o"
|
||||
@@ -200,10 +209,19 @@ mpw() {
|
||||
"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"
|
||||
)
|
||||
# optional features
|
||||
(( mpw_color )) && CFLAGS+=( -DCOLOR ) LDFLAGS+=( -l"curses" )
|
||||
|
||||
cc "${CFLAGS[@]}" -c types.c -o types.o "$@"
|
||||
cc "${CFLAGS[@]}" "${LDFLAGS[@]}" "types.o" mpw.c -o mpw "$@"
|
||||
cc "${CFLAGS[@]}" "$@" -c mpw-algorithm.c -o mpw-algorithm.o
|
||||
cc "${CFLAGS[@]}" "$@" -c mpw-types.c -o mpw-types.o
|
||||
cc "${CFLAGS[@]}" "$@" -c mpw-util.c -o mpw-util.o
|
||||
cc "${CFLAGS[@]}" "${LDFLAGS[@]}" "$@" "mpw-algorithm.o" "mpw-types.o" "mpw-util.o" \
|
||||
mpw-cli.c -o mpw
|
||||
echo "done! Now run ./install or use ./mpw"
|
||||
}
|
||||
|
||||
@@ -217,15 +235,9 @@ mpw-bench() {
|
||||
echo "Building target: $target..."
|
||||
CFLAGS=(
|
||||
# include paths
|
||||
-I"lib/scrypt/lib" -I"lib/scrypt/libcperciva"
|
||||
-I"lib/bcrypt"
|
||||
-I"lib/include"
|
||||
)
|
||||
LDFLAGS=(
|
||||
# library paths
|
||||
-L"." -L"lib/scrypt"
|
||||
-L"lib/bcrypt"
|
||||
# libraries
|
||||
-l"crypto"
|
||||
# scrypt
|
||||
"lib/scrypt/scrypt-crypto_aesctr.o"
|
||||
"lib/scrypt/scrypt-sha256.o"
|
||||
@@ -238,14 +250,51 @@ mpw-bench() {
|
||||
"lib/bcrypt/crypt_gensalt.o"
|
||||
"lib/bcrypt/wrapper.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[@]}" "types.o" mpw-bench.c -o mpw-bench "$@"
|
||||
cc "${CFLAGS[@]}" "${LDFLAGS[@]}" "$@" "mpw-algorithm.o" "mpw-types.o" "mpw-util.o" \
|
||||
mpw-bench.c -o 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
|
||||
|
||||
haslib() {
|
||||
|
||||
@@ -4,14 +4,14 @@ set -e
|
||||
cd "${BASH_SOURCE%/*}"
|
||||
tag=$(git describe)
|
||||
commit=$(git describe --long --dirty)
|
||||
[[ $tag && $commit = $tag-* ]] || exit 1
|
||||
[[ $tag && $commit = $tag* ]] || exit 1
|
||||
git show --show-signature --pretty=format:%H --quiet "$tag" > VERSION
|
||||
|
||||
mpwArchive=mpw-$commit.tar.gz
|
||||
[[ -e $mpwArchive ]] && echo "WARNING: $mpwArchive already exists. Will overwrite."
|
||||
read -n1 -p "Will prepare and release $mpwArchive. Press a key to continue or ^C to abort."
|
||||
|
||||
git ls-files -z . | xargs -0 tar -cvzf "$mpwArchive"
|
||||
git ls-files -z . | xargs -0 tar -Lcvzf "$mpwArchive"
|
||||
echo "$mpwArchive ready, SHA256: $(openssl sha -sha256 < "$mpwArchive")"
|
||||
|
||||
cd ../../Site/current
|
||||
|
||||
@@ -45,8 +45,8 @@ fi
|
||||
echo
|
||||
|
||||
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
|
||||
printf 'export MP_USERNAME=%q\n' "$MP_USERNAME" >> ~/.bashrc
|
||||
if MP_FULLNAME=$(ask "Your full name:") && [[ $MP_FULLNAME ]] ; then
|
||||
printf 'export MP_FULLNAME=%q\n' "$MP_FULLNAME" >> ~/.bashrc
|
||||
fi
|
||||
echo
|
||||
|
||||
|
||||
12
MasterPassword/C/lib/bcrypt-arm.patch
Normal file
12
MasterPassword/C/lib/bcrypt-arm.patch
Normal 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
|
||||
@@ -1,3 +1,4 @@
|
||||
home=http://www.openwall.com/crypt/
|
||||
pkg=http://www.openwall.com/crypt/crypt_blowfish-1.3.tar.gz
|
||||
pkg_sha256=83fa01fca6996fe8d882b7f8e9ba0305a5664936100b01481ea3c6a8ce8d72fd
|
||||
patches=(arm)
|
||||
|
||||
53
MasterPassword/C/mpw-algorithm.c
Normal file
53
MasterPassword/C/mpw-algorithm.c
Normal file
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// mpw-algorithm.c
|
||||
// MasterPassword
|
||||
//
|
||||
// Created by Maarten Billemont on 2014-12-20.
|
||||
// Copyright (c) 2014 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#include "mpw-algorithm.h"
|
||||
#include "mpw-algorithm_v0.c"
|
||||
#include "mpw-algorithm_v1.c"
|
||||
#include "mpw-algorithm_v2.c"
|
||||
#include "mpw-algorithm_v3.c"
|
||||
|
||||
#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 MPAlgorithmVersion algorithmVersion) {
|
||||
|
||||
switch (algorithmVersion) {
|
||||
case MPAlgorithmVersion0:
|
||||
return mpw_masterKeyForUser_v0( fullName, masterPassword );
|
||||
case MPAlgorithmVersion1:
|
||||
return mpw_masterKeyForUser_v1( fullName, masterPassword );
|
||||
case MPAlgorithmVersion2:
|
||||
return mpw_masterKeyForUser_v2( fullName, masterPassword );
|
||||
case MPAlgorithmVersion3:
|
||||
return mpw_masterKeyForUser_v3( fullName, masterPassword );
|
||||
default:
|
||||
ftl( "Unsupported version: %d", algorithmVersion );
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
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 MPAlgorithmVersion algorithmVersion) {
|
||||
|
||||
switch (algorithmVersion) {
|
||||
case MPAlgorithmVersion0:
|
||||
return mpw_passwordForSite_v0( masterKey, siteName, siteType, siteCounter, siteVariant, siteContext );
|
||||
case MPAlgorithmVersion1:
|
||||
return mpw_passwordForSite_v1( masterKey, siteName, siteType, siteCounter, siteVariant, siteContext );
|
||||
case MPAlgorithmVersion2:
|
||||
return mpw_passwordForSite_v2( masterKey, siteName, siteType, siteCounter, siteVariant, siteContext );
|
||||
case MPAlgorithmVersion3:
|
||||
return mpw_passwordForSite_v3( masterKey, siteName, siteType, siteCounter, siteVariant, siteContext );
|
||||
default:
|
||||
ftl( "Unsupported version: %d", algorithmVersion );
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
32
MasterPassword/C/mpw-algorithm.h
Normal file
32
MasterPassword/C/mpw-algorithm.h
Normal file
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// mpw-algorithm.h
|
||||
// MasterPassword
|
||||
//
|
||||
// Created by Maarten Billemont on 2014-12-20.
|
||||
// Copyright (c) 2014 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "mpw-types.h"
|
||||
|
||||
typedef enum(unsigned int, MPAlgorithmVersion) {
|
||||
/** V0 did math with chars whose signedness was platform-dependent. */
|
||||
MPAlgorithmVersion0,
|
||||
/** V1 miscounted the byte-length of multi-byte site names. */
|
||||
MPAlgorithmVersion1,
|
||||
/** V2 miscounted the byte-length of multi-byte user names. */
|
||||
MPAlgorithmVersion2,
|
||||
/** V3 is the current version. */
|
||||
MPAlgorithmVersion3,
|
||||
};
|
||||
#define MPAlgorithmVersionCurrent MPAlgorithmVersion3
|
||||
|
||||
/** 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, const MPAlgorithmVersion algorithmVersion);
|
||||
|
||||
/** 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, const MPAlgorithmVersion algorithmVersion);
|
||||
125
MasterPassword/C/mpw-algorithm_v0.c
Normal file
125
MasterPassword/C/mpw-algorithm_v0.c
Normal file
@@ -0,0 +1,125 @@
|
||||
//
|
||||
// mpw-algorithm.c
|
||||
// MasterPassword
|
||||
//
|
||||
// Created by Maarten Billemont on 2014-12-20.
|
||||
// Copyright (c) 2014 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "mpw-types.h"
|
||||
#include "mpw-util.h"
|
||||
|
||||
#define MP_N 32768
|
||||
#define MP_r 8
|
||||
#define MP_p 2
|
||||
#define MP_hash PearlHashSHA256
|
||||
|
||||
static const char *mpw_templateForType_v0(MPSiteType type, uint16_t seedByte) {
|
||||
|
||||
size_t count = 0;
|
||||
const char **templates = mpw_templatesForType( type, &count );
|
||||
if (!count)
|
||||
return NULL;
|
||||
|
||||
return templates[seedByte % count];
|
||||
}
|
||||
|
||||
static const char mpw_characterFromClass_v0(char characterClass, uint16_t seedByte) {
|
||||
|
||||
const char *classCharacters = mpw_charactersInClass( characterClass );
|
||||
return classCharacters[seedByte % strlen( classCharacters )];
|
||||
}
|
||||
|
||||
static const uint8_t *mpw_masterKeyForUser_v0(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( mpw_charlen( 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;
|
||||
}
|
||||
|
||||
static const char *mpw_passwordForSite_v0(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( mpw_charlen( siteName ) ) );
|
||||
mpw_pushString( &sitePasswordInfo, &sitePasswordInfoSize, siteName );
|
||||
mpw_pushInt( &sitePasswordInfo, &sitePasswordInfoSize, htonl( siteCounter ) );
|
||||
if (siteContext) {
|
||||
mpw_pushInt( &sitePasswordInfo, &sitePasswordInfoSize, htonl( mpw_charlen( 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 char *sitePasswordSeed = (const char *)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_v0( siteType, htons( 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_v0( template[c], htons( sitePasswordSeed[c + 1] ) );
|
||||
trc( "class %c, index %u (0x%02X) -> character: %c\n",
|
||||
template[c], htons( sitePasswordSeed[c + 1] ), htons( sitePasswordSeed[c + 1] ), sitePassword[c] );
|
||||
}
|
||||
mpw_free( sitePasswordSeed, sizeof( sitePasswordSeed ) );
|
||||
|
||||
return sitePassword;
|
||||
}
|
||||
109
MasterPassword/C/mpw-algorithm_v1.c
Normal file
109
MasterPassword/C/mpw-algorithm_v1.c
Normal file
@@ -0,0 +1,109 @@
|
||||
//
|
||||
// mpw-algorithm.c
|
||||
// MasterPassword
|
||||
//
|
||||
// Created by Maarten Billemont on 2014-12-20.
|
||||
// Copyright (c) 2014 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "mpw-types.h"
|
||||
#include "mpw-util.h"
|
||||
|
||||
#define MP_N 32768
|
||||
#define MP_r 8
|
||||
#define MP_p 2
|
||||
#define MP_hash PearlHashSHA256
|
||||
|
||||
static const uint8_t *mpw_masterKeyForUser_v1(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( mpw_charlen( 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;
|
||||
}
|
||||
|
||||
static const char *mpw_passwordForSite_v1(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( mpw_charlen( siteName ) ) );
|
||||
mpw_pushString( &sitePasswordInfo, &sitePasswordInfoSize, siteName );
|
||||
mpw_pushInt( &sitePasswordInfo, &sitePasswordInfoSize, htonl( siteCounter ) );
|
||||
if (siteContext) {
|
||||
mpw_pushInt( &sitePasswordInfo, &sitePasswordInfoSize, htonl( mpw_charlen( 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;
|
||||
}
|
||||
109
MasterPassword/C/mpw-algorithm_v2.c
Normal file
109
MasterPassword/C/mpw-algorithm_v2.c
Normal file
@@ -0,0 +1,109 @@
|
||||
//
|
||||
// mpw-algorithm.c
|
||||
// MasterPassword
|
||||
//
|
||||
// Created by Maarten Billemont on 2014-12-20.
|
||||
// Copyright (c) 2014 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "mpw-types.h"
|
||||
#include "mpw-util.h"
|
||||
|
||||
#define MP_N 32768
|
||||
#define MP_r 8
|
||||
#define MP_p 2
|
||||
#define MP_hash PearlHashSHA256
|
||||
|
||||
static const uint8_t *mpw_masterKeyForUser_v2(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( mpw_charlen( 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;
|
||||
}
|
||||
|
||||
static const char *mpw_passwordForSite_v2(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;
|
||||
}
|
||||
109
MasterPassword/C/mpw-algorithm_v3.c
Normal file
109
MasterPassword/C/mpw-algorithm_v3.c
Normal file
@@ -0,0 +1,109 @@
|
||||
//
|
||||
// mpw-algorithm.c
|
||||
// MasterPassword
|
||||
//
|
||||
// Created by Maarten Billemont on 2014-12-20.
|
||||
// Copyright (c) 2014 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "mpw-types.h"
|
||||
#include "mpw-util.h"
|
||||
|
||||
#define MP_N 32768
|
||||
#define MP_r 8
|
||||
#define MP_p 2
|
||||
#define MP_hash PearlHashSHA256
|
||||
|
||||
static const uint8_t *mpw_masterKeyForUser_v3(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;
|
||||
}
|
||||
|
||||
static const char *mpw_passwordForSite_v3(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;
|
||||
}
|
||||
@@ -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 <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 <string.h>
|
||||
#include <errno.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include <alg/sha256.h>
|
||||
#include <crypto/crypto_scrypt.h>
|
||||
#include <ow-crypt.h>
|
||||
#include "types.h"
|
||||
#include <scrypt/sha256.h>
|
||||
#include <bcrypt/ow-crypt.h>
|
||||
|
||||
#include "mpw-types.h"
|
||||
#include "mpw-algorithm.h"
|
||||
#include "mpw-util.h"
|
||||
|
||||
#define MP_N 32768
|
||||
#define MP_r 8
|
||||
@@ -22,166 +25,84 @@
|
||||
#define MP_dkLen 64
|
||||
#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[]) {
|
||||
|
||||
char *userName = "Robert Lee Mitchel";
|
||||
char *masterPassword = "banana colored duckling";
|
||||
char *siteName = "masterpasswordapp.com";
|
||||
uint32_t siteCounter = 1;
|
||||
MPElementType siteType = MPElementTypeGeneratedLong;
|
||||
|
||||
// Start MP
|
||||
const char *fullName = "Robert Lee Mitchel";
|
||||
const char *masterPassword = "banana colored duckling";
|
||||
const char *siteName = "masterpasswordapp.com";
|
||||
const uint32_t siteCounter = 1;
|
||||
const MPSiteType siteType = MPSiteTypeGeneratedLong;
|
||||
const MPSiteVariant siteVariant = MPSiteVariantPassword;
|
||||
const char *siteContext = NULL;
|
||||
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) {
|
||||
// Calculate the master key salt.
|
||||
char *mpNameSpace = "com.lyndir.masterpassword";
|
||||
const uint32_t n_userNameLength = htonl(strlen(userName));
|
||||
const size_t masterKeySaltLength = strlen(mpNameSpace) + sizeof(n_userNameLength) + strlen(userName);
|
||||
char *masterKeySalt = malloc( masterKeySaltLength );
|
||||
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));
|
||||
const uint8_t *masterKey = mpw_masterKeyForUser(
|
||||
fullName, masterPassword, MPAlgorithmVersionCurrent );
|
||||
if (!masterKey)
|
||||
ftl( "Could not allocate master key: %d\n", errno );
|
||||
free( (void *)mpw_passwordForSite(
|
||||
masterKey, siteName, siteType, siteCounter, siteVariant, siteContext, MPAlgorithmVersionCurrent ) );
|
||||
free( (void *)masterKey );
|
||||
|
||||
if (i % 1 == 0)
|
||||
fprintf( stderr, "\rmpw: iteration %d / %d..", i, iterations );
|
||||
}
|
||||
|
||||
// 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 );
|
||||
const double mpwSpeed = mpw_showSpeed( startTime, iterations, "mpw" );
|
||||
|
||||
// Start SHA-256
|
||||
if (gettimeofday(&startTime, NULL) != 0) {
|
||||
fprintf(stderr, "Could not get time: %d\n", errno);
|
||||
return 1;
|
||||
}
|
||||
|
||||
iterations = 50000000;
|
||||
uint8_t hash[32];
|
||||
mpw_getTime( &startTime );
|
||||
for (int i = 0; i < iterations; ++i) {
|
||||
SHA256_Buf(masterPassword, strlen(masterPassword), hash);
|
||||
SHA256_Buf( masterPassword, strlen( masterPassword ), hash );
|
||||
|
||||
if (i % 1000 == 0)
|
||||
fprintf( stderr, "\rsha256: iteration %d / %d..", i, iterations );
|
||||
}
|
||||
|
||||
// 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 );
|
||||
const double sha256Speed = mpw_showSpeed( startTime, iterations, "sha256" );
|
||||
|
||||
// Start BCrypt
|
||||
if (gettimeofday(&startTime, NULL) != 0) {
|
||||
fprintf(stderr, "Could not get time: %d\n", errno);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int bcrypt_cost = 9;
|
||||
iterations = 600;
|
||||
mpw_getTime( &startTime );
|
||||
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)
|
||||
fprintf( stderr, "\rbcrypt (cost %d): iteration %d / %d..", bcrypt_cost, i, iterations );
|
||||
}
|
||||
|
||||
// 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 );
|
||||
const double bcrypt9Speed = mpw_showSpeed( startTime, iterations, "bcrypt9" );
|
||||
|
||||
// Summarize.
|
||||
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 bcrypt (cost 9)\n", bcrypt9Speed / 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 );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
218
MasterPassword/C/mpw-cli.c
Normal file
218
MasterPassword/C/mpw-cli.c
Normal file
@@ -0,0 +1,218 @@
|
||||
#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"
|
||||
#define MP_env_algorithm "MP_ALGORITHM"
|
||||
|
||||
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 version The algorithm version to use.\n"
|
||||
" Defaults to %s in env or %d.\n\n", MP_env_algorithm, MPAlgorithmVersionCurrent );
|
||||
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 );
|
||||
MPAlgorithmVersion algorithmVersion = MPAlgorithmVersionCurrent;
|
||||
const char *algorithmVersionString = getenv( MP_env_algorithm );
|
||||
if (algorithmVersionString && strlen( algorithmVersionString ))
|
||||
if (sscanf( algorithmVersionString, "%u", &algorithmVersion ) != 1)
|
||||
ftl( "Invalid %s: %s\n", MP_env_algorithm, algorithmVersionString );
|
||||
|
||||
// Read the options.
|
||||
for (int opt; (opt = getopt( argc, argv, "u:t:c:v: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 'V':
|
||||
if (sscanf( optarg, "%u", &algorithmVersion ) != 1)
|
||||
ftl( "Not a version: %s\n", 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 );
|
||||
trc( "algorithmVersion: %u\n", algorithmVersion );
|
||||
|
||||
// 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, algorithmVersion );
|
||||
mpw_freeString( masterPassword );
|
||||
if (!masterKey)
|
||||
ftl( "Couldn't derive master key." );
|
||||
|
||||
const char *sitePassword = mpw_passwordForSite(
|
||||
masterKey, siteName, siteType, siteCounter, siteVariant, siteContextString, algorithmVersion );
|
||||
mpw_free( masterKey, MP_dkLen );
|
||||
if (!sitePassword)
|
||||
ftl( "Couldn't derive site password." );
|
||||
|
||||
fprintf( stdout, "%s\n", sitePassword );
|
||||
return 0;
|
||||
}
|
||||
76
MasterPassword/C/mpw-tests-util.c
Normal file
76
MasterPassword/C/mpw-tests-util.c
Normal 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;
|
||||
}
|
||||
16
MasterPassword/C/mpw-tests-util.h
Normal file
16
MasterPassword/C/mpw-tests-util.h
Normal 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);
|
||||
77
MasterPassword/C/mpw-tests.c
Normal file
77
MasterPassword/C/mpw-tests.c
Normal 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, MPAlgorithmVersionCurrent );
|
||||
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, MPAlgorithmVersionCurrent );
|
||||
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;
|
||||
}
|
||||
190
MasterPassword/C/mpw-types.c
Normal file
190
MasterPassword/C/mpw-types.c
Normal file
@@ -0,0 +1,190 @@
|
||||
//
|
||||
// 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();
|
||||
}
|
||||
|
||||
inline const char **mpw_templatesForType(MPSiteType type, size_t *count) {
|
||||
|
||||
if (!(type & MPSiteTypeClassGenerated)) {
|
||||
ftl( "Not a generated type: %d", type );
|
||||
*count = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case MPSiteTypeGeneratedMaximum: {
|
||||
*count = 2;
|
||||
return (const char *[]){ "anoxxxxxxxxxxxxxxxxx", "axxxxxxxxxxxxxxxxxno" };
|
||||
}
|
||||
case MPSiteTypeGeneratedLong: {
|
||||
*count = 21;
|
||||
return (const char *[]){ "CvcvnoCvcvCvcv", "CvcvCvcvnoCvcv", "CvcvCvcvCvcvno",
|
||||
"CvccnoCvcvCvcv", "CvccCvcvnoCvcv", "CvccCvcvCvcvno",
|
||||
"CvcvnoCvccCvcv", "CvcvCvccnoCvcv", "CvcvCvccCvcvno",
|
||||
"CvcvnoCvcvCvcc", "CvcvCvcvnoCvcc", "CvcvCvcvCvccno",
|
||||
"CvccnoCvccCvcv", "CvccCvccnoCvcv", "CvccCvccCvcvno",
|
||||
"CvcvnoCvccCvcc", "CvcvCvccnoCvcc", "CvcvCvccCvccno",
|
||||
"CvccnoCvcvCvcc", "CvccCvcvnoCvcc", "CvccCvcvCvccno" };
|
||||
}
|
||||
case MPSiteTypeGeneratedMedium: {
|
||||
*count = 2;
|
||||
return (const char *[]){ "CvcnoCvc", "CvcCvcno" };
|
||||
}
|
||||
case MPSiteTypeGeneratedBasic: {
|
||||
*count = 3;
|
||||
return (const char *[]){ "aaanaaan", "aannaaan", "aaannaaa" };
|
||||
}
|
||||
case MPSiteTypeGeneratedShort: {
|
||||
*count = 1;
|
||||
return (const char *[]){"Cvcn"};
|
||||
}
|
||||
case MPSiteTypeGeneratedPIN: {
|
||||
*count = 1;
|
||||
return (const char *[]){ "nnnn" };
|
||||
}
|
||||
case MPSiteTypeGeneratedName: {
|
||||
*count = 1;
|
||||
return (const char *[]) {"cvccvcvcv"};
|
||||
}
|
||||
case MPSiteTypeGeneratedPhrase: {
|
||||
*count = 3;
|
||||
return (const char *[]){ "cvcc cvc cvccvcv cvc", "cvc cvccvcvcv cvcv", "cv cvccv cvc cvcvccv" };
|
||||
}
|
||||
default: {
|
||||
ftl( "Unknown generated type: %d", type );
|
||||
*count = 0;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char *mpw_templateForType(MPSiteType type, uint8_t seedByte) {
|
||||
|
||||
size_t count = 0;
|
||||
const char **templates = mpw_templatesForType( type, &count );
|
||||
if (!count)
|
||||
return NULL;
|
||||
|
||||
return templates[seedByte % count];
|
||||
}
|
||||
|
||||
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_charactersInClass(char characterClass) {
|
||||
|
||||
switch (characterClass) {
|
||||
case 'V':
|
||||
return "AEIOU";
|
||||
case 'C':
|
||||
return "BCDFGHJKLMNPQRSTVWXYZ";
|
||||
case 'v':
|
||||
return "aeiou";
|
||||
case 'c':
|
||||
return "bcdfghjklmnpqrstvwxyz";
|
||||
case 'A':
|
||||
return "AEIOUBCDFGHJKLMNPQRSTVWXYZ";
|
||||
case 'a':
|
||||
return "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz";
|
||||
case 'n':
|
||||
return "0123456789";
|
||||
case 'o':
|
||||
return "@&%?,=[]_:-+*$#!'^~;()/.";
|
||||
case 'x':
|
||||
return "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz0123456789!@#$%^&*()";
|
||||
case ' ':
|
||||
return " ";
|
||||
default: {
|
||||
fprintf( stderr, "Unknown character class: %c", characterClass );
|
||||
abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char mpw_characterFromClass(char characterClass, uint8_t seedByte) {
|
||||
|
||||
const char *classCharacters = mpw_charactersInClass( characterClass );
|
||||
return classCharacters[seedByte % strlen( classCharacters )];
|
||||
}
|
||||
94
MasterPassword/C/mpw-types.h
Normal file
94
MasterPassword/C/mpw-types.h
Normal file
@@ -0,0 +1,94 @@
|
||||
//
|
||||
// mpw-types.h
|
||||
// MasterPassword
|
||||
//
|
||||
// Created by Maarten Billemont on 2012-02-01.
|
||||
// Copyright (c) 2014 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef NS_ENUM
|
||||
#define enum(_type, _name) NS_ENUM(_type, _name)
|
||||
#else
|
||||
#define enum(_type, _name) _type _name; enum
|
||||
#endif
|
||||
|
||||
#define MP_dkLen 64
|
||||
|
||||
//// Types.
|
||||
|
||||
typedef enum( unsigned int, MPSiteVariant ) {
|
||||
/** 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,
|
||||
};
|
||||
|
||||
typedef enum( unsigned int, MPSiteTypeClass ) {
|
||||
/** Generate the password. */
|
||||
MPSiteTypeClassGenerated = 1 << 4,
|
||||
/** Store the password. */
|
||||
MPSiteTypeClassStored = 1 << 5,
|
||||
};
|
||||
|
||||
typedef enum( unsigned int, MPSiteFeature ) {
|
||||
/** Export the key-protected content data. */
|
||||
MPSiteFeatureExportContent = 1 << 10,
|
||||
/** Never export content. */
|
||||
MPSiteFeatureDevicePrivate = 1 << 11,
|
||||
};
|
||||
|
||||
typedef enum( unsigned int, MPSiteType) {
|
||||
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,
|
||||
};
|
||||
|
||||
//// Type utilities.
|
||||
|
||||
/**
|
||||
* @return The variant represented by the given name.
|
||||
*/
|
||||
const MPSiteVariant mpw_variantWithName(const char *variantName);
|
||||
/**
|
||||
* @return An internal string containing the scope identifier to apply when encoding for the given variant.
|
||||
*/
|
||||
const char *mpw_scopeForVariant(MPSiteVariant variant);
|
||||
|
||||
/**
|
||||
* @return The type represented by the given name.
|
||||
*/
|
||||
const MPSiteType mpw_typeWithName(const char *typeName);
|
||||
|
||||
/**
|
||||
* @return An array of internal strings that express the templates to use for the given type.
|
||||
* The amount of elements in the array is stored in count.
|
||||
* If an unsupported type is given, count will be 0 and will return NULL.
|
||||
*/
|
||||
const char **mpw_templatesForType(MPSiteType type, size_t *count);
|
||||
/**
|
||||
* @return An internal string that contains the password encoding template of the given type
|
||||
* for a seed that starts with the given byte.
|
||||
*/
|
||||
const char *mpw_templateForType(MPSiteType type, uint8_t seedByte);
|
||||
|
||||
/**
|
||||
* @return An internal string that contains all the characters that occur in the given character class.
|
||||
*/
|
||||
const char *mpw_charactersInClass(char characterClass);
|
||||
/**
|
||||
* @return A character from given character class that encodes the given byte.
|
||||
*/
|
||||
const char mpw_characterFromClass(char characterClass, uint8_t seedByte);
|
||||
|
||||
172
MasterPassword/C/mpw-util.c
Normal file
172
MasterPassword/C/mpw-util.c
Normal file
@@ -0,0 +1,172 @@
|
||||
//
|
||||
// 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 <locale.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;
|
||||
}
|
||||
|
||||
const size_t mpw_charlen(const char *string) {
|
||||
|
||||
setlocale( LC_ALL, "en_US.UTF-8" );
|
||||
return mbstowcs( NULL, string, strlen( string ) );
|
||||
}
|
||||
66
MasterPassword/C/mpw-util.h
Normal file
66
MasterPassword/C/mpw-util.h
Normal file
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// mpw-util.h
|
||||
// MasterPassword
|
||||
//
|
||||
// Created by Maarten Billemont on 2014-12-20.
|
||||
// Copyright (c) 2014 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
//// Logging.
|
||||
|
||||
#ifdef DEBUG
|
||||
#ifndef trc
|
||||
#define trc(...) fprintf( stderr, __VA_ARGS__ )
|
||||
#endif
|
||||
#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 C-string in 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 C-string in 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);
|
||||
|
||||
//// String utilities.
|
||||
|
||||
const size_t mpw_charlen(const char *string);
|
||||
@@ -17,8 +17,8 @@ mpw() {
|
||||
:| _copy 2>/dev/null
|
||||
|
||||
# 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.
|
||||
printf %s "$(MP_USERNAME=$MP_USERNAME command mpw "$@")" | _copy
|
||||
printf %s "$(MP_FULLNAME=$MP_FULLNAME command mpw "$@")" | _copy
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
1
MasterPassword/C/mpw_tests.xml
Symbolic link
1
MasterPassword/C/mpw_tests.xml
Symbolic link
@@ -0,0 +1 @@
|
||||
../Java/masterpassword-algorithm/src/test/resources/mpw_tests.xml
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -33,17 +33,24 @@
|
||||
</dependency>
|
||||
|
||||
<!-- EXTERNAL DEPENDENCIES -->
|
||||
<dependency>
|
||||
<groupId>net.sf.plist</groupId>
|
||||
<artifactId>property-list</artifactId>
|
||||
<version>2.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.lambdaworks</groupId>
|
||||
<artifactId>scrypt</artifactId>
|
||||
<version>1.4.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- TESTING -->
|
||||
<dependency>
|
||||
<groupId>org.testng</groupId>
|
||||
<artifactId>testng</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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() );
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
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.ImmutableMap;
|
||||
import com.lyndir.lhunath.opal.system.util.MetaObject;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -13,20 +16,21 @@ import java.util.Map;
|
||||
*/
|
||||
public class MPTemplate extends MetaObject {
|
||||
|
||||
private final String templateString;
|
||||
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();
|
||||
for (int i = 0; i < template.length(); ++i)
|
||||
builder.add( characterClasses.get( template.charAt( i ) ) );
|
||||
ImmutableList.Builder<MPTemplateCharacterClass> builder = ImmutableList.builder();
|
||||
for (int i = 0; i < templateString.length(); ++i)
|
||||
builder.add( MPTemplateCharacterClass.forIdentifier( templateString.charAt( i ) ) );
|
||||
|
||||
this.template = builder.build();
|
||||
this.templateString = templateString;
|
||||
template = builder.build();
|
||||
}
|
||||
|
||||
public MPTemplate(final List<MPTemplateCharacterClass> template) {
|
||||
|
||||
this.template = template;
|
||||
public String getTemplateString() {
|
||||
return templateString;
|
||||
}
|
||||
|
||||
public MPTemplateCharacterClass getCharacterClassAtIndex(final int index) {
|
||||
@@ -38,4 +42,9 @@ public class MPTemplate extends MetaObject {
|
||||
|
||||
return template.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return strf( "{MPTemplate: %s}", templateString );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.lhunath.opal.system.util.MetaObject;
|
||||
import com.lyndir.lhunath.opal.system.util.ObjectMeta;
|
||||
|
||||
@@ -9,16 +10,29 @@ import com.lyndir.lhunath.opal.system.util.ObjectMeta;
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
public class MPTemplateCharacterClass extends MetaObject {
|
||||
public enum MPTemplateCharacterClass {
|
||||
|
||||
UpperVowel( 'V', "AEIOU" ),
|
||||
UpperConsonant( 'C', "BCDFGHJKLMNPQRSTVWXYZ" ),
|
||||
LowerVowel( 'v', "aeiou" ),
|
||||
LowerConsonant( 'c', "bcdfghjklmnpqrstvwxyz" ),
|
||||
UpperAlphanumeric( 'A', "AEIOUBCDFGHJKLMNPQRSTVWXYZ" ),
|
||||
Alphanumeric( 'a', "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz" ),
|
||||
Numeric( 'n', "0123456789" ),
|
||||
Other( 'o', "@&%?,=[]_:-+*$#!'^~;()/." ),
|
||||
Any( 'x', "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz0123456789!@#$%^&*()" ),
|
||||
Space( ' ', " " );
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( MPTemplateCharacterClass.class );
|
||||
|
||||
private final char identifier;
|
||||
@ObjectMeta(useFor = { })
|
||||
private final char[] characters;
|
||||
|
||||
public MPTemplateCharacterClass(final char identifier, final char[] characters) {
|
||||
MPTemplateCharacterClass(final char identifier, final String characters) {
|
||||
|
||||
this.identifier = identifier;
|
||||
this.characters = characters;
|
||||
this.characters = characters.toCharArray();
|
||||
}
|
||||
|
||||
public char getIdentifier() {
|
||||
@@ -30,4 +44,12 @@ public class MPTemplateCharacterClass extends MetaObject {
|
||||
|
||||
return characters[index % characters.length];
|
||||
}
|
||||
|
||||
public static MPTemplateCharacterClass forIdentifier(final char identifier) {
|
||||
for (MPTemplateCharacterClass characterClass : values())
|
||||
if (characterClass.getIdentifier() == identifier)
|
||||
return characterClass;
|
||||
|
||||
throw logger.bug( "No character class defined for identifier: %s", identifier );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.io.Closeables;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.lhunath.opal.system.util.MetaObject;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import net.sf.plist.*;
|
||||
import net.sf.plist.io.PropertyListException;
|
||||
import net.sf.plist.io.PropertyListParser;
|
||||
|
||||
|
||||
/**
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
public class MPTemplates extends MetaObject {
|
||||
|
||||
static final Logger logger = Logger.get( MPTemplates.class );
|
||||
|
||||
private final Map<MPElementType, List<MPTemplate>> templates;
|
||||
|
||||
public MPTemplates(final Map<MPElementType, List<MPTemplate>> templates) {
|
||||
|
||||
this.templates = templates;
|
||||
}
|
||||
|
||||
public static MPTemplates load() {
|
||||
|
||||
return loadFromPList( "ciphers.plist" );
|
||||
}
|
||||
|
||||
public static MPTemplates loadFromPList(final String templateResource) {
|
||||
|
||||
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
|
||||
InputStream templateStream = Thread.currentThread().getContextClassLoader().getResourceAsStream( templateResource );
|
||||
Preconditions.checkNotNull( templateStream, "Not found: %s", templateResource );
|
||||
try {
|
||||
NSObject plistObject = PropertyListParser.parse( templateStream );
|
||||
Preconditions.checkState( NSDictionary.class.isAssignableFrom( plistObject.getClass() ) );
|
||||
NSDictionary plist = (NSDictionary) plistObject;
|
||||
|
||||
NSDictionary characterClassesDict = (NSDictionary) plist.get( "MPCharacterClasses" );
|
||||
NSDictionary templatesDict = (NSDictionary) plist.get( "MPElementGeneratedEntity" );
|
||||
|
||||
ImmutableMap.Builder<Character, MPTemplateCharacterClass> characterClassesBuilder = ImmutableMap.builder();
|
||||
for (final Map.Entry<String, NSObject> characterClassEntry : characterClassesDict.entrySet()) {
|
||||
String key = characterClassEntry.getKey();
|
||||
NSObject value = characterClassEntry.getValue();
|
||||
Preconditions.checkState( key.length() == 1 );
|
||||
Preconditions.checkState( NSString.class.isAssignableFrom( value.getClass() ));
|
||||
|
||||
char character = key.charAt( 0 );
|
||||
char[] characterClass = ((NSString)value).getValue().toCharArray();
|
||||
characterClassesBuilder.put( character, new MPTemplateCharacterClass( character, characterClass ) );
|
||||
}
|
||||
ImmutableMap<Character, MPTemplateCharacterClass> characterClasses = characterClassesBuilder.build();
|
||||
|
||||
ImmutableMap.Builder<MPElementType, List<MPTemplate>> templatesBuilder = ImmutableMap.builder();
|
||||
for (final Map.Entry<String, NSObject> template : templatesDict.entrySet()) {
|
||||
String key = template.getKey();
|
||||
NSObject value = template.getValue();
|
||||
Preconditions.checkState( NSArray.class.isAssignableFrom( value.getClass() ) );
|
||||
|
||||
MPElementType type = MPElementType.forName( key );
|
||||
List<NSObject> templateStrings = ((NSArray) value).getValue();
|
||||
|
||||
ImmutableList.Builder<MPTemplate> typeTemplatesBuilder = ImmutableList.<MPTemplate>builder();
|
||||
for (final NSObject templateString : templateStrings)
|
||||
typeTemplatesBuilder.add( new MPTemplate( ((NSString) templateString).getValue(), characterClasses ) );
|
||||
|
||||
templatesBuilder.put( type, typeTemplatesBuilder.build() );
|
||||
}
|
||||
ImmutableMap<MPElementType, List<MPTemplate>> templates = templatesBuilder.build();
|
||||
|
||||
return new MPTemplates( templates );
|
||||
}
|
||||
catch (PropertyListException e) {
|
||||
logger.err( e, "Could not parse templates from: %s", templateResource );
|
||||
throw Throwables.propagate( e );
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.err( e, "Could not read templates from: %s", templateResource );
|
||||
throw Throwables.propagate( e );
|
||||
}
|
||||
finally {
|
||||
Closeables.closeQuietly( templateStream );
|
||||
}
|
||||
}
|
||||
|
||||
public MPTemplate getTemplateForTypeAtRollingIndex(final MPElementType type, final int templateIndex) {
|
||||
|
||||
List<MPTemplate> typeTemplates = templates.get( type );
|
||||
|
||||
return typeTemplates.get( templateIndex % typeTemplates.size() );
|
||||
}
|
||||
|
||||
public static void main(final String... arguments) {
|
||||
|
||||
load();
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,8 @@ package com.lyndir.masterpassword;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.io.CharSource;
|
||||
import com.google.common.io.CharStreams;
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.lambdaworks.crypto.SCrypt;
|
||||
import com.lyndir.lhunath.opal.crypto.CryptUtils;
|
||||
import com.lyndir.lhunath.opal.system.*;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -14,7 +11,7 @@ import java.nio.ByteOrder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.GeneralSecurityException;
|
||||
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 static final int ALGORITHM = 1;
|
||||
public static final String VERSION = "2.1";
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( MasterKey.class );
|
||||
private static final int MP_N = 32768;
|
||||
private static final int MP_r = 8;
|
||||
private static final int MP_p = 2;
|
||||
private static final int MP_dkLen = 64;
|
||||
private static final int MP_intLen = 32;
|
||||
private static final Charset MP_charset = Charsets.UTF_8;
|
||||
private static final ByteOrder MP_byteOrder = ByteOrder.BIG_ENDIAN;
|
||||
private static final MessageDigests MP_hash = MessageDigests.SHA256;
|
||||
private static final MessageAuthenticationDigests MP_mac = MessageAuthenticationDigests.HmacSHA256;
|
||||
private static final MPTemplates templates = MPTemplates.load();
|
||||
|
||||
private final String userName;
|
||||
private final byte[] key;
|
||||
private final String fullName;
|
||||
private final byte[] masterKey;
|
||||
|
||||
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();
|
||||
byte[] userNameLengthBytes = ByteBuffer.allocate( Integer.SIZE / Byte.SIZE )
|
||||
.order( MP_byteOrder )
|
||||
.putInt( userName.length() )
|
||||
.array();
|
||||
byte[] salt = Bytes.concat( "com.lyndir.masterpassword".getBytes( MP_charset ), //
|
||||
userNameLengthBytes, userName.getBytes( MP_charset ) );
|
||||
byte[] userNameBytes = fullName.getBytes( MP_charset );
|
||||
byte[] userNameLengthBytes = bytesForInt( userNameBytes.length );
|
||||
|
||||
String mpKeyScope = MPSiteVariant.Password.getScope();
|
||||
byte[] masterKeySalt = Bytes.concat( mpKeyScope.getBytes( MP_charset ), userNameLengthBytes, userNameBytes );
|
||||
logger.trc( "key scope: %s", mpKeyScope );
|
||||
logger.trc( "masterKeySalt ID: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) );
|
||||
|
||||
try {
|
||||
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;
|
||||
|
||||
logger.trc( "User: %s, master password derives to key ID: %s (took %.2fs)", //
|
||||
userName, getKeyID(), (double) (System.currentTimeMillis() - start) / 1000 );
|
||||
logger.trc( "masterKey ID: %s (derived in %.2fs)", CodeUtils.encodeHex( idForBytes( masterKey ) ),
|
||||
(System.currentTimeMillis() - start) / 1000D );
|
||||
}
|
||||
catch (GeneralSecurityException e) {
|
||||
throw logger.bug( e );
|
||||
}
|
||||
}
|
||||
|
||||
public String getUserName() {
|
||||
public String getFullName() {
|
||||
|
||||
return userName;
|
||||
return fullName;
|
||||
}
|
||||
|
||||
public String getKeyID() {
|
||||
public byte[] getKeyID() {
|
||||
|
||||
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 );
|
||||
byte[] subkey = new byte[Math.min( subkeyLength, key.length )];
|
||||
System.arraycopy( key, 0, subkey, 0, subkey.length );
|
||||
byte[] subkey = new byte[Math.min( subkeyLength, masterKey.length )];
|
||||
System.arraycopy( masterKey, 0, subkey, 0, subkey.length );
|
||||
|
||||
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.checkArgument( type.getTypeClass() == MPElementTypeClass.Generated );
|
||||
Preconditions.checkArgument( !name.isEmpty() );
|
||||
Preconditions.checkArgument( siteType.getTypeClass() == MPSiteTypeClass.Generated );
|
||||
Preconditions.checkArgument( !siteName.isEmpty() );
|
||||
|
||||
if (counter == 0)
|
||||
counter = (int) (System.currentTimeMillis() / (300 * 1000)) * 300;
|
||||
logger.trc( "siteName: %s", siteName );
|
||||
logger.trc( "siteCounter: %d", siteCounter );
|
||||
logger.trc( "siteVariant: %d (%s)", siteVariant.ordinal(), siteVariant );
|
||||
logger.trc( "siteType: %d (%s)", siteType.ordinal(), siteType );
|
||||
|
||||
byte[] nameLengthBytes = ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( MP_byteOrder ).putInt( name.length() ).array();
|
||||
byte[] counterBytes = ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( MP_byteOrder ).putInt( counter ).array();
|
||||
logger.trc( "seed from: hmac-sha256(%s, 'com.lyndir.masterpassword' | %s | %s | %s)", CryptUtils.encodeBase64( key ),
|
||||
CodeUtils.encodeHex( nameLengthBytes ), name, CodeUtils.encodeHex( counterBytes ) );
|
||||
byte[] seed = MP_mac.of( key, Bytes.concat( "com.lyndir.masterpassword".getBytes( MP_charset ), //
|
||||
nameLengthBytes, //
|
||||
name.getBytes( MP_charset ), //
|
||||
counterBytes ) );
|
||||
logger.trc( "seed is: %s", CryptUtils.encodeBase64( seed ) );
|
||||
if (siteCounter == 0)
|
||||
siteCounter = (int) (System.currentTimeMillis() / (300 * 1000)) * 300;
|
||||
|
||||
Preconditions.checkState( seed.length > 0 );
|
||||
int templateIndex = seed[0] & 0xFF; // Mask the integer's sign.
|
||||
MPTemplate template = templates.getTemplateForTypeAtRollingIndex( type, templateIndex );
|
||||
logger.trc( "type: %s, template: %s", type, template );
|
||||
String siteScope = siteVariant.getScope();
|
||||
byte[] siteNameBytes = siteName.getBytes( MP_charset );
|
||||
byte[] siteNameLengthBytes = bytesForInt( siteNameBytes.length );
|
||||
byte[] siteCounterBytes = bytesForInt( siteCounter );
|
||||
byte[] siteContextBytes = siteContext == null? null: siteContext.getBytes( MP_charset );
|
||||
byte[] siteContextLengthBytes = bytesForInt( siteContextBytes == null? 0: siteContextBytes.length );
|
||||
logger.trc( "site scope: %s, context: %s", siteScope, siteContext == null? "<empty>": siteContext );
|
||||
logger.trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)", siteScope, CodeUtils.encodeHex( siteNameLengthBytes ),
|
||||
siteName, CodeUtils.encodeHex( siteCounterBytes ), CodeUtils.encodeHex( siteContextLengthBytes ),
|
||||
siteContext == null? "(null)": siteContext );
|
||||
|
||||
byte[] sitePasswordInfo = Bytes.concat( siteScope.getBytes( MP_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
|
||||
if (siteContextBytes != null)
|
||||
sitePasswordInfo = Bytes.concat( sitePasswordInfo, siteContextLengthBytes, siteContextBytes );
|
||||
logger.trc( "sitePasswordInfo ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) );
|
||||
|
||||
byte[] sitePasswordSeed = MP_mac.of( masterKey, sitePasswordInfo );
|
||||
logger.trc( "sitePasswordSeed ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordSeed ) ) );
|
||||
|
||||
Preconditions.checkState( sitePasswordSeed.length > 0 );
|
||||
int templateIndex = sitePasswordSeed[0] & 0xFF; // Mask the integer's sign.
|
||||
MPTemplate template = siteType.getTemplateAtRollingIndex( templateIndex );
|
||||
logger.trc( "type %s, template: %s", siteType, template.getTemplateString() );
|
||||
|
||||
StringBuilder password = new StringBuilder( template.length() );
|
||||
for (int i = 0; i < template.length(); ++i) {
|
||||
int characterIndex = 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 );
|
||||
char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex );
|
||||
logger.trc( "class: %s, index: %d, byte: 0x%02X, chosen password character: %s", characterClass, characterIndex, seed[i + 1],
|
||||
passwordCharacter );
|
||||
logger.trc( "class %c, index %d (0x%02X) -> character: %c", characterClass.getIdentifier(), characterIndex,
|
||||
sitePasswordSeed[i + 1], passwordCharacter );
|
||||
|
||||
password.append( passwordCharacter );
|
||||
}
|
||||
@@ -124,6 +141,14 @@ public class MasterKey {
|
||||
public void invalidate() {
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
package com.lyndir.masterpassword.entity;
|
||||
|
||||
/**
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
public class MPElementEntity {
|
||||
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package com.lyndir.masterpassword.entity;
|
||||
|
||||
/**
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
public class MPElementGeneratedEntity extends MPElementEntity {
|
||||
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package com.lyndir.masterpassword.entity;
|
||||
|
||||
/**
|
||||
* <i>07 04, 2012</i>
|
||||
*
|
||||
* @author lhunath
|
||||
*/
|
||||
public class MPElementStoredEntity extends MPElementEntity {
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
../../../../../../MasterPassword/Resources/Data/ciphers.plist
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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#&@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>
|
||||
|
||||
@@ -86,9 +86,8 @@ public class EmergencyActivity extends Activity {
|
||||
sitePasswordField.setTypeface( Res.sourceCodePro_Black );
|
||||
sitePasswordField.setPaintFlags( userNameField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
|
||||
|
||||
typeField.setAdapter(
|
||||
new ArrayAdapter<MPElementType>( this, R.layout.type_item, MPElementType.forClass( MPElementTypeClass.Generated ) ) );
|
||||
typeField.setSelection( MPElementType.GeneratedLong.ordinal() );
|
||||
typeField.setAdapter( new ArrayAdapter<>( this, R.layout.type_item, MPSiteType.forClass( MPSiteTypeClass.Generated ) ) );
|
||||
typeField.setSelection( MPSiteType.GeneratedLong.ordinal() );
|
||||
|
||||
counterField.setMinValue( 1 );
|
||||
counterField.setMaxValue( Integer.MAX_VALUE );
|
||||
@@ -129,7 +128,7 @@ public class EmergencyActivity extends Activity {
|
||||
|
||||
SharedPreferences.Editor pref = getPreferences( MODE_PRIVATE ).edit();
|
||||
pref.putString( "userName", userName );
|
||||
pref.commit();
|
||||
pref.apply();
|
||||
|
||||
if (masterKeyFuture != null)
|
||||
masterKeyFuture.cancel( true );
|
||||
@@ -170,7 +169,7 @@ public class EmergencyActivity extends Activity {
|
||||
|
||||
private void updateSitePassword() {
|
||||
final String siteName = siteNameField.getText().toString();
|
||||
final MPElementType type = (MPElementType) typeField.getSelectedItem();
|
||||
final MPSiteType type = (MPSiteType) typeField.getSelectedItem();
|
||||
final int counter = counterField.getValue();
|
||||
|
||||
if (masterKeyFuture == null || siteName.isEmpty() || type == null) {
|
||||
@@ -184,7 +183,7 @@ public class EmergencyActivity extends Activity {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
final String sitePassword = masterKeyFuture.get().encode( siteName, type, counter );
|
||||
final String sitePassword = masterKeyFuture.get().encode( siteName, type, counter, MPSiteVariant.Password, null );
|
||||
|
||||
runOnUiThread( new Runnable() {
|
||||
@Override
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
package com.lyndir.masterpassword.model;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-08-20
|
||||
*/
|
||||
public class User {
|
||||
|
||||
private String name;
|
||||
private Avatar avatar;
|
||||
private Avatar avatar;
|
||||
|
||||
public User(final String name, final Avatar avatar) {
|
||||
this.name = name;
|
||||
@@ -20,4 +25,19 @@ public class User {
|
||||
public Avatar getAvatar() {
|
||||
return avatar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
return this == obj || obj instanceof User && name.equals( ((User) obj).name );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return name.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return strf( "{User: %s}", name );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,12 +18,16 @@
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse;
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.io.LineReader;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
|
||||
import com.lyndir.lhunath.opal.system.util.StringUtils;
|
||||
import java.io.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
@@ -34,7 +38,6 @@ import java.util.Arrays;
|
||||
public class CLI {
|
||||
|
||||
private static final String ENV_USERNAME = "MP_USERNAME";
|
||||
private static final String ENV_PASSWORD = "MP_PASSWORD";
|
||||
private static final String ENV_SITETYPE = "MP_SITETYPE";
|
||||
private static final String ENV_SITECOUNTER = "MP_SITECOUNTER";
|
||||
|
||||
@@ -42,71 +45,115 @@ public class CLI {
|
||||
throws IOException {
|
||||
|
||||
// Read information from the environment.
|
||||
String siteName = null;
|
||||
String userName = System.getenv().get( ENV_USERNAME );
|
||||
String masterPassword = System.getenv().get( ENV_PASSWORD );
|
||||
String siteTypeName = ifNotNullElse( System.getenv().get( ENV_SITETYPE ), "" );
|
||||
MPElementType siteType = siteTypeName.isEmpty()? MPElementType.GeneratedLong: MPElementType.forName( siteTypeName );
|
||||
String siteCounterName = ifNotNullElse( System.getenv().get( ENV_SITECOUNTER ), "" );
|
||||
String siteName = null, masterPassword, context = null;
|
||||
String userName = System.getenv( ENV_USERNAME );
|
||||
String siteTypeName = ifNotNullElse( System.getenv( ENV_SITETYPE ), "" );
|
||||
MPSiteType siteType = siteTypeName.isEmpty()? MPSiteType.GeneratedLong: MPSiteType.forOption( siteTypeName );
|
||||
MPSiteVariant variant = MPSiteVariant.Password;
|
||||
String siteCounterName = ifNotNullElse( System.getenv( ENV_SITECOUNTER ), "" );
|
||||
int siteCounter = siteCounterName.isEmpty()? 1: Integer.parseInt( siteCounterName );
|
||||
|
||||
// Parse information from option arguments.
|
||||
boolean typeArg = false, counterArg = false, userNameArg = false;
|
||||
boolean userNameArg = false, typeArg = false, counterArg = false, variantArg = false, contextArg = false;
|
||||
for (final String arg : Arrays.asList( args ))
|
||||
if ("-t".equals( arg ) || "--type".equals( arg ))
|
||||
typeArg = true;
|
||||
else if (typeArg) {
|
||||
if ("list".equalsIgnoreCase( arg )) {
|
||||
System.out.format( "%30s | %s\n", "type", "description" );
|
||||
for (final MPElementType aType : MPElementType.values())
|
||||
System.out.format( "%30s | %s\n", aType.getName(), aType.getDescription() );
|
||||
System.exit( 0 );
|
||||
}
|
||||
|
||||
siteType = MPElementType.forName( arg );
|
||||
typeArg = false;
|
||||
} else if ("-c".equals( arg ) || "--counter".equals( arg ))
|
||||
counterArg = true;
|
||||
else if (counterArg) {
|
||||
siteCounter = ConversionUtils.toIntegerNN( arg );
|
||||
counterArg = false;
|
||||
} else if ("-u".equals( arg ) || "--username".equals( arg ))
|
||||
// Full Name
|
||||
if ("-u".equals( arg ) || "--username".equals( arg ))
|
||||
userNameArg = true;
|
||||
else if (userNameArg) {
|
||||
userName = arg;
|
||||
userNameArg = false;
|
||||
} else if ("-h".equals( arg ) || "--help".equals( arg )) {
|
||||
}
|
||||
|
||||
// Type
|
||||
else if ("-t".equals( arg ) || "--type".equals( arg ))
|
||||
typeArg = true;
|
||||
else if (typeArg) {
|
||||
siteType = 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( "\tMaster Password CLI" );
|
||||
System.out.println( "\t\tLyndir" );
|
||||
System.out.format( "Usage: mpw [-u name] [-t type] [-c counter] site\n\n" );
|
||||
System.out.format( " -u name Specify the full name of the user.\n" );
|
||||
System.out.format( " Defaults to %s in env.\n\n", ENV_USERNAME );
|
||||
System.out.format( " -t type Specify the password's template.\n" );
|
||||
System.out.format( " Defaults to %s in env or 'long' for password, 'name' for login.\n", ENV_SITETYPE );
|
||||
|
||||
System.out.println( "[options] [site name]" );
|
||||
int optionsLength = 0;
|
||||
Map<String, 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( "Available options:" );
|
||||
|
||||
System.out.println( "\t-t | --type [site password type]" );
|
||||
System.out.format( "\t\tDefault: %s. The password type to use for this site.\n", siteType.getName() );
|
||||
System.out.println( "\t\tUse 'list' to see the available types." );
|
||||
System.out.format( " -c counter The value of the counter.\n" );
|
||||
System.out.format( " Defaults to %s in env or '1'.\n\n", ENV_SITECOUNTER );
|
||||
System.out.format( " -v variant The kind of content to generate.\n" );
|
||||
System.out.format( " Defaults to 'password'.\n" );
|
||||
|
||||
optionsLength = 0;
|
||||
Map<String, 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( "\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( "\t-u | --username [user's name]" );
|
||||
System.out.println( "\t\tDefault: asked. The name of the user." );
|
||||
|
||||
System.out.println();
|
||||
System.out.println( "Available environment variables:" );
|
||||
|
||||
System.out.format( "\t%s\n", ENV_USERNAME );
|
||||
System.out.println( "\t\tThe name of the user." );
|
||||
|
||||
System.out.format( "\t%s\n", ENV_PASSWORD );
|
||||
System.out.println( "\t\tThe master password of the user." );
|
||||
|
||||
System.out.println();
|
||||
System.out.format( " ENVIRONMENT\n\n" );
|
||||
System.out.format( " MP_USERNAME | The full name of the user.\n" );
|
||||
System.out.format( " MP_SITETYPE | The default password template.\n" );
|
||||
System.out.format( " MP_SITECOUNTER | The default counter value.\n\n" );
|
||||
return;
|
||||
} else
|
||||
siteName = arg;
|
||||
@@ -126,18 +173,16 @@ public class CLI {
|
||||
userName = lineReader.readLine();
|
||||
}
|
||||
|
||||
if (masterPassword == null) {
|
||||
if (console != null)
|
||||
masterPassword = new String( console.readPassword( "%s's master password: ", userName ) );
|
||||
if (console != null)
|
||||
masterPassword = new String( console.readPassword( "%s's master password: ", userName ) );
|
||||
|
||||
else {
|
||||
System.err.format( "%s's master password: ", userName );
|
||||
masterPassword = lineReader.readLine();
|
||||
}
|
||||
else {
|
||||
System.err.format( "%s's master password: ", userName );
|
||||
masterPassword = lineReader.readLine();
|
||||
}
|
||||
}
|
||||
|
||||
// Encode and write out the site password.
|
||||
System.out.println( new MasterKey( userName, masterPassword ).encode( siteName, siteType, siteCounter ) );
|
||||
System.out.println( new MasterKey( userName, masterPassword ).encode( siteName, siteType, siteCounter, variant, context ) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
<configuration>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>com.lyndir.masterpassword.GUI</mainClass>
|
||||
<mainClass>com.lyndir.masterpassword.gui.GUI</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
<filters>
|
||||
@@ -73,10 +73,11 @@
|
||||
<!-- PROJECT REFERENCES -->
|
||||
<dependency>
|
||||
<groupId>com.lyndir.masterpassword</groupId>
|
||||
<artifactId>masterpassword-algorithm</artifactId>
|
||||
<artifactId>masterpassword-model</artifactId>
|
||||
<version>GIT-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- EXTERNAL DEPENDENCIES -->
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.lyndir.masterpassword.util.Components;
|
||||
import java.awt.*;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
import java.awt.event.*;
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.*;
|
||||
import javax.swing.event.*;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-06-08
|
||||
*/
|
||||
public class PasswordFrame extends JFrame implements DocumentListener {
|
||||
|
||||
private final User user;
|
||||
private final JTextField siteNameField;
|
||||
private final JComboBox<MPElementType> siteTypeField;
|
||||
private final JSpinner siteCounterField;
|
||||
private final JTextField passwordField;
|
||||
private final JLabel tipLabel;
|
||||
|
||||
public PasswordFrame(User user)
|
||||
throws HeadlessException {
|
||||
super( "Master Password" );
|
||||
this.user = user;
|
||||
|
||||
JLabel label;
|
||||
|
||||
setContentPane( new JPanel( new BorderLayout( 20, 20 ) ) {
|
||||
{
|
||||
setBorder( new EmptyBorder( 20, 20, 20, 20 ) );
|
||||
}
|
||||
} );
|
||||
|
||||
// User
|
||||
add( label = new JLabel( strf( "Generating passwords for: %s", user.getUserName() ) ), BorderLayout.NORTH );
|
||||
label.setFont( Res.exoRegular().deriveFont( 12f ) );
|
||||
label.setAlignmentX( LEFT_ALIGNMENT );
|
||||
|
||||
// Site
|
||||
JPanel sitePanel = new JPanel();
|
||||
sitePanel.setLayout( new BoxLayout( sitePanel, BoxLayout.PAGE_AXIS ) );
|
||||
sitePanel.setBorder( new CompoundBorder( new EtchedBorder( EtchedBorder.RAISED ), new EmptyBorder( 8, 8, 8, 8 ) ) );
|
||||
add( sitePanel, BorderLayout.CENTER );
|
||||
|
||||
// Site Name
|
||||
sitePanel.add( label = new JLabel( "Site Name:", JLabel.LEADING ) );
|
||||
label.setFont( Res.exoRegular().deriveFont( 12f ) );
|
||||
label.setAlignmentX( LEFT_ALIGNMENT );
|
||||
|
||||
sitePanel.add( siteNameField = new JTextField() {
|
||||
@Override
|
||||
public Dimension getMaximumSize() {
|
||||
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
|
||||
}
|
||||
} );
|
||||
siteNameField.setFont( Res.exoRegular().deriveFont( 12f ) );
|
||||
siteNameField.setAlignmentX( LEFT_ALIGNMENT );
|
||||
siteNameField.getDocument().addDocumentListener( this );
|
||||
siteNameField.addActionListener( new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
updatePassword( new PasswordCallback() {
|
||||
@Override
|
||||
public void passwordGenerated(final String siteName, final String sitePassword) {
|
||||
StringSelection clipboardContents = new StringSelection( sitePassword );
|
||||
Toolkit.getDefaultToolkit().getSystemClipboard().setContents( clipboardContents, null );
|
||||
|
||||
SwingUtilities.invokeLater( new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
passwordField.setText( null );
|
||||
siteNameField.setText( null );
|
||||
|
||||
if (getDefaultCloseOperation() == WindowConstants.EXIT_ON_CLOSE)
|
||||
System.exit( 0 );
|
||||
else
|
||||
dispose();
|
||||
}
|
||||
} );
|
||||
}
|
||||
} );
|
||||
}
|
||||
} );
|
||||
|
||||
// Site Type & Counter
|
||||
MPElementType[] types = Iterables.toArray( MPElementType.forClass( MPElementTypeClass.Generated ), MPElementType.class );
|
||||
JComponent siteSettings = Components.boxLayout( BoxLayout.LINE_AXIS, //
|
||||
siteTypeField = new JComboBox<>( types ), //
|
||||
siteCounterField = new JSpinner(
|
||||
new SpinnerNumberModel( 1, 1, Integer.MAX_VALUE, 1 ) ) {
|
||||
@Override
|
||||
public Dimension getMaximumSize() {
|
||||
return new Dimension( 20, getPreferredSize().height );
|
||||
}
|
||||
} );
|
||||
siteSettings.setAlignmentX( LEFT_ALIGNMENT );
|
||||
sitePanel.add( siteSettings );
|
||||
siteTypeField.setFont( Res.exoRegular().deriveFont( 12f ) );
|
||||
siteTypeField.setAlignmentX( LEFT_ALIGNMENT );
|
||||
siteTypeField.setAlignmentY( CENTER_ALIGNMENT );
|
||||
siteTypeField.setSelectedItem( MPElementType.GeneratedLong );
|
||||
siteTypeField.addItemListener( new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(final ItemEvent e) {
|
||||
updatePassword( null );
|
||||
}
|
||||
} );
|
||||
|
||||
siteCounterField.setFont( Res.exoRegular().deriveFont( 12f ) );
|
||||
siteCounterField.setAlignmentX( RIGHT_ALIGNMENT );
|
||||
siteCounterField.setAlignmentY( CENTER_ALIGNMENT );
|
||||
siteCounterField.addChangeListener( new ChangeListener() {
|
||||
@Override
|
||||
public void stateChanged(final ChangeEvent e) {
|
||||
updatePassword( null );
|
||||
}
|
||||
} );
|
||||
|
||||
// Password
|
||||
passwordField = new JTextField( " " );
|
||||
passwordField.setFont( Res.sourceCodeProBlack().deriveFont( 40f ) );
|
||||
passwordField.setHorizontalAlignment( JTextField.CENTER );
|
||||
passwordField.setAlignmentX( Component.CENTER_ALIGNMENT );
|
||||
passwordField.setEditable( false );
|
||||
|
||||
// Tip
|
||||
tipLabel = new JLabel( " ", JLabel.CENTER );
|
||||
tipLabel.setFont( Res.exoThin().deriveFont( 9f ) );
|
||||
tipLabel.setAlignmentX( Component.CENTER_ALIGNMENT );
|
||||
|
||||
add( Components.boxLayout( BoxLayout.PAGE_AXIS, passwordField, tipLabel ), BorderLayout.SOUTH );
|
||||
|
||||
pack();
|
||||
setMinimumSize( getSize() );
|
||||
setPreferredSize( new Dimension( 600, getSize().height ) );
|
||||
pack();
|
||||
|
||||
setLocationByPlatform( true );
|
||||
setLocationRelativeTo( null );
|
||||
}
|
||||
|
||||
private void updatePassword(final PasswordCallback callback) {
|
||||
final MPElementType siteType = (MPElementType) siteTypeField.getSelectedItem();
|
||||
final String siteName = siteNameField.getText();
|
||||
final int siteCounter = (Integer) siteCounterField.getValue();
|
||||
|
||||
if (siteType.getTypeClass() != MPElementTypeClass.Generated || siteName == null || siteName.isEmpty() || !user.hasKey()) {
|
||||
passwordField.setText( null );
|
||||
tipLabel.setText( null );
|
||||
return;
|
||||
}
|
||||
|
||||
Res.execute( new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final String sitePassword = user.getKey().encode( siteName, siteType, siteCounter );
|
||||
if (callback != null)
|
||||
callback.passwordGenerated( siteName, sitePassword );
|
||||
|
||||
SwingUtilities.invokeLater( new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!siteName.equals( siteNameField.getText() ))
|
||||
return;
|
||||
|
||||
passwordField.setText( sitePassword );
|
||||
tipLabel.setText( "Press [Enter] to copy the password." );
|
||||
}
|
||||
} );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertUpdate(final DocumentEvent e) {
|
||||
updatePassword( null );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(final DocumentEvent e) {
|
||||
updatePassword( null );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(final DocumentEvent e) {
|
||||
updatePassword( null );
|
||||
}
|
||||
|
||||
interface PasswordCallback {
|
||||
|
||||
void passwordGenerated(String siteName, String sitePassword);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.lyndir.masterpassword;
|
||||
package com.lyndir.masterpassword.gui;
|
||||
|
||||
import com.apple.eawt.*;
|
||||
import javax.swing.*;
|
||||
@@ -34,7 +34,7 @@ public class AppleGUI extends GUI {
|
||||
@Override
|
||||
protected PasswordFrame newPasswordFrame(final User user) {
|
||||
PasswordFrame frame = super.newPasswordFrame( user );
|
||||
frame.setDefaultCloseOperation( WindowConstants.DISPOSE_ON_CLOSE );
|
||||
frame.setDefaultCloseOperation( WindowConstants.HIDE_ON_CLOSE );
|
||||
|
||||
return frame;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.lyndir.masterpassword;
|
||||
package com.lyndir.masterpassword.gui;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.awt.*;
|
||||
import javax.swing.*;
|
||||
|
||||
@@ -10,6 +11,7 @@ import javax.swing.*;
|
||||
public abstract class AuthenticationPanel extends JPanel {
|
||||
|
||||
protected final UnlockFrame unlockFrame;
|
||||
protected final JLabel avatarLabel;
|
||||
|
||||
public AuthenticationPanel(final UnlockFrame unlockFrame) {
|
||||
this.unlockFrame = unlockFrame;
|
||||
@@ -18,7 +20,7 @@ public abstract class AuthenticationPanel extends JPanel {
|
||||
|
||||
// Avatar
|
||||
add( Box.createVerticalGlue() );
|
||||
add( new JLabel( Res.avatar(0) ) {
|
||||
add( avatarLabel = new JLabel( Res.avatar( 0 ) ) {
|
||||
@Override
|
||||
public Dimension getMaximumSize() {
|
||||
return new Dimension( Integer.MAX_VALUE, Integer.MAX_VALUE );
|
||||
@@ -28,20 +30,22 @@ public abstract class AuthenticationPanel extends JPanel {
|
||||
}
|
||||
|
||||
protected void updateUser(boolean repack) {
|
||||
unlockFrame.setUser( getUser() );
|
||||
unlockFrame.setUser( getSelectedUser() );
|
||||
validate();
|
||||
|
||||
if (repack)
|
||||
unlockFrame.repack();
|
||||
}
|
||||
|
||||
protected abstract User getUser();
|
||||
protected abstract User getSelectedUser();
|
||||
|
||||
public Component getFocusComponent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getHelpText() {
|
||||
return null;
|
||||
public Iterable<? extends JButton> getButtons() {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
public abstract void reset();
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.lyndir.masterpassword;
|
||||
package com.lyndir.masterpassword.gui;
|
||||
|
||||
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
|
||||
|
||||
@@ -15,19 +15,16 @@
|
||||
*/
|
||||
|
||||
|
||||
package com.lyndir.masterpassword;
|
||||
package com.lyndir.masterpassword.gui;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
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.util.TypeUtils;
|
||||
import java.io.*;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.jar.*;
|
||||
import javax.swing.*;
|
||||
|
||||
@@ -76,7 +73,7 @@ public class GUI implements UnlockFrame.SignInCallback {
|
||||
String upstreamRevision = upstream.readFirstLine();
|
||||
logger.inf( "Local Revision: <%s>", manifestRevision );
|
||||
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"
|
||||
+ "http://masterpasswordapp.com/masterpassword-gui.jar" );
|
||||
JOptionPane.showMessageDialog( null, "A new version of Master Password is available.\n"
|
||||
@@ -94,32 +91,31 @@ public class GUI implements UnlockFrame.SignInCallback {
|
||||
SwingUtilities.invokeLater( new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (passwordFrame == null) {
|
||||
if (passwordFrame == null)
|
||||
unlockFrame.setVisible( true );
|
||||
} else {
|
||||
else
|
||||
passwordFrame.setVisible( true );
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Override
|
||||
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;
|
||||
}
|
||||
user.getKey();
|
||||
|
||||
passwordFrame = newPasswordFrame( user );
|
||||
|
||||
open();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected PasswordFrame newPasswordFrame(final User user) {
|
||||
PasswordFrame frame = new PasswordFrame( user );
|
||||
frame.setDefaultCloseOperation( WindowConstants.EXIT_ON_CLOSE );
|
||||
|
||||
return frame;
|
||||
return new PasswordFrame( user );
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.lyndir.masterpassword;
|
||||
package com.lyndir.masterpassword.gui;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
@@ -11,35 +11,38 @@ import javax.swing.event.DocumentListener;
|
||||
/**
|
||||
* @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;
|
||||
|
||||
public TextAuthenticationPanel(final UnlockFrame unlockFrame) {
|
||||
public IncognitoAuthenticationPanel(final UnlockFrame unlockFrame) {
|
||||
|
||||
// User Name
|
||||
// Full Name
|
||||
super( unlockFrame );
|
||||
JLabel userNameLabel = new JLabel( "User Name:" );
|
||||
userNameLabel.setAlignmentX( Component.LEFT_ALIGNMENT );
|
||||
userNameLabel.setHorizontalAlignment( SwingConstants.CENTER );
|
||||
userNameLabel.setVerticalAlignment( SwingConstants.BOTTOM );
|
||||
add( userNameLabel );
|
||||
JLabel fullNameLabel = new JLabel( "Full Name:" );
|
||||
fullNameLabel.setFont( Res.exoRegular().deriveFont( 12f ) );
|
||||
fullNameLabel.setAlignmentX( LEFT_ALIGNMENT );
|
||||
fullNameLabel.setHorizontalAlignment( SwingConstants.CENTER );
|
||||
fullNameLabel.setVerticalAlignment( SwingConstants.BOTTOM );
|
||||
add( fullNameLabel );
|
||||
|
||||
userNameField = new JTextField() {
|
||||
fullNameField = new JTextField() {
|
||||
@Override
|
||||
public Dimension getMaximumSize() {
|
||||
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
|
||||
}
|
||||
};
|
||||
userNameField.setAlignmentX( Component.LEFT_ALIGNMENT );
|
||||
userNameField.getDocument().addDocumentListener( this );
|
||||
userNameField.addActionListener( this );
|
||||
add( userNameField );
|
||||
fullNameField.setFont( Res.sourceCodeProRegular().deriveFont( 12f ) );
|
||||
fullNameField.setAlignmentX( LEFT_ALIGNMENT );
|
||||
fullNameField.getDocument().addDocumentListener( this );
|
||||
fullNameField.addActionListener( this );
|
||||
add( fullNameField );
|
||||
|
||||
// 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.setVerticalAlignment( SwingConstants.BOTTOM );
|
||||
add( masterPasswordLabel );
|
||||
@@ -50,7 +53,7 @@ public class TextAuthenticationPanel extends AuthenticationPanel implements Docu
|
||||
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
|
||||
}
|
||||
};
|
||||
masterPasswordField.setAlignmentX( Component.LEFT_ALIGNMENT );
|
||||
masterPasswordField.setAlignmentX( LEFT_ALIGNMENT );
|
||||
masterPasswordField.addActionListener( this );
|
||||
masterPasswordField.getDocument().addDocumentListener( this );
|
||||
add( masterPasswordField );
|
||||
@@ -58,12 +61,17 @@ public class TextAuthenticationPanel extends AuthenticationPanel implements Docu
|
||||
|
||||
@Override
|
||||
public Component getFocusComponent() {
|
||||
return userNameField;
|
||||
return fullNameField;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected User getUser() {
|
||||
return new User( userNameField.getText(), new String( masterPasswordField.getPassword() ) );
|
||||
public void reset() {
|
||||
masterPasswordField.setText( "" );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected User getSelectedUser() {
|
||||
return new IncognitoUser( fullNameField.getText(), new String( masterPasswordField.getPassword() ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -84,6 +92,6 @@ public class TextAuthenticationPanel extends AuthenticationPanel implements Docu
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
updateUser( false );
|
||||
unlockFrame.trySignIn( userNameField, masterPasswordField );
|
||||
unlockFrame.trySignIn( fullNameField, masterPasswordField );
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
public void use() {
|
||||
model.updateLastUsed();
|
||||
MPUserFileManager.get().save();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
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.lhunath.opal.system.util.ObjectUtils;
|
||||
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() )) {
|
||||
reset();
|
||||
throw new MasterKeyException( strf( "Incorrect master password for user: %s", getFullName() ) );
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
super.reset();
|
||||
|
||||
masterPassword = null;
|
||||
}
|
||||
|
||||
@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( model, site.getSiteName(), site.getSiteType(), site.getSiteCounter() ) );
|
||||
model.updateLastUsed();
|
||||
MPUserFileManager.get().save();
|
||||
}
|
||||
|
||||
public boolean keySaved() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
package com.lyndir.masterpassword.gui;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.util.concurrent.*;
|
||||
import com.lyndir.masterpassword.*;
|
||||
import com.lyndir.masterpassword.util.Components;
|
||||
import java.awt.*;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
import java.awt.event.*;
|
||||
import java.util.concurrent.Callable;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.*;
|
||||
import javax.swing.event.*;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2014-06-08
|
||||
*/
|
||||
public class PasswordFrame extends JFrame implements DocumentListener {
|
||||
|
||||
private final User user;
|
||||
private final JTextField siteNameField;
|
||||
private final JButton siteAddButton;
|
||||
private final JComboBox<MPSiteType> siteTypeField;
|
||||
private final JSpinner siteCounterField;
|
||||
private final JPasswordField passwordField;
|
||||
private final JLabel tipLabel;
|
||||
private final JCheckBox maskPasswordField;
|
||||
private final char passwordEchoChar;
|
||||
private final Font passwordEchoFont;
|
||||
private boolean updatingUI;
|
||||
private Site currentSite;
|
||||
|
||||
public PasswordFrame(User user)
|
||||
throws HeadlessException {
|
||||
super( "Master Password" );
|
||||
this.user = user;
|
||||
|
||||
JLabel label;
|
||||
|
||||
setDefaultCloseOperation( DISPOSE_ON_CLOSE );
|
||||
setContentPane( new JPanel( new BorderLayout( 20, 20 ) ) {
|
||||
{
|
||||
setBorder( new EmptyBorder( 20, 20, 20, 20 ) );
|
||||
}
|
||||
} );
|
||||
|
||||
// User
|
||||
add( label = new JLabel( strf( "Generating passwords for: %s", user.getFullName() ) ), BorderLayout.NORTH );
|
||||
label.setFont( Res.exoRegular().deriveFont( 12f ) );
|
||||
label.setAlignmentX( LEFT_ALIGNMENT );
|
||||
|
||||
// Site
|
||||
JPanel sitePanel = new JPanel();
|
||||
sitePanel.setLayout( new BoxLayout( sitePanel, BoxLayout.PAGE_AXIS ) );
|
||||
sitePanel.setBorder( new CompoundBorder( new EtchedBorder( EtchedBorder.RAISED ), new EmptyBorder( 8, 8, 8, 8 ) ) );
|
||||
add( sitePanel, BorderLayout.CENTER );
|
||||
|
||||
// Site Name
|
||||
sitePanel.add( label = new JLabel( "Site Name:", JLabel.LEADING ) );
|
||||
label.setFont( Res.exoRegular().deriveFont( 12f ) );
|
||||
label.setAlignmentX( LEFT_ALIGNMENT );
|
||||
|
||||
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
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
PasswordFrame.this.user.addSite( currentSite );
|
||||
siteAddButton.setVisible( false );
|
||||
}
|
||||
} );
|
||||
siteControls.setAlignmentX( LEFT_ALIGNMENT );
|
||||
sitePanel.add( siteControls );
|
||||
siteNameField.setFont( Res.sourceCodeProRegular().deriveFont( 12f ) );
|
||||
siteNameField.setAlignmentX( LEFT_ALIGNMENT );
|
||||
siteNameField.getDocument().addDocumentListener( this );
|
||||
siteNameField.addActionListener( new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
Futures.addCallback( updatePassword(), new FutureCallback<String>() {
|
||||
@Override
|
||||
public void onSuccess(final String sitePassword) {
|
||||
StringSelection clipboardContents = new StringSelection( sitePassword );
|
||||
Toolkit.getDefaultToolkit().getSystemClipboard().setContents( clipboardContents, null );
|
||||
|
||||
SwingUtilities.invokeLater( new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
passwordField.setText( null );
|
||||
siteNameField.setText( null );
|
||||
|
||||
dispatchEvent( new WindowEvent( PasswordFrame.this, WindowEvent.WINDOW_CLOSING ) );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(final Throwable t) {
|
||||
}
|
||||
} );
|
||||
}
|
||||
} );
|
||||
|
||||
// Site Type & Counter
|
||||
MPSiteType[] types = Iterables.toArray( MPSiteType.forClass( MPSiteTypeClass.Generated ), MPSiteType.class );
|
||||
JComponent siteSettings = Components.boxLayout( BoxLayout.LINE_AXIS, //
|
||||
siteTypeField = new JComboBox<>( types ), //
|
||||
siteCounterField = new JSpinner(
|
||||
new SpinnerNumberModel( 1, 1, Integer.MAX_VALUE, 1 ) ) {
|
||||
@Override
|
||||
public Dimension getMaximumSize() {
|
||||
return new Dimension( 20, getPreferredSize().height );
|
||||
}
|
||||
} );
|
||||
siteSettings.setAlignmentX( LEFT_ALIGNMENT );
|
||||
sitePanel.add( siteSettings );
|
||||
siteTypeField.setFont( Res.sourceCodeProRegular().deriveFont( 12f ) );
|
||||
siteTypeField.setAlignmentX( LEFT_ALIGNMENT );
|
||||
siteTypeField.setAlignmentY( CENTER_ALIGNMENT );
|
||||
siteTypeField.setSelectedItem( MPSiteType.GeneratedLong );
|
||||
siteTypeField.addItemListener( new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(final ItemEvent e) {
|
||||
updatePassword();
|
||||
}
|
||||
} );
|
||||
|
||||
siteCounterField.setFont( Res.sourceCodeProRegular().deriveFont( 12f ) );
|
||||
siteCounterField.setAlignmentX( RIGHT_ALIGNMENT );
|
||||
siteCounterField.setAlignmentY( CENTER_ALIGNMENT );
|
||||
siteCounterField.addChangeListener( new ChangeListener() {
|
||||
@Override
|
||||
public void stateChanged(final ChangeEvent e) {
|
||||
updatePassword();
|
||||
}
|
||||
} );
|
||||
|
||||
// Mask
|
||||
maskPasswordField = new JCheckBox();
|
||||
maskPasswordField.setFont( Res.exoRegular().deriveFont( 12f ) );
|
||||
maskPasswordField.setAlignmentX( Component.CENTER_ALIGNMENT );
|
||||
maskPasswordField.setText( "Hide Password" );
|
||||
maskPasswordField.setSelected( true );
|
||||
maskPasswordField.addItemListener( new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(ItemEvent e) {
|
||||
updateMask();
|
||||
}
|
||||
} );
|
||||
|
||||
// Password
|
||||
passwordField = new JPasswordField();
|
||||
passwordField.setHorizontalAlignment( JTextField.CENTER );
|
||||
passwordField.setAlignmentX( Component.CENTER_ALIGNMENT );
|
||||
passwordField.setEditable( false );
|
||||
passwordEchoChar = passwordField.getEchoChar();
|
||||
passwordEchoFont = passwordField.getFont().deriveFont( 40f );
|
||||
updateMask();
|
||||
|
||||
// Tip
|
||||
tipLabel = new JLabel( " ", JLabel.CENTER );
|
||||
tipLabel.setFont( Res.exoRegular().deriveFont( 9f ) );
|
||||
tipLabel.setAlignmentX( Component.CENTER_ALIGNMENT );
|
||||
|
||||
add( Components.boxLayout( BoxLayout.PAGE_AXIS, maskPasswordField, passwordField, tipLabel ), BorderLayout.SOUTH );
|
||||
|
||||
pack();
|
||||
setMinimumSize( getSize() );
|
||||
setPreferredSize( new Dimension( 600, getSize().height ) );
|
||||
pack();
|
||||
|
||||
setLocationByPlatform( true );
|
||||
setLocationRelativeTo( null );
|
||||
}
|
||||
|
||||
private void updateMask() {
|
||||
passwordField.setEchoChar( maskPasswordField.isSelected()? passwordEchoChar: (char) 0 );
|
||||
passwordField.setFont( maskPasswordField.isSelected()? passwordEchoFont: Res.sourceCodeProBlack().deriveFont( 40f ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private ListenableFuture<String> updatePassword() {
|
||||
|
||||
final String siteNameQuery = siteNameField.getText();
|
||||
if (updatingUI)
|
||||
return Futures.immediateCancelledFuture();
|
||||
if (siteNameQuery == null || siteNameQuery.isEmpty() || !user.hasKey()) {
|
||||
tipLabel.setText( null );
|
||||
passwordField.setText( null );
|
||||
return Futures.immediateCancelledFuture();
|
||||
}
|
||||
|
||||
MPSiteType siteType = siteTypeField.getModel().getElementAt( siteTypeField.getSelectedIndex() );
|
||||
final int siteCounter = (Integer) siteCounterField.getValue();
|
||||
final Site site = currentSite != null && currentSite.getSiteName().equals( siteNameQuery )? currentSite
|
||||
: Iterables.getFirst( user.findSitesByName( siteNameQuery ), new IncognitoSite( siteNameQuery, siteType, siteCounter ) );
|
||||
assert site != null;
|
||||
if (site == currentSite) {
|
||||
site.setSiteType( siteType );
|
||||
site.setSiteCounter( siteCounter );
|
||||
}
|
||||
|
||||
ListenableFuture<String> passwordFuture = Res.execute( this, 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() {
|
||||
@Override
|
||||
public void run() {
|
||||
updatingUI = true;
|
||||
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 );
|
||||
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
|
||||
public void insertUpdate(final DocumentEvent e) {
|
||||
updatePassword();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(final DocumentEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(final DocumentEvent e) {
|
||||
updatePassword();
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,19 @@
|
||||
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.StringUtils.*;
|
||||
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.io.Resources;
|
||||
import com.google.common.util.concurrent.*;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.awt.image.ImageObserver;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.swing.*;
|
||||
@@ -22,17 +24,18 @@ import javax.swing.*;
|
||||
*/
|
||||
public abstract class Res {
|
||||
|
||||
private static final ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
private static final Logger logger = Logger.get( Res.class );
|
||||
private static final WeakHashMap<Window, ExecutorService> executorByWindow = new WeakHashMap<>();
|
||||
private static final Logger logger = Logger.get( Res.class );
|
||||
|
||||
private static Font sourceCodeProRegular;
|
||||
private static Font sourceCodeProBlack;
|
||||
private static Font exoBold;
|
||||
private static Font exoExtraBold;
|
||||
private static Font exoRegular;
|
||||
private static Font exoThin;
|
||||
|
||||
public static void execute(final Runnable job) {
|
||||
executor.submit( new Runnable() {
|
||||
public static Future<?> execute(final Window host, final Runnable job) {
|
||||
return getExecutor( host ).submit( new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
@@ -45,20 +48,72 @@ public abstract class Res {
|
||||
} );
|
||||
}
|
||||
|
||||
public static <V> ListenableFuture<V> execute(final Window host, final Callable<V> job) {
|
||||
ExecutorService executor = getExecutor( host );
|
||||
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 );
|
||||
}
|
||||
|
||||
private static ExecutorService getExecutor(final Window host) {
|
||||
ExecutorService executor = executorByWindow.get( host );
|
||||
|
||||
if (executor == null) {
|
||||
executorByWindow.put( host, executor = Executors.newSingleThreadExecutor() );
|
||||
|
||||
host.addWindowListener( new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosed(final WindowEvent e) {
|
||||
ExecutorService executor = executorByWindow.remove( host );
|
||||
if (executor != null)
|
||||
executor.shutdownNow();
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
return executor;
|
||||
}
|
||||
|
||||
public static Icon iconAdd() {
|
||||
return new RetinaIcon( Resources.getResource( "media/icon_add@2x.png" ) );
|
||||
}
|
||||
|
||||
public static Icon iconQuestion() {
|
||||
return new RetinaIcon( Resources.getResource( "media/icon_question@2x.png" ) );
|
||||
}
|
||||
|
||||
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() {
|
||||
try {
|
||||
URL resource = Resources.getResource( "fonts/SourceCodePro-Bold.otf" );
|
||||
Font font = Font.createFont( Font.TRUETYPE_FONT, resource.openStream() );
|
||||
return sourceCodeProBlack != null? sourceCodeProBlack: //
|
||||
(sourceCodeProBlack = font);
|
||||
return sourceCodeProBlack != null? sourceCodeProBlack: (sourceCodeProBlack =
|
||||
Font.createFont( Font.TRUETYPE_FONT, Resources.getResource( "fonts/SourceCodePro-Bold.otf" ).openStream() ));
|
||||
}
|
||||
catch (FontFormatException | IOException e) {
|
||||
throw Throwables.propagate( e );
|
||||
@@ -67,10 +122,8 @@ public abstract class Res {
|
||||
|
||||
public static Font exoBold() {
|
||||
try {
|
||||
URL resource = Resources.getResource( "fonts/Exo2.0-Bold.otf" );
|
||||
Font font = Font.createFont( Font.TRUETYPE_FONT, resource.openStream() );
|
||||
return exoBold != null? exoBold: //
|
||||
(exoBold = font);
|
||||
return exoBold != null? exoBold: (exoBold =
|
||||
Font.createFont( Font.TRUETYPE_FONT, Resources.getResource( "fonts/Exo2.0-Bold.otf" ).openStream() ));
|
||||
}
|
||||
catch (FontFormatException | IOException e) {
|
||||
throw Throwables.propagate( e );
|
||||
@@ -79,10 +132,8 @@ public abstract class Res {
|
||||
|
||||
public static Font exoExtraBold() {
|
||||
try {
|
||||
URL resource = Resources.getResource( "fonts/Exo2.0-ExtraBold.otf" );
|
||||
Font font = Font.createFont( Font.TRUETYPE_FONT, resource.openStream() );
|
||||
return exoExtraBold != null? exoExtraBold: //
|
||||
(exoExtraBold = font);
|
||||
return exoExtraBold != null? exoExtraBold: (exoExtraBold
|
||||
= Font.createFont( Font.TRUETYPE_FONT, Resources.getResource( "fonts/Exo2.0-ExtraBold.otf" ).openStream() ));
|
||||
}
|
||||
catch (FontFormatException | IOException e) {
|
||||
throw Throwables.propagate( e );
|
||||
@@ -91,10 +142,8 @@ public abstract class Res {
|
||||
|
||||
public static Font exoRegular() {
|
||||
try {
|
||||
URL resource = Resources.getResource( "fonts/Exo2.0-Regular.otf" );
|
||||
Font font = Font.createFont( Font.TRUETYPE_FONT, resource.openStream() );
|
||||
return exoRegular != null? exoRegular: //
|
||||
(exoRegular = font);
|
||||
return exoRegular != null? exoRegular: (exoRegular =
|
||||
Font.createFont( Font.TRUETYPE_FONT, Resources.getResource( "fonts/Exo2.0-Regular.otf" ).openStream() ));
|
||||
}
|
||||
catch (FontFormatException | IOException e) {
|
||||
throw Throwables.propagate( e );
|
||||
@@ -103,10 +152,8 @@ public abstract class Res {
|
||||
|
||||
public static Font exoThin() {
|
||||
try {
|
||||
URL resource = Resources.getResource( "fonts/Exo2.0-Thin.otf" );
|
||||
Font font = Font.createFont( Font.TRUETYPE_FONT, resource.openStream() );
|
||||
return exoThin != null? exoThin: //
|
||||
(exoThin = font);
|
||||
return exoThin != null? exoThin: (exoThin =
|
||||
Font.createFont( Font.TRUETYPE_FONT, Resources.getResource( "fonts/Exo2.0-Thin.otf" ).openStream() ));
|
||||
}
|
||||
catch (FontFormatException | IOException e) {
|
||||
throw Throwables.propagate( e );
|
||||
@@ -115,7 +162,7 @@ public abstract class Res {
|
||||
|
||||
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;
|
||||
|
||||
@@ -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() );
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.lyndir.masterpassword;
|
||||
package com.lyndir.masterpassword.gui;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||
|
||||
@@ -14,12 +14,13 @@ import javax.swing.border.*;
|
||||
*/
|
||||
public class UnlockFrame extends JFrame {
|
||||
|
||||
private final SignInCallback signInCallback;
|
||||
private final JPanel root;
|
||||
private final JButton signInButton;
|
||||
private final JPanel authenticationContainer;
|
||||
private boolean useConfig;
|
||||
public User user;
|
||||
private final SignInCallback signInCallback;
|
||||
private final JPanel root;
|
||||
private final JButton signInButton;
|
||||
private final JPanel authenticationContainer;
|
||||
private AuthenticationPanel authenticationPanel;
|
||||
private boolean incognito;
|
||||
public User user;
|
||||
|
||||
public UnlockFrame(final SignInCallback signInCallback)
|
||||
throws HeadlessException {
|
||||
@@ -38,6 +39,7 @@ public class UnlockFrame extends JFrame {
|
||||
// Sign In
|
||||
root.add( Components.boxLayout( BoxLayout.LINE_AXIS, Box.createGlue(), signInButton = new JButton( "Sign In" ), Box.createGlue() ),
|
||||
BorderLayout.SOUTH );
|
||||
signInButton.setFont( Res.exoRegular().deriveFont( 12f ) );
|
||||
signInButton.setAlignmentX( LEFT_ALIGNMENT );
|
||||
signInButton.addActionListener( new AbstractAction() {
|
||||
@Override
|
||||
@@ -46,7 +48,6 @@ public class UnlockFrame extends JFrame {
|
||||
}
|
||||
} );
|
||||
|
||||
useConfig = ConfigAuthenticationPanel.hasConfigUsers();
|
||||
createAuthenticationPanel();
|
||||
|
||||
setLocationByPlatform( true );
|
||||
@@ -64,22 +65,22 @@ public class UnlockFrame extends JFrame {
|
||||
private void createAuthenticationPanel() {
|
||||
authenticationContainer.removeAll();
|
||||
|
||||
final AuthenticationPanel authenticationPanel;
|
||||
if (useConfig) {
|
||||
authenticationPanel = new ConfigAuthenticationPanel( this );
|
||||
if (incognito) {
|
||||
authenticationPanel = new IncognitoAuthenticationPanel( this );
|
||||
} else {
|
||||
authenticationPanel = new TextAuthenticationPanel( this );
|
||||
authenticationPanel = new ModelAuthenticationPanel( this );
|
||||
}
|
||||
authenticationPanel.updateUser( false );
|
||||
authenticationContainer.add( authenticationPanel, BorderLayout.CENTER );
|
||||
|
||||
final JCheckBox typeCheckBox = new JCheckBox( "Use Config File" );
|
||||
typeCheckBox.setAlignmentX( LEFT_ALIGNMENT );
|
||||
typeCheckBox.setSelected( useConfig );
|
||||
typeCheckBox.addItemListener( new ItemListener() {
|
||||
final JCheckBox incognitoCheckBox = new JCheckBox( "Incognito" );
|
||||
incognitoCheckBox.setFont( Res.exoRegular().deriveFont( 12f ) );
|
||||
incognitoCheckBox.setAlignmentX( LEFT_ALIGNMENT );
|
||||
incognitoCheckBox.setSelected( incognito );
|
||||
incognitoCheckBox.addItemListener( new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(final ItemEvent e) {
|
||||
useConfig = typeCheckBox.isSelected();
|
||||
incognito = incognitoCheckBox.isSelected();
|
||||
SwingUtilities.invokeLater( new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -89,24 +90,15 @@ public class UnlockFrame extends JFrame {
|
||||
}
|
||||
} );
|
||||
|
||||
JButton typeHelp = new JButton( Res.iconQuestion() );
|
||||
typeHelp.setMargin( new Insets( 0, 0, 0, 0 ) );
|
||||
typeHelp.setBackground( Color.red );
|
||||
typeHelp.setAlignmentX( RIGHT_ALIGNMENT );
|
||||
typeHelp.setBorder( null );
|
||||
typeHelp.addActionListener( new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
JOptionPane.showMessageDialog( UnlockFrame.this, authenticationPanel.getHelpText(), "Help",
|
||||
JOptionPane.INFORMATION_MESSAGE );
|
||||
}
|
||||
} );
|
||||
if (authenticationPanel.getHelpText() == null) {
|
||||
typeHelp.setVisible( false );
|
||||
JComponent toolsPanel = Components.boxLayout( BoxLayout.LINE_AXIS, incognitoCheckBox, Box.createGlue() );
|
||||
toolsPanel.setAlignmentX( Component.LEFT_ALIGNMENT );
|
||||
authenticationContainer.add( toolsPanel );
|
||||
for (JButton button : authenticationPanel.getButtons()) {
|
||||
button.setMargin( new Insets( 0, 0, 0, 0 ) );
|
||||
button.setAlignmentX( RIGHT_ALIGNMENT );
|
||||
button.setBorder( null );
|
||||
toolsPanel.add( button );
|
||||
}
|
||||
JComponent typePanel = Components.boxLayout( BoxLayout.LINE_AXIS, typeCheckBox, Box.createGlue(), typeHelp );
|
||||
typePanel.setAlignmentX( Component.LEFT_ALIGNMENT );
|
||||
authenticationContainer.add( typePanel );
|
||||
|
||||
checkSignIn();
|
||||
validate();
|
||||
@@ -126,25 +118,23 @@ public class UnlockFrame extends JFrame {
|
||||
}
|
||||
|
||||
boolean checkSignIn() {
|
||||
boolean enabled = user != null && !user.getUserName().isEmpty() && user.hasKey();
|
||||
boolean enabled = user != null && !user.getFullName().isEmpty() && user.hasKey();
|
||||
signInButton.setEnabled( enabled );
|
||||
|
||||
return enabled;
|
||||
}
|
||||
|
||||
void trySignIn(final JComponent... signInComponents) {
|
||||
if (!checkSignIn()) {
|
||||
if (!checkSignIn())
|
||||
return;
|
||||
}
|
||||
|
||||
for (JComponent signInComponent : signInComponents) {
|
||||
for (JComponent signInComponent : signInComponents)
|
||||
signInComponent.setEnabled( false );
|
||||
}
|
||||
|
||||
signInButton.setEnabled( false );
|
||||
signInButton.setText( "Signing In..." );
|
||||
|
||||
Res.execute( new Runnable() {
|
||||
Res.execute( this, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final boolean success = signInCallback.signedIn( user );
|
||||
@@ -157,10 +147,10 @@ public class UnlockFrame extends JFrame {
|
||||
return;
|
||||
}
|
||||
|
||||
authenticationPanel.reset();
|
||||
signInButton.setText( "Sign In" );
|
||||
for (JComponent signInComponent : signInComponents) {
|
||||
for (JComponent signInComponent : signInComponents)
|
||||
signInComponent.setEnabled( true );
|
||||
}
|
||||
checkSignIn();
|
||||
}
|
||||
} );
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.lyndir.masterpassword.gui;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||
|
||||
import com.lyndir.masterpassword.MasterKey;
|
||||
import com.lyndir.masterpassword.model.MPUser;
|
||||
import java.security.KeyException;
|
||||
import java.util.Objects;
|
||||
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) {
|
||||
String masterPassword = getMasterPassword();
|
||||
if (masterPassword == null || masterPassword.isEmpty()) {
|
||||
reset();
|
||||
throw new MasterKeyException( strf( "Master password unknown for user: %s", getFullName() ) );
|
||||
}
|
||||
|
||||
key = new MasterKey( getFullName(), masterPassword );
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
key = null;
|
||||
}
|
||||
|
||||
public abstract Iterable<Site> findSitesByName(final String siteName);
|
||||
|
||||
public abstract void addSite(final Site site);
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
return this == obj || obj instanceof User && Objects.equals( getFullName(), ((User) obj).getFullName() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode( getFullName() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getFullName();
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
51
MasterPassword/Java/masterpassword-model/pom.xml
Normal file
51
MasterPassword/Java/masterpassword-model/pom.xml
Normal file
@@ -0,0 +1,51 @@
|
||||
<?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>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.auto.value</groupId>
|
||||
<artifactId>auto-value</artifactId>
|
||||
<version>1.0-rc1</version>
|
||||
<scope>provided</scope>
|
||||
</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>
|
||||
@@ -0,0 +1,143 @@
|
||||
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 java.util.Objects;
|
||||
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 final MPUser user;
|
||||
private int mpVersion;
|
||||
private Instant lastUsed;
|
||||
private String siteName;
|
||||
private MPSiteType siteType;
|
||||
private int siteCounter;
|
||||
private int uses;
|
||||
private String loginName;
|
||||
|
||||
public MPSite(final MPUser user, final String siteName) {
|
||||
this( user, siteName, DEFAULT_TYPE, DEFAULT_COUNTER );
|
||||
}
|
||||
|
||||
public MPSite(final MPUser user, final String siteName, final MPSiteType siteType, final int siteCounter) {
|
||||
this.user = user;
|
||||
this.mpVersion = MasterKey.ALGORITHM;
|
||||
this.lastUsed = new Instant();
|
||||
this.siteName = siteName;
|
||||
this.siteType = siteType;
|
||||
this.siteCounter = siteCounter;
|
||||
}
|
||||
|
||||
protected MPSite(final MPUser user, 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.user = user;
|
||||
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 );
|
||||
}
|
||||
|
||||
public MPUser getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
@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 updateLastUsed() {
|
||||
lastUsed = new Instant();
|
||||
user.updateLastUsed();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
return this == obj || obj instanceof MPSite && Objects.equals( siteName, ((MPSite) obj).siteName );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode( siteName );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return strf( "{MPSite: %s}", siteName );
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.lyndir.masterpassword.model;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
return this == obj || obj instanceof MPSiteResult && Objects.equals( site, ((MPSiteResult) obj).site );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode( site );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return strf( "{MPSiteResult: %s}", site );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
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.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( user, //
|
||||
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( user, //
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package com.lyndir.masterpassword.model;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
||||
|
||||
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 implements Comparable<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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
return this == obj || obj instanceof MPUser && Objects.equals( fullName, ((MPUser) obj).fullName );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode( fullName );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return strf( "{MPUser: %s}", fullName );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(final MPUser o) {
|
||||
int comparison = lastUsed.compareTo( o.lastUsed );
|
||||
if (comparison == 0)
|
||||
comparison = fullName.compareTo( o.fullName );
|
||||
|
||||
return comparison;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
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 File mpwd = new File( System.getProperty( "user.home" ), ".mpw.d" );
|
||||
private static final MPUserFileManager instance;
|
||||
|
||||
static {
|
||||
File mpwrc = new File( System.getProperty( "user.home" ), ".mpwrc" );
|
||||
if (mpwrc.exists() && !mpwd.exists())
|
||||
if (!mpwrc.renameTo( mpwd ))
|
||||
logger.err( "Couldn't migrate: %s -> %s", mpwrc, mpwd );
|
||||
|
||||
instance = create( mpwd );
|
||||
}
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.lyndir.masterpassword.model;
|
||||
|
||||
import com.google.common.collect.*;
|
||||
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( Ordering.natural() );
|
||||
}
|
||||
|
||||
public void addUser(final MPUser user) {
|
||||
usersByName.put( user.getFullName(), user );
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>com.lyndir.lhunath</groupId>
|
||||
<artifactId>lyndir</artifactId>
|
||||
<version>1.20</version>
|
||||
<version>1.18</version>
|
||||
</parent>
|
||||
|
||||
<name>Master Password</name>
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
<modules>
|
||||
<module>masterpassword-algorithm</module>
|
||||
<module>masterpassword-model</module>
|
||||
<module>masterpassword-cli</module>
|
||||
<module>masterpassword-gui</module>
|
||||
</modules>
|
||||
|
||||
@@ -19,8 +19,9 @@
|
||||
#import "MPStoredSiteEntity.h"
|
||||
#import "MPGeneratedSiteEntity.h"
|
||||
#import "MPSiteQuestionEntity.h"
|
||||
#import "mpw-algorithm.h"
|
||||
|
||||
#define MPAlgorithmDefaultVersion 2
|
||||
#define MPAlgorithmDefaultVersion MPAlgorithmVersionCurrent
|
||||
#define MPAlgorithmDefault MPAlgorithmForVersion(MPAlgorithmDefaultVersion)
|
||||
|
||||
id<MPAlgorithm> MPAlgorithmForVersion(NSUInteger version);
|
||||
@@ -43,7 +44,7 @@ NSString *NSStringFromTimeToCrack(TimeToCrack timeToCrack);
|
||||
@protocol MPAlgorithm<NSObject>
|
||||
|
||||
@required
|
||||
- (NSUInteger)version;
|
||||
- (MPAlgorithmVersion)version;
|
||||
- (BOOL)tryMigrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc;
|
||||
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit;
|
||||
|
||||
@@ -51,7 +52,6 @@ NSString *NSStringFromTimeToCrack(TimeToCrack timeToCrack);
|
||||
- (MPKey *)keyFromKeyData:(NSData *)keyData;
|
||||
- (NSData *)keyIDForKeyData:(NSData *)keyData;
|
||||
|
||||
- (NSString *)scopeForVariant:(MPSiteVariant)variant;
|
||||
- (NSString *)nameOfType:(MPSiteType)type;
|
||||
- (NSString *)shortNameOfType:(MPSiteType)type;
|
||||
- (NSString *)classNameOfType:(MPSiteType)type;
|
||||
|
||||
@@ -18,11 +18,4 @@
|
||||
#import "MPAlgorithm.h"
|
||||
|
||||
@interface MPAlgorithmV0 : NSObject<MPAlgorithm>
|
||||
|
||||
- (NSDictionary *)allCiphers;
|
||||
- (NSArray *)ciphersForType:(MPSiteType)type;
|
||||
- (NSArray *)cipherClasses;
|
||||
- (NSArray *)cipherClassCharacters;
|
||||
- (NSString *)charactersForCipherClass:(NSString *)cipherClass;
|
||||
|
||||
@end
|
||||
|
||||
@@ -19,16 +19,11 @@
|
||||
#import "MPEntities.h"
|
||||
#import "MPAppDelegate_Shared.h"
|
||||
#import "MPAppDelegate_InApp.h"
|
||||
#import "MPSiteQuestionEntity.h"
|
||||
#import "mpw-util.h"
|
||||
#import "mpw-types.h"
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/err.h>
|
||||
|
||||
#define MP_N 32768
|
||||
#define MP_r 8
|
||||
#define MP_p 2
|
||||
#define MP_dkLen 64
|
||||
#define MP_hash PearlHashSHA256
|
||||
|
||||
/* An AMD HD 7970 calculates 2495M SHA-1 hashes per second at a cost of ~350$ per GPU */
|
||||
#define CRACKING_PER_SECOND 2495000000UL
|
||||
#define CRACKING_PRICE 350
|
||||
@@ -53,9 +48,9 @@
|
||||
ctx = NULL;
|
||||
}
|
||||
|
||||
- (NSUInteger)version {
|
||||
- (MPAlgorithmVersion)version {
|
||||
|
||||
return 0;
|
||||
return MPAlgorithmVersion0;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
@@ -112,21 +107,13 @@
|
||||
|
||||
- (MPKey *)keyForPassword:(NSString *)password ofUserNamed:(NSString *)userName {
|
||||
|
||||
uint32_t nuserNameLength = htonl( userName.length );
|
||||
NSDate *start = [NSDate date];
|
||||
NSData *keyData = [PearlSCrypt deriveKeyWithLength:MP_dkLen fromPassword:[password dataUsingEncoding:NSUTF8StringEncoding]
|
||||
usingSalt:[NSData dataByConcatenatingDatas:
|
||||
[@"com.lyndir.masterpassword" dataUsingEncoding:NSUTF8StringEncoding],
|
||||
[NSData dataWithBytes:&nuserNameLength
|
||||
length:sizeof( nuserNameLength )],
|
||||
[userName dataUsingEncoding:NSUTF8StringEncoding],
|
||||
nil] N:MP_N r:MP_r p:MP_p];
|
||||
|
||||
MPKey *key = [self keyFromKeyData:keyData];
|
||||
trc( @"User: %@, password: %@ derives to key ID: %@ (took %0.2fs)", userName, password, [key.keyID encodeHex],
|
||||
uint8_t const *masterKeyBytes = mpw_masterKeyForUser( userName.UTF8String, password.UTF8String, [self version] );
|
||||
MPKey *masterKey = [self keyFromKeyData:[NSData dataWithBytes:masterKeyBytes length:MP_dkLen]];
|
||||
mpw_free( masterKeyBytes, MP_dkLen );
|
||||
trc( @"User: %@, password: %@ derives to key ID: %@ (took %0.2fs)", userName, password, [masterKey.keyID encodeHex],
|
||||
-[start timeIntervalSinceNow] );
|
||||
|
||||
return key;
|
||||
return masterKey;
|
||||
}
|
||||
|
||||
- (MPKey *)keyFromKeyData:(NSData *)keyData {
|
||||
@@ -136,21 +123,7 @@
|
||||
|
||||
- (NSData *)keyIDForKeyData:(NSData *)keyData {
|
||||
|
||||
return [keyData hashWith:MP_hash];
|
||||
}
|
||||
|
||||
- (NSString *)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";
|
||||
}
|
||||
|
||||
Throw( @"Unsupported variant: %ld", (long)variant );
|
||||
return [keyData hashWith:PearlHashSHA256];
|
||||
}
|
||||
|
||||
- (NSString *)nameOfType:(MPSiteType)type {
|
||||
@@ -327,40 +300,6 @@
|
||||
return previousType;
|
||||
}
|
||||
|
||||
- (NSDictionary *)allCiphers {
|
||||
|
||||
static NSDictionary *ciphers = nil;
|
||||
static dispatch_once_t once = 0;
|
||||
dispatch_once( &once, ^{
|
||||
ciphers = [NSDictionary dictionaryWithContentsOfURL:
|
||||
[[NSBundle mainBundle] URLForResource:@"ciphers" withExtension:@"plist"]];
|
||||
} );
|
||||
|
||||
return ciphers;
|
||||
}
|
||||
|
||||
- (NSArray *)ciphersForType:(MPSiteType)type {
|
||||
|
||||
NSString *typeClass = [self classNameOfType:type];
|
||||
NSString *typeName = [self nameOfType:type];
|
||||
return [[[self allCiphers] valueForKey:typeClass] valueForKey:typeName];
|
||||
}
|
||||
|
||||
- (NSArray *)cipherClasses {
|
||||
|
||||
return [[[self allCiphers] valueForKey:@"MPCharacterClasses"] allKeys];
|
||||
}
|
||||
|
||||
- (NSArray *)cipherClassCharacters {
|
||||
|
||||
return [[[self allCiphers] valueForKey:@"MPCharacterClasses"] allValues];
|
||||
}
|
||||
|
||||
- (NSString *)charactersForCipherClass:(NSString *)cipherClass {
|
||||
|
||||
return [NSNullToNil( [NSNullToNil( [[self allCiphers] valueForKey:@"MPCharacterClasses"] ) valueForKey:cipherClass] ) copy];
|
||||
}
|
||||
|
||||
- (NSString *)generateLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key {
|
||||
|
||||
return [self generateContentForSiteNamed:name ofType:MPSiteTypeGeneratedName withCounter:1
|
||||
@@ -383,44 +322,10 @@
|
||||
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
|
||||
variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key {
|
||||
|
||||
// Determine the seed whose bytes will be used for calculating a password
|
||||
uint32_t ncounter = htonl( counter ), nnameLength = htonl( name.length ), ncontextLength = htonl( context.length );
|
||||
NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof( ncounter )];
|
||||
NSData *nameLengthBytes = [NSData dataWithBytes:&nnameLength length:sizeof( nnameLength )];
|
||||
NSData *contextLengthBytes = [NSData dataWithBytes:&ncontextLength length:sizeof( ncontextLength )];
|
||||
NSString *scope = [self scopeForVariant:variant];
|
||||
trc( @"seed from: hmac-sha256(%@, %@ | %@ | %@ | %@ | %@)",
|
||||
[[key keyID] encodeHex], scope, [nameLengthBytes encodeHex], name, [counterBytes encodeHex], context );
|
||||
NSData *seed = [[NSData dataByConcatenatingDatas:
|
||||
[scope dataUsingEncoding:NSUTF8StringEncoding],
|
||||
nameLengthBytes,
|
||||
[name dataUsingEncoding:NSUTF8StringEncoding],
|
||||
counterBytes,
|
||||
context? contextLengthBytes: nil,
|
||||
[context dataUsingEncoding:NSUTF8StringEncoding],
|
||||
nil]
|
||||
hmacWith:PearlHashSHA256 key:key.keyData];
|
||||
trc( @"seed is: %@", [seed encodeHex] );
|
||||
const char *seedBytes = seed.bytes;
|
||||
|
||||
// Determine the cipher from the first seed byte.
|
||||
NSAssert( [seed length], @"Missing seed." );
|
||||
NSArray *typeCiphers = [self ciphersForType:type];
|
||||
NSString *cipher = typeCiphers[htons( seedBytes[0] ) % [typeCiphers count]];
|
||||
trc( @"type %@ (%lu), ciphers: %@, selected: %@", [self nameOfType:type], (unsigned long)type, typeCiphers, cipher );
|
||||
|
||||
// Encode the content, character by character, using subsequent seed bytes and the cipher.
|
||||
NSAssert( [seed length] >= [cipher length] + 1, @"Insufficient seed bytes to encode cipher." );
|
||||
NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]];
|
||||
for (NSUInteger c = 0; c < [cipher length]; ++c) {
|
||||
uint16_t keyByte = htons( seedBytes[c + 1] );
|
||||
NSString *cipherClass = [cipher substringWithRange:NSMakeRange( c, 1 )];
|
||||
NSString *cipherClassCharacters = [self charactersForCipherClass:cipherClass];
|
||||
NSString *character = [cipherClassCharacters substringWithRange:NSMakeRange( keyByte % [cipherClassCharacters length], 1 )];
|
||||
|
||||
trc( @"class %@ has characters: %@, index: %u, selected: %@", cipherClass, cipherClassCharacters, keyByte, character );
|
||||
[content appendString:character];
|
||||
}
|
||||
char const *contentBytes = mpw_passwordForSite( key.keyData.bytes, name.UTF8String, type, (uint32_t)counter,
|
||||
variant, context.UTF8String, [self version] );
|
||||
NSString *content = [NSString stringWithCString:contentBytes encoding:NSUTF8StringEncoding];
|
||||
mpw_freeString( contentBytes );
|
||||
|
||||
return content;
|
||||
}
|
||||
@@ -793,6 +698,8 @@
|
||||
|
||||
- (NSString *)decryptContent:(NSData *)encryptedContent usingKey:(MPKey *)key {
|
||||
|
||||
if (!key)
|
||||
return nil;
|
||||
NSData *decryptedContent = nil;
|
||||
if ([encryptedContent length])
|
||||
decryptedContent = [encryptedContent decryptWithSymmetricKey:[key subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
|
||||
@@ -806,21 +713,23 @@
|
||||
|
||||
if (!type)
|
||||
return NO;
|
||||
NSArray *ciphers = [self ciphersForType:type];
|
||||
if (!ciphers)
|
||||
size_t count = 0;
|
||||
const char **templates = mpw_templatesForType( type, &count );
|
||||
if (!templates)
|
||||
return NO;
|
||||
|
||||
BIGNUM *permutations = BN_new(), *cipherPermutations = BN_new();
|
||||
for (NSString *cipher in ciphers) {
|
||||
BN_one( cipherPermutations );
|
||||
BIGNUM *permutations = BN_new(), *templatePermutations = BN_new();
|
||||
for (int t = 0; t < count; ++t) {
|
||||
const char *template = templates[t];
|
||||
BN_one( templatePermutations );
|
||||
|
||||
for (NSUInteger c = 0; c < [cipher length]; ++c)
|
||||
BN_mul_word( cipherPermutations,
|
||||
(BN_ULONG)[[self charactersForCipherClass:[cipher substringWithRange:NSMakeRange( c, 1 )]] length] );
|
||||
for (NSUInteger c = 0; c < strlen( template ); ++c)
|
||||
BN_mul_word( templatePermutations,
|
||||
(BN_ULONG)strlen( mpw_charactersInClass( template[c] ) ) );
|
||||
|
||||
BN_add( permutations, permutations, cipherPermutations );
|
||||
BN_add( permutations, permutations, templatePermutations );
|
||||
}
|
||||
BN_free( cipherPermutations );
|
||||
BN_free( templatePermutations );
|
||||
|
||||
return [self timeToCrack:timeToCrack permutations:permutations forAttacker:attacker];
|
||||
}
|
||||
@@ -830,25 +739,21 @@
|
||||
BIGNUM *permutations = BN_new();
|
||||
BN_one( permutations );
|
||||
|
||||
NSMutableString *cipher = [NSMutableString new];
|
||||
for (NSUInteger c = 0; c < [password length]; ++c) {
|
||||
NSString *passwordCharacter = [password substringWithRange:NSMakeRange( c, 1 )];
|
||||
const char passwordCharacter = [password substringWithRange:NSMakeRange( c, 1 )].UTF8String[0];
|
||||
|
||||
unsigned int characterEntropy = 0;
|
||||
for (NSString *cipherClass in @[ @"v", @"c", @"a", @"x" ]) {
|
||||
NSString *charactersForClass = [self charactersForCipherClass:cipherClass];
|
||||
for (NSString *characterClass in @[ @"v", @"c", @"a", @"x" ]) {
|
||||
char const *charactersForClass = mpw_charactersInClass( characterClass.UTF8String[0] );
|
||||
|
||||
if ([charactersForClass rangeOfString:passwordCharacter].location != NSNotFound) {
|
||||
if (strchr( charactersForClass, passwordCharacter )) {
|
||||
// Found class for password character.
|
||||
characterEntropy = (BN_ULONG)[charactersForClass length];
|
||||
[cipher appendString:cipherClass];
|
||||
characterEntropy = (BN_ULONG)strlen(charactersForClass);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!characterEntropy) {
|
||||
[cipher appendString:@"b"];
|
||||
if (!characterEntropy)
|
||||
characterEntropy = 256 /* a byte */;
|
||||
}
|
||||
|
||||
BN_mul_word( permutations, characterEntropy );
|
||||
}
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
|
||||
@implementation MPAlgorithmV1
|
||||
|
||||
- (NSUInteger)version {
|
||||
- (MPAlgorithmVersion)version {
|
||||
|
||||
return 1;
|
||||
return MPAlgorithmVersion1;
|
||||
}
|
||||
|
||||
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit {
|
||||
@@ -45,49 +45,4 @@
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
|
||||
variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key {
|
||||
|
||||
// Determine the seed whose bytes will be used for calculating a password
|
||||
uint32_t ncounter = htonl( counter ), nnameLength = htonl( name.length ), ncontextLength = htonl( context.length );
|
||||
NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof( ncounter )];
|
||||
NSData *nameLengthBytes = [NSData dataWithBytes:&nnameLength length:sizeof( nnameLength )];
|
||||
NSData *contextLengthBytes = [NSData dataWithBytes:&ncontextLength length:sizeof( ncontextLength )];
|
||||
NSString *scope = [self scopeForVariant:variant];
|
||||
trc( @"seed from: hmac-sha256(%@, %@ | %@ | %@ | %@)",
|
||||
[[key keyID] encodeHex], scope, [nameLengthBytes encodeHex], name, [counterBytes encodeHex] );
|
||||
NSData *seed = [[NSData dataByConcatenatingDatas:
|
||||
[scope dataUsingEncoding:NSUTF8StringEncoding],
|
||||
nameLengthBytes,
|
||||
[name dataUsingEncoding:NSUTF8StringEncoding],
|
||||
counterBytes,
|
||||
context? contextLengthBytes: nil,
|
||||
[context dataUsingEncoding:NSUTF8StringEncoding],
|
||||
nil]
|
||||
hmacWith:PearlHashSHA256 key:key.keyData];
|
||||
trc( @"seed is: %@", [seed encodeHex] );
|
||||
const unsigned char *seedBytes = seed.bytes;
|
||||
|
||||
// Determine the cipher from the first seed byte.
|
||||
NSAssert( [seed length], @"Missing seed." );
|
||||
NSArray *typeCiphers = [self ciphersForType:type];
|
||||
NSString *cipher = typeCiphers[seedBytes[0] % [typeCiphers count]];
|
||||
trc( @"type %@ (%lu), ciphers: %@, selected: %@", [self nameOfType:type], (unsigned long)type, typeCiphers, cipher );
|
||||
|
||||
// Encode the content, character by character, using subsequent seed bytes and the cipher.
|
||||
NSAssert( [seed length] >= [cipher length] + 1, @"Insufficient seed bytes to encode cipher." );
|
||||
NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]];
|
||||
for (NSUInteger c = 0; c < [cipher length]; ++c) {
|
||||
uint16_t keyByte = seedBytes[c + 1];
|
||||
NSString *cipherClass = [cipher substringWithRange:NSMakeRange( c, 1 )];
|
||||
NSString *cipherClassCharacters = [self charactersForCipherClass:cipherClass];
|
||||
NSString *character = [cipherClassCharacters substringWithRange:NSMakeRange( keyByte % [cipherClassCharacters length], 1 )];
|
||||
|
||||
trc( @"class %@ has characters: %@, index: %u, selected: %@", cipherClass, cipherClassCharacters, keyByte, character );
|
||||
[content appendString:character];
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
*/
|
||||
|
||||
//
|
||||
// MPAlgorithmV1
|
||||
// MPAlgorithmV2
|
||||
//
|
||||
// Created by Maarten Billemont on 17/07/12.
|
||||
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
*/
|
||||
|
||||
//
|
||||
// MPAlgorithmV1
|
||||
// MPAlgorithmV2
|
||||
//
|
||||
// Created by Maarten Billemont on 17/07/12.
|
||||
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
|
||||
@@ -21,9 +21,9 @@
|
||||
|
||||
@implementation MPAlgorithmV2
|
||||
|
||||
- (NSUInteger)version {
|
||||
- (MPAlgorithmVersion)version {
|
||||
|
||||
return 2;
|
||||
return MPAlgorithmVersion2;
|
||||
}
|
||||
|
||||
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit {
|
||||
@@ -46,52 +46,4 @@
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
|
||||
variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key {
|
||||
|
||||
// Determine the seed whose bytes will be used for calculating a password
|
||||
NSData *nameBytes = [name dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSData *contextBytes = [context dataUsingEncoding:NSUTF8StringEncoding];
|
||||
uint32_t ncounter = htonl( counter ), nnameLength = htonl( nameBytes.length ), ncontextLength = htonl( contextBytes.length );
|
||||
NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof( ncounter )];
|
||||
NSData *nameLengthBytes = [NSData dataWithBytes:&nnameLength length:sizeof( nnameLength )];
|
||||
NSData *contextLengthBytes = [NSData dataWithBytes:&ncontextLength length:sizeof( ncontextLength )];
|
||||
NSString *scope = [self scopeForVariant:variant];
|
||||
NSData *scopeBytes = [scope dataUsingEncoding:NSUTF8StringEncoding];
|
||||
trc( @"seed from: hmac-sha256(%@, %@ | %@ | %@ | %@)",
|
||||
[[key keyID] encodeHex], scope, [nameLengthBytes encodeHex], name, [counterBytes encodeHex] );
|
||||
NSData *seed = [[NSData dataByConcatenatingDatas:
|
||||
scopeBytes,
|
||||
nameLengthBytes,
|
||||
nameBytes,
|
||||
counterBytes,
|
||||
context? contextLengthBytes: nil,
|
||||
contextBytes,
|
||||
nil]
|
||||
hmacWith:PearlHashSHA256 key:key.keyData];
|
||||
trc( @"seed is: %@", [seed encodeHex] );
|
||||
const unsigned char *seedBytes = seed.bytes;
|
||||
|
||||
// Determine the cipher from the first seed byte.
|
||||
NSAssert( [seed length], @"Missing seed." );
|
||||
NSArray *typeCiphers = [self ciphersForType:type];
|
||||
NSString *cipher = typeCiphers[seedBytes[0] % [typeCiphers count]];
|
||||
trc( @"type %@ (%lu), ciphers: %@, selected: %@", [self nameOfType:type], (unsigned long)type, typeCiphers, cipher );
|
||||
|
||||
// Encode the content, character by character, using subsequent seed bytes and the cipher.
|
||||
NSAssert( [seed length] >= [cipher length] + 1, @"Insufficient seed bytes to encode cipher." );
|
||||
NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]];
|
||||
for (NSUInteger c = 0; c < [cipher length]; ++c) {
|
||||
uint16_t keyByte = seedBytes[c + 1];
|
||||
NSString *cipherClass = [cipher substringWithRange:NSMakeRange( c, 1 )];
|
||||
NSString *cipherClassCharacters = [self charactersForCipherClass:cipherClass];
|
||||
NSString *character = [cipherClassCharacters substringWithRange:NSMakeRange( keyByte % [cipherClassCharacters length], 1 )];
|
||||
|
||||
trc( @"class %@ has characters: %@, index: %u, selected: %@", cipherClass, cipherClassCharacters, keyByte, character );
|
||||
[content appendString:character];
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
21
MasterPassword/ObjC/MPAlgorithmV3.h
Normal file
21
MasterPassword/ObjC/MPAlgorithmV3.h
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
||||
*
|
||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*
|
||||
* @author Maarten Billemont <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPAlgorithmV3
|
||||
//
|
||||
// Created by Maarten Billemont on 13/01/15.
|
||||
// Copyright 2015 lhunath (Maarten Billemont). All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPAlgorithmV2.h"
|
||||
|
||||
@interface MPAlgorithmV3 : MPAlgorithmV2
|
||||
@end
|
||||
48
MasterPassword/ObjC/MPAlgorithmV3.m
Normal file
48
MasterPassword/ObjC/MPAlgorithmV3.m
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
|
||||
*
|
||||
* See the enclosed file LICENSE for license information (LGPLv3). If you did
|
||||
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*
|
||||
* @author Maarten Billemont <lhunath@lyndir.com>
|
||||
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
*/
|
||||
|
||||
//
|
||||
// MPAlgorithmV3
|
||||
//
|
||||
// Created by Maarten Billemont on 13/01/15.
|
||||
// Copyright 2015 lhunath (Maarten Billemont). All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPAlgorithmV3.h"
|
||||
#import "MPEntities.h"
|
||||
|
||||
@implementation MPAlgorithmV3
|
||||
|
||||
- (MPAlgorithmVersion)version {
|
||||
|
||||
return MPAlgorithmVersion3;
|
||||
}
|
||||
|
||||
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit {
|
||||
|
||||
if (site.version != [self version] - 1)
|
||||
// Only migrate from previous version.
|
||||
return NO;
|
||||
|
||||
if (!explicit) {
|
||||
if (site.type & MPSiteTypeClassGenerated && site.name.length != [site.name dataUsingEncoding:NSUTF8StringEncoding].length) {
|
||||
// This migration requires explicit permission for types of the generated class.
|
||||
site.requiresExplicitMigration = YES;
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply migration.
|
||||
site.requiresExplicitMigration = NO;
|
||||
site.version = [self version];
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -611,7 +611,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
}
|
||||
if (!headerEnded)
|
||||
continue;
|
||||
if (!importKeyID || ![importUserName length])
|
||||
if (![importUserName length])
|
||||
return MPImportResultMalformedInput;
|
||||
if (![importedSiteLine length])
|
||||
continue;
|
||||
@@ -689,12 +689,11 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
if (user && ![userKey.keyID isEqualToData:user.keyID])
|
||||
return MPImportResultInvalidPassword;
|
||||
__block MPKey *importKey = userKey;
|
||||
if (![importKey.keyID isEqualToData:importKeyID])
|
||||
if (importKeyID && ![importKey.keyID isEqualToData:importKeyID])
|
||||
importKey = [importAlgorithm keyForPassword:askImportPassword( importUserName ) ofUserNamed:importUserName];
|
||||
if (![importKey.keyID isEqualToData:importKeyID])
|
||||
if (importKeyID && ![importKey.keyID isEqualToData:importKeyID])
|
||||
return MPImportResultInvalidPassword;
|
||||
|
||||
|
||||
// Delete existing sites.
|
||||
if (sitesToDelete.count)
|
||||
[sitesToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
|
||||
@@ -711,7 +710,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
else {
|
||||
user = [MPUserEntity insertNewObjectInContext:context];
|
||||
user.name = importUserName;
|
||||
user.keyID = importKeyID;
|
||||
user.keyID = [userKey keyID];
|
||||
if (importAvatar != NSNotFound)
|
||||
user.avatar = importAvatar;
|
||||
dbg( @"Created User: %@", [user debugDescription] );
|
||||
|
||||
@@ -6,45 +6,6 @@
|
||||
// Copyright (c) 2012 Lyndir. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPKey.h"
|
||||
|
||||
typedef NS_ENUM( NSUInteger, MPSiteTypeClass ) {
|
||||
/** Generate the password. */
|
||||
MPSiteTypeClassGenerated = 1 << 4,
|
||||
/** Store the password. */
|
||||
MPSiteTypeClassStored = 1 << 5,
|
||||
};
|
||||
|
||||
typedef NS_ENUM( NSUInteger, MPSiteVariant ) {
|
||||
/** Generate the password. */
|
||||
MPSiteVariantPassword,
|
||||
/** Generate the login name. */
|
||||
MPSiteVariantLogin,
|
||||
/** Generate a security answer. */
|
||||
MPSiteVariantAnswer,
|
||||
};
|
||||
|
||||
typedef NS_ENUM( NSUInteger, MPSiteFeature ) {
|
||||
/** Export the key-protected content data. */
|
||||
MPSiteFeatureExportContent = 1 << 10,
|
||||
/** Never export content. */
|
||||
MPSiteFeatureDevicePrivate = 1 << 11,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSUInteger, MPSiteType) {
|
||||
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,
|
||||
};
|
||||
|
||||
#define MPErrorDomain @"MPErrorDomain"
|
||||
|
||||
#define MPSignedInNotification @"MPSignedInNotification"
|
||||
|
||||
@@ -58,6 +58,33 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
||||
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||
|
||||
#ifdef CRASHLYTICS
|
||||
NSString *crashlyticsAPIKey = [self crashlyticsAPIKey];
|
||||
if ([crashlyticsAPIKey length]) {
|
||||
inf(@"Initializing Crashlytics");
|
||||
#if defined (DEBUG) || defined (ADHOC)
|
||||
[Crashlytics sharedInstance].debugMode = YES;
|
||||
#endif
|
||||
[Crashlytics setUserIdentifier:[PearlKeyChain deviceIdentifier]];
|
||||
[Crashlytics setObjectValue:[PearlKeyChain deviceIdentifier] forKey:@"deviceIdentifier"];
|
||||
[Crashlytics setUserName:@"Anonymous"];
|
||||
[Crashlytics setObjectValue:@"Anonymous" forKey:@"username"];
|
||||
[Crashlytics startWithAPIKey:crashlyticsAPIKey];
|
||||
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
|
||||
PearlLogLevel level = PearlLogLevelInfo;
|
||||
if ([[MPConfig get].sendInfo boolValue])
|
||||
level = PearlLogLevelDebug;
|
||||
|
||||
if (message.level >= level)
|
||||
CLSLog( @"%@", [message messageDescription] );
|
||||
|
||||
return YES;
|
||||
}];
|
||||
CLSLog( @"Crashlytics (%@) initialized for: %@ v%@.", //
|
||||
[Crashlytics sharedInstance].version, [PearlInfoPlist get].CFBundleName, [PearlInfoPlist get].CFBundleVersion );
|
||||
}
|
||||
#endif
|
||||
|
||||
// Setup delegates and listeners.
|
||||
[MPConfig get].delegate = self;
|
||||
__weak id weakSelf = self;
|
||||
@@ -224,6 +251,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
||||
return;
|
||||
|
||||
NSURL *url = openPanel.URL;
|
||||
[openPanel close];
|
||||
|
||||
PearlNotMainQueue( ^{
|
||||
NSError *error;
|
||||
@@ -285,19 +313,19 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
||||
case MPImportResultCancelled:
|
||||
break;
|
||||
case MPImportResultInternalError:
|
||||
[NSAlert alertWithError:[NSError errorWithDomain:MPErrorDomain code:0 userInfo:@{
|
||||
[[NSAlert alertWithError:[NSError errorWithDomain:MPErrorDomain code:0 userInfo:@{
|
||||
NSLocalizedDescriptionKey : @"Import failed because of an internal error."
|
||||
}]];
|
||||
}]] runModal];
|
||||
break;
|
||||
case MPImportResultMalformedInput:
|
||||
[NSAlert alertWithError:[NSError errorWithDomain:MPErrorDomain code:0 userInfo:@{
|
||||
[[NSAlert alertWithError:[NSError errorWithDomain:MPErrorDomain code:0 userInfo:@{
|
||||
NSLocalizedDescriptionKey : @"The import doesn't look like a Master Password export."
|
||||
}]];
|
||||
}]] runModal];
|
||||
break;
|
||||
case MPImportResultInvalidPassword:
|
||||
[NSAlert alertWithError:[NSError errorWithDomain:MPErrorDomain code:0 userInfo:@{
|
||||
[[NSAlert alertWithError:[NSError errorWithDomain:MPErrorDomain code:0 userInfo:@{
|
||||
NSLocalizedDescriptionKey : @"Incorrect master password for the import sites."
|
||||
}]];
|
||||
}]] runModal];
|
||||
break;
|
||||
}
|
||||
} );
|
||||
@@ -604,4 +632,25 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:NSStringFromSelector( configKey )];
|
||||
}
|
||||
|
||||
#pragma mark - Crashlytics
|
||||
|
||||
- (NSDictionary *)crashlyticsInfo {
|
||||
|
||||
static NSDictionary *crashlyticsInfo = nil;
|
||||
if (crashlyticsInfo == nil)
|
||||
crashlyticsInfo = [[NSDictionary alloc] initWithContentsOfURL:
|
||||
[[NSBundle mainBundle] URLForResource:@"Crashlytics" withExtension:@"plist"]];
|
||||
|
||||
return crashlyticsInfo;
|
||||
}
|
||||
|
||||
- (NSString *)crashlyticsAPIKey {
|
||||
|
||||
NSString *crashlyticsAPIKey = NSNullToNil( [[self crashlyticsInfo] valueForKeyPath:@"API Key"] );
|
||||
if (![crashlyticsAPIKey length])
|
||||
wrn( @"Crashlytics API key not set. Crash logs won't be recorded." );
|
||||
|
||||
return crashlyticsAPIKey;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -44,6 +44,8 @@
|
||||
|
||||
[super windowDidLoad];
|
||||
|
||||
[self replaceFonts:self.window.contentView];
|
||||
|
||||
// [[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillBecomeActiveNotification object:nil
|
||||
// queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
// [self fadeIn];
|
||||
@@ -95,6 +97,20 @@
|
||||
self.siteTable.controller = self;
|
||||
}
|
||||
|
||||
- (void)replaceFonts:(NSView *)view {
|
||||
|
||||
if (view.window.backingScaleFactor == 1)
|
||||
[view enumerateViews:^(NSView *subview, BOOL *stop, BOOL *recurse) {
|
||||
if ([subview respondsToSelector:@selector( setFont: )]) {
|
||||
NSFont *font = [(id)subview font];
|
||||
if ([font.fontName isEqualToString:@"HelveticaNeue-Thin"])
|
||||
[(id)subview setFont:[NSFont fontWithName:@"HelveticaNeue" matrix:font.matrix]];
|
||||
if ([font.fontName isEqualToString:@"HelveticaNeue-Light"])
|
||||
[(id)subview setFont:[NSFont fontWithName:@"HelveticaNeue" matrix:font.matrix]];
|
||||
}
|
||||
} recurse:YES];
|
||||
}
|
||||
|
||||
- (void)flagsChanged:(NSEvent *)theEvent {
|
||||
|
||||
BOOL alternatePressed = (theEvent.modifierFlags & NSAlternateKeyMask) != 0;
|
||||
@@ -186,6 +202,11 @@
|
||||
|
||||
#pragma mark - NSTableViewDelegate
|
||||
|
||||
- (void)tableView:(NSTableView *)tableView didAddRowView:(NSTableRowView *)rowView forRow:(NSInteger)row {
|
||||
|
||||
[self replaceFonts:rowView];
|
||||
}
|
||||
|
||||
#pragma mark - NSAlert
|
||||
|
||||
- (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
|
||||
@@ -513,13 +534,13 @@
|
||||
NSMutableArray *fuzzyGroups = [NSMutableArray new];
|
||||
[fuzzyRE enumerateMatchesInString:queryString options:0 range:NSMakeRange( 0, queryString.length )
|
||||
usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
|
||||
[fuzzyGroups addObject:[queryString substringWithRange:result.range] ];
|
||||
[fuzzyGroups addObject:[queryString substringWithRange:result.range]];
|
||||
}];
|
||||
[MPMacAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
|
||||
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
|
||||
fetchRequest.sortDescriptors = @[ [[NSSortDescriptor alloc] initWithKey:@"lastUsed" ascending:NO] ];
|
||||
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name LIKE[cd] %@) AND user == %@",
|
||||
queryPattern, queryPattern, [MPMacAppDelegate get].activeUserOID];
|
||||
queryPattern, queryPattern, [MPMacAppDelegate get].activeUserOID];
|
||||
|
||||
NSError *error = nil;
|
||||
NSArray *siteResults = [context executeFetchRequest:fetchRequest error:&error];
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6250" systemVersion="14A389" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6254" systemVersion="14B25" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6250"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6254"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="MPPasswordWindowController">
|
||||
@@ -26,7 +26,7 @@
|
||||
<windowCollectionBehavior key="collectionBehavior" transient="YES" ignoresCycle="YES" fullScreenAuxiliary="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="0.0" y="0.0" width="640" height="560"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="877"/>
|
||||
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="640" height="560"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
@@ -144,7 +144,7 @@
|
||||
</binding>
|
||||
</connections>
|
||||
</button>
|
||||
<scrollView focusRingType="none" borderType="none" autohidesScrollers="YES" horizontalLineScroll="35" horizontalPageScroll="10" verticalLineScroll="35" verticalPageScroll="10" hasHorizontalScroller="NO" horizontalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="Bme-XK-MMc" userLabel="Sites Table">
|
||||
<scrollView focusRingType="none" borderType="none" autohidesScrollers="YES" horizontalLineScroll="35" horizontalPageScroll="10" verticalLineScroll="35" verticalPageScroll="10" hasHorizontalScroller="NO" hasVerticalScroller="NO" horizontalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="Bme-XK-MMc" userLabel="Sites Table">
|
||||
<rect key="frame" x="64" y="80" width="512" height="147"/>
|
||||
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="e11-59-xSS">
|
||||
<rect key="frame" x="0.0" y="0.0" width="512" height="147"/>
|
||||
|
||||
@@ -8,12 +8,14 @@
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
93D390C676DF52DA7E459F19 /* MPPasswordWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39D9D0061FF1159998F06 /* MPPasswordWindow.m */; };
|
||||
93D391E61DC23E128DA4446C /* NSView+Traversing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D393EE88DE554BCCBC1C2D /* NSView+Traversing.h */; };
|
||||
93D392EC39DA43C46C692C12 /* NSDictionary+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */; };
|
||||
93D394C4254EEB45FB335AFB /* MPSitesTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39423D7BF4FD31FE6D27C /* MPSitesTableView.m */; };
|
||||
93D395E4830290EBB6E71F34 /* MPNoStateButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39538C4CEFF46DF379254 /* MPNoStateButton.m */; };
|
||||
93D395F08A087F8A24689347 /* NSArray+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */; };
|
||||
93D3970BCF85F7902E611168 /* PearlProfiler.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39DB3A8ADED08C39A6228 /* PearlProfiler.m */; };
|
||||
93D39784E725A34D1EE3FB3B /* MPInitialWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39D3CB30874147D9A9E1B /* MPInitialWindowController.m */; };
|
||||
93D3987F6D9046DBEE4D8364 /* NSView+Traversing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D392870DF659AFC1870521 /* NSView+Traversing.m */; };
|
||||
93D39C34FE35830EF5BE1D2A /* NSArray+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D396D04E57792A54D437AC /* NSArray+Indexing.h */; };
|
||||
93D39C5789EFA607CF788082 /* MPSiteModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39E73BF5CBF8E5B005CD3 /* MPSiteModel.m */; };
|
||||
93D39D304F73B3BBA031522A /* PearlProfiler.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D394EEFF5BF555A55AF361 /* PearlProfiler.h */; };
|
||||
@@ -75,6 +77,21 @@
|
||||
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 */; };
|
||||
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 */; };
|
||||
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 */; };
|
||||
DA89D4EC1A51EABD00AC64D7 /* Pearl-Cocoa.h in Headers */ = {isa = PBXBuildFile; fileRef = DA89D4EA1A51EABD00AC64D7 /* Pearl-Cocoa.h */; };
|
||||
DA89D4ED1A51EABD00AC64D7 /* Pearl-Cocoa.m in Sources */ = {isa = PBXBuildFile; fileRef = DA89D4EB1A51EABD00AC64D7 /* Pearl-Cocoa.m */; };
|
||||
DA8ED895192906920099B726 /* PearlTween.m in Sources */ = {isa = PBXBuildFile; fileRef = DA8ED891192906920099B726 /* PearlTween.m */; };
|
||||
DA8ED896192906920099B726 /* PearlTween.h in Headers */ = {isa = PBXBuildFile; fileRef = DA8ED892192906920099B726 /* PearlTween.h */; };
|
||||
DA8ED897192906920099B726 /* map-macro.h in Headers */ = {isa = PBXBuildFile; fileRef = DA8ED894192906920099B726 /* map-macro.h */; };
|
||||
@@ -211,6 +228,15 @@
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
DA6774391A474A03004F356A /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = /usr/share/man/man1/;
|
||||
dstSubfolderSpec = 0;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 1;
|
||||
};
|
||||
DAADCC4E19FB006500987B1D /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -236,10 +262,12 @@
|
||||
/* Begin PBXFileReference section */
|
||||
93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Indexing.m"; sourceTree = "<group>"; };
|
||||
93D39240B5143E01F0B75E96 /* MPSiteModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSiteModel.h; sourceTree = "<group>"; };
|
||||
93D392870DF659AFC1870521 /* NSView+Traversing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSView+Traversing.m"; sourceTree = "<group>"; };
|
||||
93D392A4F3DE0BD758B9B056 /* MPNoStateButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPNoStateButton.h; sourceTree = "<group>"; };
|
||||
93D392C3918763B3B72CF366 /* MPPasswordWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordWindowController.h; sourceTree = "<group>"; };
|
||||
93D39368EF3CBFEF2AFCA15A /* MPInitialWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPInitialWindowController.h; sourceTree = "<group>"; };
|
||||
93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+Indexing.h"; sourceTree = "<group>"; };
|
||||
93D393EE88DE554BCCBC1C2D /* NSView+Traversing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSView+Traversing.h"; sourceTree = "<group>"; };
|
||||
93D39423D7BF4FD31FE6D27C /* MPSitesTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSitesTableView.m; sourceTree = "<group>"; };
|
||||
93D394EEFF5BF555A55AF361 /* PearlProfiler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PearlProfiler.h; path = ../../../External/Pearl/Pearl/PearlProfiler.h; sourceTree = "<group>"; };
|
||||
93D39538C4CEFF46DF379254 /* MPNoStateButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPNoStateButton.m; sourceTree = "<group>"; };
|
||||
@@ -742,6 +770,25 @@
|
||||
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; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
DA89D4EA1A51EABD00AC64D7 /* Pearl-Cocoa.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "Pearl-Cocoa.h"; sourceTree = "<group>"; };
|
||||
DA89D4EB1A51EABD00AC64D7 /* Pearl-Cocoa.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "Pearl-Cocoa.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>"; };
|
||||
DA8ED894192906920099B726 /* map-macro.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "map-macro.h"; sourceTree = "<group>"; };
|
||||
@@ -888,6 +935,13 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DA6774381A474A03004F356A /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
DAADCC4D19FB006500987B1D /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -939,6 +993,7 @@
|
||||
DA5BFA39147E415C00F98B1E = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DA6773291A4746AF004F356A /* C */,
|
||||
DA5E5C961724A667003798D8 /* ObjC */,
|
||||
DACA23B41705DF7D002C6C22 /* Resources */,
|
||||
DACA22121705DDC5002C6C22 /* External */,
|
||||
@@ -956,6 +1011,7 @@
|
||||
DAC77CAD148291A600BCF976 /* libPearl.a */,
|
||||
DAC6326C148680650075AEA5 /* libjrswizzle.a */,
|
||||
DAADCC5019FB006500987B1D /* libKCOrderedAccessorFix.a */,
|
||||
DA67743B1A474A03004F356A /* mpw-test */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -1476,6 +1532,41 @@
|
||||
path = Insignia;
|
||||
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 */,
|
||||
);
|
||||
name = C;
|
||||
path = ../../C;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DA89D4E51A51E53100AC64D7 /* Pearl-Cocoa */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DA89D4EA1A51EABD00AC64D7 /* Pearl-Cocoa.h */,
|
||||
DA89D4EB1A51EABD00AC64D7 /* Pearl-Cocoa.m */,
|
||||
93D392870DF659AFC1870521 /* NSView+Traversing.m */,
|
||||
93D393EE88DE554BCCBC1C2D /* NSView+Traversing.h */,
|
||||
);
|
||||
path = "Pearl-Cocoa";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DA8ED893192906920099B726 /* include */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -1497,6 +1588,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DAFE45D715039823003ABA7C /* Pearl */,
|
||||
DA89D4E51A51E53100AC64D7 /* Pearl-Cocoa */,
|
||||
DAFE45FC15039823003ABA7C /* Pearl-Crypto */,
|
||||
DAC77CB1148291A600BCF976 /* Pearl-Prefix.pch */,
|
||||
);
|
||||
@@ -1782,6 +1874,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DA10007F1998A4C6002B873F /* scrypt in Headers */,
|
||||
DA89D4EC1A51EABD00AC64D7 /* Pearl-Cocoa.h in Headers */,
|
||||
DA1000801998A4C6002B873F /* openssl in Headers */,
|
||||
DA2CA4F218D323D3007798F8 /* NSTimer+PearlBlock.h in Headers */,
|
||||
DAFE4A1315039824003ABA7C /* NSObject+PearlExport.h in Headers */,
|
||||
@@ -1819,6 +1912,7 @@
|
||||
DA2CA4EE18D323D3007798F8 /* NSError+PearlFullDescription.h in Headers */,
|
||||
DAADCC4819FAFFAD00987B1D /* NSPersistentStore+PearlMigration.h in Headers */,
|
||||
93D39D304F73B3BBA031522A /* PearlProfiler.h in Headers */,
|
||||
93D391E61DC23E128DA4446C /* NSView+Traversing.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -1847,6 +1941,23 @@
|
||||
productReference = DA5BFA44147E415C00F98B1E /* Master Password.app */;
|
||||
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 */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = DAADCC5E19FB006500987B1D /* Build configuration list for PBXNativeTarget "KCOrderedAccessorFix" */;
|
||||
@@ -1919,6 +2030,9 @@
|
||||
};
|
||||
};
|
||||
};
|
||||
DA67743A1A474A03004F356A = {
|
||||
CreatedOnToolsVersion = 6.1.1;
|
||||
};
|
||||
DAADCC4F19FB006500987B1D = {
|
||||
CreatedOnToolsVersion = 6.0.1;
|
||||
};
|
||||
@@ -1947,6 +2061,7 @@
|
||||
DAC77CAC148291A600BCF976 /* Pearl */,
|
||||
DAC6326B148680650075AEA5 /* jrswizzle */,
|
||||
DAADCC4F19FB006500987B1D /* KCOrderedAccessorFix */,
|
||||
DA67743A1A474A03004F356A /* mpw-test */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@@ -1972,6 +2087,7 @@
|
||||
DACA27131705DF81002C6C22 /* avatar-3@2x.png in Resources */,
|
||||
DACA27141705DF81002C6C22 /* avatar-7.png in Resources */,
|
||||
DAF4EF57190A828100023C90 /* Exo2.0-Regular.otf in Resources */,
|
||||
DA6773CA1A4746AF004F356A /* bashlib in Resources */,
|
||||
DACA27151705DF81002C6C22 /* avatar-0.png in Resources */,
|
||||
DA6558A419A99609009A0BEB /* Images.xcassets in Resources */,
|
||||
DACA27161705DF81002C6C22 /* avatar-12.png in Resources */,
|
||||
@@ -1979,6 +2095,7 @@
|
||||
DAF4EF59190A828100023C90 /* Exo2.0-Bold.otf in Resources */,
|
||||
DACA27181705DF81002C6C22 /* avatar-9.png in Resources */,
|
||||
DAAA81B0195A8D1300FA30D9 /* gradient.png in Resources */,
|
||||
DA6774331A4746AF004F356A /* mpw.bashrc in Resources */,
|
||||
DACA27191705DF81002C6C22 /* avatar-1@2x.png in Resources */,
|
||||
DA2508F119511D3600AC23F1 /* MPPasswordWindowController.xib in Resources */,
|
||||
DA60717C195D040500CA98B5 /* icon_gear.png in Resources */,
|
||||
@@ -2001,6 +2118,7 @@
|
||||
DACA27291705DF81002C6C22 /* avatar-17.png in Resources */,
|
||||
DACA272A1705DF81002C6C22 /* avatar-5.png in Resources */,
|
||||
DACA272B1705DF81002C6C22 /* avatar-2.png in Resources */,
|
||||
DA6773CD1A4746AF004F356A /* install in Resources */,
|
||||
DACA272C1705DF81002C6C22 /* avatar-14@2x.png in Resources */,
|
||||
DACA272D1705DF81002C6C22 /* avatar-4@2x.png in Resources */,
|
||||
DACA272E1705DF81002C6C22 /* avatar-9@2x.png in Resources */,
|
||||
@@ -2017,7 +2135,9 @@
|
||||
DACA27351705DF81002C6C22 /* avatar-11.png in Resources */,
|
||||
DACA27361705DF81002C6C22 /* avatar-0@2x.png in Resources */,
|
||||
DACA27371705DF81002C6C22 /* avatar-10@2x.png in Resources */,
|
||||
DA6773CC1A4746AF004F356A /* distribute in Resources */,
|
||||
DACA27381705DF81002C6C22 /* menu-icon.png in Resources */,
|
||||
DA6773CB1A4746AF004F356A /* build in Resources */,
|
||||
DACA29671705DF81002C6C22 /* SourceCodePro-ExtraLight.otf in Resources */,
|
||||
DACA29681705DF81002C6C22 /* SourceCodePro-Black.otf in Resources */,
|
||||
DACA296F1705DF81002C6C22 /* Crashlytics.plist in Resources */,
|
||||
@@ -2089,7 +2209,7 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = "/bin/bash -e";
|
||||
shellScript = "../../../External/Mac/Crashlytics.framework/run \\\n \"$(/usr/libexec/PlistBuddy -c \"Print :'API Key'\" ../../Resources/Crashlytics/Crashlytics.plist)\"";
|
||||
shellScript = "../../../External/Mac/Crashlytics.framework/run \\\n \"$(/usr/libexec/PlistBuddy -c \"Print :'API Key'\" ../../Resources/Crashlytics/Crashlytics.plist)\" 410fb41450e3a2e50fa8357682d812ecd3e1846f2141a99bdb9d3a6a981ad69c";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
@@ -2104,6 +2224,7 @@
|
||||
DA32CFE519CF1C71004F3F0E /* MPUserEntity.m in Sources */,
|
||||
DA5E5CF71724A667003798D8 /* MPAlgorithmV0.m in Sources */,
|
||||
DA5E5CF81724A667003798D8 /* MPAlgorithmV1.m in Sources */,
|
||||
DA6774311A4746AF004F356A /* mpw-util.c in Sources */,
|
||||
DA5E5CF91724A667003798D8 /* MPAppDelegate_Key.m in Sources */,
|
||||
DA5180CE19FF307E00A587E9 /* MPAppDelegate_Store.m in Sources */,
|
||||
DA5E5CFA1724A667003798D8 /* MPAppDelegate_Shared.m in Sources */,
|
||||
@@ -2119,15 +2240,29 @@
|
||||
93D39C5789EFA607CF788082 /* MPSiteModel.m in Sources */,
|
||||
DA5180CA19FF2F9200A587E9 /* MPAlgorithmV2.m in Sources */,
|
||||
93D39F833DEC1C89B2F795AC /* MPPasswordWindowController.m in Sources */,
|
||||
DA67742F1A4746AF004F356A /* mpw-types.c in Sources */,
|
||||
DA32CFD919CF1C70004F3F0E /* MPGeneratedSiteEntity.m in Sources */,
|
||||
93D390C676DF52DA7E459F19 /* MPPasswordWindow.m in Sources */,
|
||||
93D39784E725A34D1EE3FB3B /* MPInitialWindowController.m in Sources */,
|
||||
DA32CFDF19CF1C70004F3F0E /* MPSiteEntity.m in Sources */,
|
||||
93D394C4254EEB45FB335AFB /* MPSitesTableView.m in Sources */,
|
||||
DA6774291A4746AF004F356A /* mpw-algorithm.c in Sources */,
|
||||
93D395E4830290EBB6E71F34 /* MPNoStateButton.m in Sources */,
|
||||
);
|
||||
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 */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -2164,6 +2299,7 @@
|
||||
DAFE4A2E15039824003ABA7C /* PearlStrings.m in Sources */,
|
||||
DAFE4A3015039824003ABA7C /* PearlStringUtils.m in Sources */,
|
||||
DAFE4A3515039824003ABA7C /* PearlCryptUtils.m in Sources */,
|
||||
DA89D4ED1A51EABD00AC64D7 /* Pearl-Cocoa.m in Sources */,
|
||||
DAFE4A3715039824003ABA7C /* PearlKeyChain.m in Sources */,
|
||||
DAFE4A3915039824003ABA7C /* PearlRSAKey.m in Sources */,
|
||||
DAFE4A3B15039824003ABA7C /* PearlSCrypt.m in Sources */,
|
||||
@@ -2181,6 +2317,7 @@
|
||||
93D39E281E3658B30550CB55 /* NSDictionary+Indexing.m in Sources */,
|
||||
DA3509FF15F101A500C14A8E /* PearlQueue.m in Sources */,
|
||||
93D3970BCF85F7902E611168 /* PearlProfiler.m in Sources */,
|
||||
93D3987F6D9046DBEE4D8364 /* NSView+Traversing.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -2234,15 +2371,21 @@
|
||||
CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES;
|
||||
CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES;
|
||||
CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_CXX0X_EXTENSIONS = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES;
|
||||
CLANG_WARN_OBJC_RECEIVER_WEAK = NO;
|
||||
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = NO;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES;
|
||||
@@ -2270,7 +2413,7 @@
|
||||
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
|
||||
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
|
||||
GCC_WARN_ABOUT_POINTER_SIGNEDNESS = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_ALLOW_INCOMPLETE_PROTOCOL = YES;
|
||||
GCC_WARN_CHECK_SWITCH_STATEMENTS = YES;
|
||||
GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES;
|
||||
@@ -2287,6 +2430,10 @@
|
||||
GCC_WARN_UNUSED_LABEL = YES;
|
||||
GCC_WARN_UNUSED_VALUE = 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_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks";
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
@@ -2294,6 +2441,7 @@
|
||||
"\"$(SRCROOT)/../../../External\"/**",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.8;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "${TARGET_NAME}";
|
||||
@@ -2309,15 +2457,21 @@
|
||||
CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES;
|
||||
CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES;
|
||||
CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_CXX0X_EXTENSIONS = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES;
|
||||
CLANG_WARN_OBJC_RECEIVER_WEAK = NO;
|
||||
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = NO;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES;
|
||||
@@ -2325,6 +2479,7 @@
|
||||
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
@@ -2333,6 +2488,7 @@
|
||||
"$(inherited)",
|
||||
"NDEBUG=1",
|
||||
"NS_BLOCK_ASSERTIONS=1",
|
||||
"CRASHLYTICS=1",
|
||||
);
|
||||
GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
|
||||
GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
|
||||
@@ -2345,7 +2501,7 @@
|
||||
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
|
||||
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
|
||||
GCC_WARN_ABOUT_POINTER_SIGNEDNESS = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_ALLOW_INCOMPLETE_PROTOCOL = YES;
|
||||
GCC_WARN_CHECK_SWITCH_STATEMENTS = YES;
|
||||
GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES;
|
||||
@@ -2356,12 +2512,16 @@
|
||||
GCC_WARN_SIGN_COMPARE = YES;
|
||||
GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = YES;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNKNOWN_PRAGMAS = YES;
|
||||
GCC_WARN_UNUSED_FUNCTION = NO;
|
||||
GCC_WARN_UNUSED_LABEL = YES;
|
||||
GCC_WARN_UNUSED_VALUE = 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_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks";
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
@@ -2369,6 +2529,7 @@
|
||||
"\"$(SRCROOT)/../../../External\"/**",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.8;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "${TARGET_NAME}";
|
||||
RUN_CLANG_STATIC_ANALYZER = YES;
|
||||
@@ -2426,6 +2587,42 @@
|
||||
};
|
||||
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 */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
@@ -2433,15 +2630,21 @@
|
||||
CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES;
|
||||
CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES;
|
||||
CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_CXX0X_EXTENSIONS = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES;
|
||||
CLANG_WARN_OBJC_RECEIVER_WEAK = NO;
|
||||
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = NO;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES;
|
||||
@@ -2449,6 +2652,7 @@
|
||||
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
@@ -2457,6 +2661,7 @@
|
||||
"$(inherited)",
|
||||
"NDEBUG=1",
|
||||
"NS_BLOCK_ASSERTIONS=1",
|
||||
"CRASHLYTICS=1",
|
||||
);
|
||||
GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
|
||||
GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
|
||||
@@ -2469,7 +2674,7 @@
|
||||
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
|
||||
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
|
||||
GCC_WARN_ABOUT_POINTER_SIGNEDNESS = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_ALLOW_INCOMPLETE_PROTOCOL = YES;
|
||||
GCC_WARN_CHECK_SWITCH_STATEMENTS = YES;
|
||||
GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES;
|
||||
@@ -2480,12 +2685,16 @@
|
||||
GCC_WARN_SIGN_COMPARE = YES;
|
||||
GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = YES;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNKNOWN_PRAGMAS = YES;
|
||||
GCC_WARN_UNUSED_FUNCTION = NO;
|
||||
GCC_WARN_UNUSED_LABEL = YES;
|
||||
GCC_WARN_UNUSED_VALUE = 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_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks";
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
@@ -2493,6 +2702,7 @@
|
||||
"\"$(SRCROOT)/../../../External\"/**",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.8;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "${TARGET_NAME}";
|
||||
RUN_CLANG_STATIC_ANALYZER = YES;
|
||||
@@ -2672,6 +2882,16 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
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" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
|
||||
@@ -29,6 +29,15 @@
|
||||
buildConfiguration = "AppStore-Mac">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
|
||||
BuildableName = "Master Password.app"
|
||||
BlueprintName = "MasterPassword"
|
||||
ReferencedContainer = "container:MasterPassword-Mac.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user