2
0

Compare commits

...

104 Commits

Author SHA1 Message Date
Maarten Billemont
b779ff5d1c Added C tests for mpw_tests.xml 2014-12-21 23:45:19 -05:00
Maarten Billemont
73c10906e3 Some better memory maintenance. 2014-12-21 12:37:21 -05:00
Maarten Billemont
0ccd545dd4 More restructuring and rewriting of the C code. 2014-12-20 14:30:34 -05:00
Maarten Billemont
49da0b47c7 Complete an initial rewrite/restructure of mpw.c 2014-12-20 00:21:03 -05:00
Maarten Billemont
672b28a5b7 Restructure, split up mpw cli from mpw core. 2014-12-19 23:15:32 -05:00
Maarten Billemont
2dbada3c7c Update build script to install header files in a shared include path, akin to how ObjC does. 2014-12-19 09:00:38 -05:00
Maarten Billemont
3dbc105fbd Update C code for inclusion in ObjC and update scrypt to colin's latest code. 2014-12-19 00:03:54 -05:00
Maarten Billemont
43d55211b0 Revert "Revert accidentally committed changes."
This reverts commit a62ae8c757.
2014-12-18 17:47:38 -05:00
Maarten Billemont
f170e9df69 Update Java GUI. 2014-12-17 00:44:48 -05:00
Maarten Billemont
1fbb6b0754 Clear the password input field and pop a warning when entering bad master password. 2014-12-17 00:31:34 -05:00
Maarten Billemont
4c526d6f08 Fixed a bug causing exceptions loading maximum security passwords. 2014-12-17 00:18:02 -05:00
Maarten Billemont
a62ae8c757 Revert accidentally committed changes. 2014-12-16 23:07:31 -05:00
Maarten Billemont
f2eb53569b Use ~/.mpwrc since ~/.mpw is used by other tools.. 2014-12-16 22:14:52 -05:00
Maarten Billemont
c2a6a3d035 Full ability to load, add and autocomplete sites from history. 2014-12-16 22:13:11 -05:00
Maarten Billemont
97dcc65eac Update install scripts for new env var names. 2014-12-16 08:54:58 -05:00
Maarten Billemont
1bd76dbb61 Read avatar from user export and allow user to modify it by clicking the picture. 2014-12-12 17:25:32 -05:00
Maarten Billemont
0fdf894bf0 Fix issue with passing context in Java algorithm and test case. 2014-12-12 10:54:28 -05:00
Maarten Billemont
19202e07d4 Merge branch 'master' of github.com:Lyndir/MasterPassword 2014-12-12 10:14:09 -05:00
Maarten Billemont
84b624aea2 WIP - integrate user and site storage through export files into Java Swing GUI. 2014-12-11 20:35:19 -05:00
Maarten Billemont
c7ac5087b3 Fix test cases loginName, securityAnswer and securityAnswer_context 2014-12-10 10:34:55 -05:00
Maarten Billemont
4ff8cd6d90 A persistence model for the Java implementation and ability to parse in and write out export files. 2014-12-08 11:11:29 -05:00
Maarten Billemont
3f4558da2b Standardize on a naming scheme: cipher -> template, userName -> fullName, element -> site. 2014-12-05 17:59:10 -05:00
Maarten Billemont
b976e79b0f Fix some mpw.c link errors.
[FIXED]     Libraries should be specified after the objects that need them for some compilers apparently..
[FIXED]     bool type requires stdbool.h, disable more color code when COLOR not defined.
[UPDATED]   Disable mpw_color by default as it doesn't link cleanly on some Linux'es for now..
2014-12-05 13:51:02 -05:00
Maarten Billemont
3d064fa68d A full test script for various inputs and a Java TestNG implementation that tests it. 2014-12-05 02:17:28 -05:00
Maarten Billemont
1a1e024178 Fix Java unit test expected values from C implementation's output. 2014-12-04 00:35:21 -05:00
Maarten Billemont
4876d62b56 Copy the string into masterPassword because the line is getting free'd. 2014-12-04 00:28:38 -05:00
Maarten Billemont
8006b7096f Make Java and C debug output comparable. 2014-12-03 23:36:18 -05:00
Maarten Billemont
a82ce7310d Remove plist dependency, fix length bug, import ciphers.plist.
[REMOVED]   Java code no longer depends on ciphers.plist and net.sf.plist.
[ADDED]     Java code now explicitly defines the algorithm's templates.
[FIXED]     Java code now properly counts the site name and user name's byte length.
[FIXED]     Java code now explicitly uses 32-bit integers.
2014-12-03 00:46:00 -05:00
Maarten Billemont
ae08cb62c5 Add the env variables to the usage output. 2014-12-03 00:40:54 -05:00
Maarten Billemont
c48fba6c01 No color fixes & malloc bug in .mpw reading.
[FIXED]     We weren't properly excluding all dependencies on ncurses when colors are not enabled.
[FIXED]     There was a memory realloc bug when reading multiple lines from ~/.mpw.
2014-12-02 20:46:53 -05:00
Maarten Billemont
3db25e7e3b Some more attempts at being better at memory handling + remove useless and untested cygwin stuff. 2014-11-23 14:01:29 -05:00
Maarten Billemont
1f7a49378b Allocate enough space for the NUL c-string delimitor. 2014-11-23 13:44:37 -05:00
Maarten Billemont
37ec21f5be Disable colors when output is not a terminal. 2014-11-23 13:33:09 -05:00
Maarten Billemont
2b8498f569 Support for patching dependencies + ARM patch for bcrypt. 2014-11-21 09:39:30 -05:00
Maarten Billemont
5c4fc61a12 Bump bashlib. 2014-11-21 08:15:51 -05:00
Maarten Billemont
c0ec65bbae Make identicon color an optional feature and specify the dependency. 2014-11-21 08:06:29 -05:00
Maarten Billemont
9ab1b2d47a Merge branch 'master' of github.com:Lyndir/MasterPassword 2014-11-21 01:31:07 -05:00
Maarten Billemont
17d38235a1 Initial test of identicon support. 2014-11-21 01:30:47 -05:00
Maarten Billemont
1fe3edec04 oops - syntax error in build script. 2014-11-18 15:33:19 -05:00
Maarten Billemont
8dd932da31 Comment on what to do when libcrypto.a is in a non-standard path for now. 2014-11-18 09:56:53 -05:00
Maarten Billemont
71ba6bd502 Travis still noisy? 2014-11-18 08:19:13 -05:00
Maarten Billemont
33eab0f199 Work around the weirdest Xcode build failure: don't output :: STUFF :: from a build script or Xcode will fail it. 2014-11-18 08:13:39 -05:00
Maarten Billemont
7d6f2533db work around pinf hang under Travis by forcing the TERM to dumb. 2014-11-18 07:32:17 -05:00
Maarten Billemont
c6cd50379e exit before pinf 2014-11-18 07:23:26 -05:00
Maarten Billemont
fb86b3bc15 exit before pinf and cp 2014-11-18 07:21:56 -05:00
Maarten Billemont
964b6f050b exit before java -jar. 2014-11-18 07:17:28 -05:00
Maarten Billemont
617923557a exit after niftyninepatch. 2014-11-18 00:11:41 -05:00
Maarten Billemont
719deb7926 exit in front of niftyninepatch 2014-11-18 00:11:27 -05:00
Maarten Billemont
a51d2b5eb4 move exit down... 2014-11-18 00:10:00 -05:00
Maarten Billemont
1f1407f31f move exit down... 2014-11-18 00:04:12 -05:00
Maarten Billemont
afb0a0365a move exit down a bit.. 2014-11-17 23:59:11 -05:00
Maarten Billemont
834434a499 exit genassets before anything useful happens - this type of debugging is really lame. You suck, Travis. 2014-11-17 23:16:55 -05:00
Maarten Billemont
00033eca37 Merge branch 'master' of github.com:Lyndir/MasterPassword 2014-11-17 20:15:50 -05:00
Maarten Billemont
574f239545 cut genassets in half to debug Travis. 2014-11-17 17:42:32 -05:00
Maarten Billemont
99f5dfe040 Try without exec, now will you run it, Travis? 2014-11-17 17:35:39 -05:00
Maarten Billemont
c0e6e60a38 Revert "More debug attempts for Travis CI."
This reverts commit b656899532.
2014-11-17 17:35:08 -05:00
Maarten Billemont
b656899532 More debug attempts for Travis CI. 2014-11-17 17:28:06 -05:00
Maarten Billemont
dbd5265ee0 Try to debug Travis CI hang on genassets. 2014-11-17 17:12:40 -05:00
Maarten Billemont
09d67113a2 Fix deadlock when PSC's store becomes available. 2014-11-10 16:45:25 -05:00
Maarten Billemont
f0fa350e64 Fix mixed content and bad path for setImmediate-polyfill. 2014-11-10 16:44:37 -05:00
Maarten Billemont
435d72a509 Read the user's name and site name from stdin if not given. 2014-11-10 12:35:31 -05:00
Maarten Billemont
395f7c71a2 Switch more references to HTTPS. 2014-11-06 07:48:02 -05:00
Maarten Billemont
96b482d94b KCOrderedAccessorFix was forked. 2014-11-03 16:04:41 -05:00
Maarten Billemont
c9c9aec9dd Sync mpw-js to js.masterpasswordapp.com. 2014-11-03 15:56:46 -05:00
Maarten Billemont
7bb79c6a1e Use HTTPS for all download links on home page. 2014-11-03 12:12:05 -05:00
Maarten Billemont
4b2251d4fa Support for fuzzy searching on iOS. 2014-11-03 12:11:46 -05:00
Maarten Billemont
1c72643aaa Move mpw-js out of the homepage. 2014-11-02 10:27:02 -05:00
Maarten Billemont
acf9b4aec8 Fix Mac darkening & icon rounding. 2014-11-01 16:37:09 -04:00
Maarten Billemont
1f173d7586 Support for dark mode icon. 2014-10-30 21:05:13 -04:00
Maarten Billemont
34be1eac70 Mac LoginHelper distribution configuration. 2014-10-30 00:39:56 -04:00
Maarten Billemont
e1a9c8194c Bump xib. 2014-10-30 00:26:42 -04:00
Maarten Billemont
e18dee2242 Also try upgrading to V2 if migrationlevel is unset but a store is available. 2014-10-30 00:18:01 -04:00
Maarten Billemont
a38829c1e2 Match the define name to its value. 2014-10-29 21:55:18 -04:00
Maarten Billemont
8615f6af5d Fuzzy site name search and highlight fuzzy results. 2014-10-29 21:24:35 -04:00
Maarten Billemont
d642cb1aee Use exposure adjust for darkening without destroying dark backgrounds. 2014-10-28 17:20:38 -04:00
Maarten Billemont
2be2a19fa0 Build fixes and layout improvements for Mac on Yosemite. 2014-10-28 00:53:16 -04:00
Maarten Billemont
0d5b51ed8d Put Mac icon into AppIconSet 2014-10-26 13:26:41 -04:00
Maarten Billemont
c781bcf10a Move Info.plist update script into Scripts to share it between Mac and iOS. 2014-10-26 12:01:30 -04:00
Maarten Billemont
1c45a0df4a Don't crash if attempting to import with an invalid password type. 2014-10-26 10:54:28 -04:00
Maarten Billemont
6b16e4d606 Remove USM from Mac app. 2014-10-26 10:41:15 -04:00
Maarten Billemont
e837752777 Explicitly synchronize configuration updates and warn if it fails. 2014-10-24 00:35:05 -04:00
Maarten Billemont
84c23fa7f6 debug genassets. 2014-10-24 00:00:51 -04:00
Maarten Billemont
2b0cc8ec7b Reference mpw-js from the main page + interlace the images. 2014-10-23 22:17:17 -04:00
Maarten Billemont
6f77e1922b Remove adwords. 2014-10-23 21:57:03 -04:00
Maarten Billemont
653f90c59c Force a refresh of the referenced stylesheet and js. 2014-10-23 21:35:17 -04:00
Maarten Billemont
3076cc3de4 A few disclaimers on mpw-js. 2014-10-23 21:33:19 -04:00
Maarten Billemont
15c53c06c6 Merge branch 'master' of github.com:Lyndir/MasterPassword 2014-10-23 20:10:44 -04:00
Maarten Billemont
b3a886a6db mpw-js: A javascript implementation of Master Password. 2014-10-23 20:10:25 -04:00
Maarten Billemont
41ae6a5de5 Remove pushqueue/popqueue, not compatible with bash3 2014-10-23 17:29:14 -04:00
Maarten Billemont
92bd2cd016 Explicitly use sha256 digests. 2014-10-23 12:03:19 -04:00
Maarten Billemont
4b975b5b04 Fix pkg_sha of crypt_blowfish; verified by checking the signature of the package against crypt_blowfish-1.3.tar.gz.sign 2014-10-23 00:26:07 -04:00
Maarten Billemont
56f04a8924 xctool gets project targets confused; specify iOS project explicitly. 2014-10-23 00:14:45 -04:00
Maarten Billemont
3c2b5de4b8 Fix crash when clearing the pasteboard. 2014-10-22 23:32:25 -04:00
Maarten Billemont
33e1492b44 clean up todo. 2014-10-22 23:21:21 -04:00
Maarten Billemont
9384e27247 submodules needs to be explicitly true? + fix version numbering. 2014-10-22 23:10:07 -04:00
Maarten Billemont
a95561dd50 Re-enable submodule support in Travis; Pearl is no longer excessive. 2014-10-22 23:05:04 -04:00
Maarten Billemont
9d809f34d9 Remove MPCheckpoints, no longer useful. Perform pasteboard importing on a background thread. 2014-10-22 23:00:20 -04:00
Maarten Billemont
fc21bd959f Add a tip on how to solve a build error on the Raspberry Pi. 2014-10-22 22:29:05 -04:00
Maarten Billemont
2de17384ff More portable digest() 2014-10-22 22:02:17 -04:00
Maarten Billemont
85dab50996 More robust against exceptions and a few other fixes. 2014-10-22 21:54:48 -04:00
Maarten Billemont
bb97e8f3e8 Fix Core Data store migration code. 2014-10-22 20:26:22 -04:00
Maarten Billemont
f3d0368a75 Don't sync .git repos. 2014-10-22 16:03:40 -04:00
Maarten Billemont
283d555d3b Missing site resources. 2014-10-20 19:59:02 -04:00
Maarten Billemont
d909e64670 Add disclaimer. 2014-10-20 14:31:21 -04:00
233 changed files with 17990 additions and 2714 deletions

1
.gitignore vendored
View File

@@ -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

8
.gitmodules vendored
View File

@@ -4,12 +4,9 @@
[submodule "External/InAppSettingsKit"]
path = External/InAppSettingsKit
url = git://github.com/lhunath/InAppSettingsKit.git
[submodule "External/RHStatusItemView"]
path = External/RHStatusItemView
url = git://github.com/lhunath/RHStatusItemView.git
[submodule "External/KCOrderedAccessorFix"]
path = External/KCOrderedAccessorFix
url = https://github.com/CFKevinRef/KCOrderedAccessorFix.git
url = https://github.com/lhunath/KCOrderedAccessorFix.git
[submodule "External/AttributedMarkdown"]
path = External/AttributedMarkdown
url = https://github.com/dreamwieber/AttributedMarkdown.git
@@ -19,3 +16,6 @@
[submodule "External/jrswizzle"]
path = External/jrswizzle
url = git://github.com/jonmarimba/jrswizzle.git
[submodule "Site/mpw-js/js/mpw-js"]
path = Site/mpw-js/js/mpw-js
url = https://github.com/Lyndir/mpw-js.git

View File

@@ -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" />

View File

@@ -1,7 +1,7 @@
language: objective-c
xcode_workspace: MasterPassword.xcworkspace
xcode_project: MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj
xcode_scheme: MasterPassword iOS (Development)
xcode_sdk: iphonesimulator
env: TERM=dumb
git:
submodules: false
before_install: ./Scripts/updateDependencies
submodules: true

View File

@@ -0,0 +1 @@
Versions/Current/Modules

View File

@@ -38,6 +38,7 @@
*
**/
OBJC_EXTERN void CLSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2);
OBJC_EXTERN void CLSLogv(NSString *format, va_list args) NS_FORMAT_FUNCTION(1,0);
/**
*
@@ -46,6 +47,8 @@ OBJC_EXTERN void CLSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2);
*
**/
OBJC_EXTERN void CLSNSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2);
OBJC_EXTERN void CLSNSLogv(NSString *format, va_list args) NS_FORMAT_FUNCTION(1,0);
@protocol CrashlyticsDelegate;

View File

@@ -0,0 +1,6 @@
framework module Crashlytics {
umbrella header "Crashlytics.h"
export *
module * { export * }
}

View File

@@ -15,13 +15,13 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>2.1.2</string>
<string>2.2.5</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>macosx</string>
</array>
<key>CFBundleVersion</key>
<string>9</string>
<string>39</string>
<key>DTPlatformName</key>
<string>macosx</string>
<key>MinimumOSVersion</key>

Binary file not shown.

2
External/Pearl vendored

View File

@@ -38,6 +38,7 @@
*
**/
OBJC_EXTERN void CLSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2);
OBJC_EXTERN void CLSLogv(NSString *format, va_list args) NS_FORMAT_FUNCTION(1,0);
/**
*
@@ -46,6 +47,8 @@ OBJC_EXTERN void CLSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2);
*
**/
OBJC_EXTERN void CLSNSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2);
OBJC_EXTERN void CLSNSLogv(NSString *format, va_list args) NS_FORMAT_FUNCTION(1,0);
@protocol CrashlyticsDelegate;

View File

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

Binary file not shown.

Binary file not shown.

View File

@@ -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 :.
: ______________________________________________________________________
:

View File

@@ -2,7 +2,15 @@
#
# TROUBLESHOOTING
# - To enable verbose algorithm/implementation debugging, use ./build -DDEBUG
# - If you see 'undefined reference to `clock_gettime'', try ./build -lrt instead
# - If you see 'undefined reference to `AES_encrypt'',
# make sure you have openssl installed.
# If libcrypto.a is in a non-standard directory, try ./build -L[your-lib-dir]
# - If you see 'undefined reference to `clock_gettime'',
# 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
@@ -25,13 +33,20 @@ 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
digest() {
openssl sha -sha256 -binary < "$1" | od -t x1 -An -v | tr -d '[:space:]'
}
fetch() {
if hash wget 2>/dev/null; then
wget -O "${1##*/}" "$1"
@@ -54,7 +69,7 @@ unpack() {
fi
printf 'Verifying package: %s, against digest: %s...' "$1" "$2"
[[ $(openssl sha < "$1") = $2 ]] || {
[[ $(digest "$1") = $2 ]] || {
printf ' mismatch!\n'
echo 2>&1 "Downloaded package doesn't match digest."
exit 1
@@ -75,7 +90,7 @@ fetchSource() (
[[ -e $files ]] || {
echo
echo "Unpacking: ${PWD##*/}, using package..."
unpack "${pkg##*/}" "$pkg_sha"
unpack "${pkg##*/}" "$pkg_sha256"
}
elif [[ $git ]] && hash git 2>/dev/null; then
@@ -108,7 +123,7 @@ fetchSource() (
echo
echo "Fetching: ${PWD##*/}, using package..."
fetch "$pkg"
unpack "${pkg##*/}" "$pkg_sha"
unpack "${pkg##*/}" "$pkg_sha256"
}
else
@@ -119,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
@@ -160,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
@@ -177,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"
# scrypt
"lib/scrypt/scrypt-crypto_aesctr.o"
"lib/scrypt/scrypt-sha256.o"
@@ -191,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"
}
@@ -208,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"
@@ -229,16 +250,56 @@ 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() {
! LC_ALL=C cc -l"$1" 2>&1 | grep -q 'library not found'
}
cc() {
if hash llvm-gcc 2>/dev/null; then
llvm-gcc "$@"

View File

@@ -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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
home=https://code.google.com/p/scrypt/
svn=http://scrypt.googlecode.com/svn
pkg=http://masterpasswordapp.com/libscrypt-b12b554.tar.gz
pkg_sha=a86445c3e031392d20652f4163adfd3fb0b1994e
pkg_sha256=c726daec68a345e420896f005394a948dc5a6924713ed94b684c856d4c247f0b

View File

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

View File

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

View File

@@ -1,20 +1,23 @@
#include <sys/time.h>
//
// mpw-bench.c
// MasterPassword
//
// Created by Maarten Billemont on 2014-12-20.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#include <stdio.h>
#include <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,82 @@
#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 );
if (!masterKey)
ftl( "Could not allocate master key: %d\n", errno );
free( (void *)mpw_passwordForSite( masterKey, siteName, siteType, siteCounter, siteVariant, siteContext ) );
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;
}

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

@@ -17,8 +17,8 @@ mpw() {
:| _copy 2>/dev/null
# 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
}

View File

@@ -1,298 +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;
}
int main(int argc, char *const argv[]) {
if (argc < 2)
usage();
// Read the environment.
const 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) {
fprintf(stderr, "Missing user name.\n");
return 1;
}
trc("userName: %s\n", userName);
if (!siteName) {
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);
// Calculate the master key salt.
const char *mpKeyScope = ScopeForVariant(MPElementVariantPassword);
trc("key scope: %s\n", mpKeyScope);
const uint32_t n_userNameLength = htonl(strlen(userName));
const size_t masterKeySaltLength = strlen(mpKeyScope) + sizeof(n_userNameLength) + strlen(userName);
char *masterKeySalt = (char *)malloc( masterKeySaltLength );
if (!masterKeySalt) {
fprintf(stderr, "Could not allocate master key salt: %d\n", errno);
return 1;
}
char *mKS = masterKeySalt;
memcpy(mKS, mpKeyScope, strlen(mpKeyScope)); mKS += strlen(mpKeyScope);
memcpy(mKS, &n_userNameLength, sizeof(n_userNameLength)); mKS += sizeof(n_userNameLength);
memcpy(mKS, userName, strlen(userName)); mKS += strlen(userName);
if (mKS - masterKeySalt != masterKeySaltLength)
abort();
trc("masterKeySalt ID: %s\n", IDForBuf(masterKeySalt, masterKeySaltLength));
// Calculate the master key.
uint8_t *masterKey = (uint8_t *)malloc( MP_dkLen );
if (!masterKey) {
fprintf(stderr, "Could not allocate master key: %d\n", errno);
return 1;
}
if (crypto_scrypt( (const uint8_t *)masterPassword, strlen(masterPassword), (const uint8_t *)masterKeySalt, masterKeySaltLength, MP_N, MP_r, MP_p, masterKey, MP_dkLen ) < 0) {
fprintf(stderr, "Could not generate master key: %d\n", errno);
return 1;
}
memset(masterKeySalt, 0, masterKeySaltLength);
free(masterKeySalt);
trc("masterPassword Hex: %s\n", Hex(masterPassword, strlen(masterPassword)));
trc("masterPassword ID: %s\n", IDForBuf(masterPassword, strlen(masterPassword)));
trc("masterKey ID: %s\n", IDForBuf(masterKey, MP_dkLen));
// Calculate the site seed.
const char *mpSiteScope = ScopeForVariant(siteVariant);
trc("site scope: %s, context: %s\n", mpSiteScope, siteContextString == NULL? "<empty>": siteContextString);
const uint32_t n_siteNameLength = htonl(strlen(siteName));
const uint32_t n_siteCounter = htonl(siteCounter);
const uint32_t n_siteContextLength = siteContextString == NULL? 0: htonl(strlen(siteContextString));
size_t sitePasswordInfoLength = strlen(mpSiteScope) + sizeof(n_siteNameLength) + strlen(siteName) + sizeof(n_siteCounter);
if (siteContextString)
sitePasswordInfoLength += sizeof(n_siteContextLength) + strlen(siteContextString);
char *sitePasswordInfo = (char *)malloc( sitePasswordInfoLength );
if (!sitePasswordInfo) {
fprintf(stderr, "Could not allocate site seed: %d\n", errno);
return 1;
}
char *sPI = sitePasswordInfo;
memcpy(sPI, mpSiteScope, strlen(mpSiteScope)); sPI += strlen(mpSiteScope);
memcpy(sPI, &n_siteNameLength, sizeof(n_siteNameLength)); sPI += sizeof(n_siteNameLength);
memcpy(sPI, siteName, strlen(siteName)); sPI += strlen(siteName);
memcpy(sPI, &n_siteCounter, sizeof(n_siteCounter)); sPI += sizeof(n_siteCounter);
if (siteContextString) {
memcpy(sPI, &n_siteContextLength, sizeof(n_siteContextLength)); sPI += sizeof(n_siteContextLength);
memcpy(sPI, siteContextString, strlen(siteContextString)); sPI += strlen(siteContextString);
}
if (sPI - sitePasswordInfo != sitePasswordInfoLength)
abort();
trc("seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)\n", mpSiteScope, Hex(&n_siteNameLength, sizeof(n_siteNameLength)), siteName, Hex(&n_siteCounter, sizeof(n_siteCounter)), Hex(&n_siteContextLength, sizeof(n_siteContextLength)), siteContextString);
trc("sitePasswordInfo ID: %s\n", IDForBuf(sitePasswordInfo, sitePasswordInfoLength));
uint8_t sitePasswordSeed[32];
HMAC_SHA256_Buf(masterKey, MP_dkLen, sitePasswordInfo, sitePasswordInfoLength, sitePasswordSeed);
memset(masterKey, 0, MP_dkLen);
memset(sitePasswordInfo, 0, sitePasswordInfoLength);
free(masterKey);
free(sitePasswordInfo);
trc("sitePasswordSeed ID: %s\n", IDForBuf(sitePasswordSeed, 32));
// Determine the cipher.
const char *cipher = CipherForType(siteType, sitePasswordSeed[0]);
trc("type %s, cipher: %s\n", siteTypeString, cipher);
if (strlen(cipher) > 32)
abort();
// Encode the password from the seed using the cipher.
char *sitePassword = (char *)calloc(strlen(cipher) + 1, sizeof(char));
for (int c = 0; c < strlen(cipher); ++c) {
sitePassword[c] = CharacterFromClass(cipher[c], sitePasswordSeed[c + 1]);
trc("class %c, character: %c\n", cipher[c], sitePassword[c]);
}
memset(sitePasswordSeed, 0, sizeof(sitePasswordSeed));
// Output the password.
fprintf( stdout, "%s\n", sitePassword );
return 0;
}

View File

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

View File

@@ -1,190 +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 <alg/sha256.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;
}

View File

@@ -1,59 +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);

View File

@@ -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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,9 @@
package com.lyndir.masterpassword;
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 );
}
}

View File

@@ -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 );
}
}

View File

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

View File

@@ -2,11 +2,8 @@ package com.lyndir.masterpassword;
import com.google.common.base.Charsets;
import com.google.common.base.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 );
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -86,9 +86,8 @@ public class EmergencyActivity extends Activity {
sitePasswordField.setTypeface( Res.sourceCodePro_Black );
sitePasswordField.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

View File

@@ -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 ) );
}
}

View File

@@ -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>

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
package com.lyndir.masterpassword;
package com.lyndir.masterpassword.gui;
import com.google.common.collect.ImmutableList;
import java.awt.*;
import 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();
}

View File

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

View File

@@ -15,19 +15,17 @@
*/
package com.lyndir.masterpassword;
package com.lyndir.masterpassword.gui;
import com.google.common.base.Charsets;
import com.google.common.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 com.lyndir.masterpassword.MasterKey;
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 +74,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,26 +92,28 @@ 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) {

View File

@@ -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 );
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,12 +1,17 @@
package com.lyndir.masterpassword;
package com.lyndir.masterpassword.gui;
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.*;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.masterpassword.*;
import com.lyndir.masterpassword.util.Components;
import 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.*;
@@ -17,12 +22,15 @@ import javax.swing.event.*;
*/
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;
private final User user;
private final JTextField siteNameField;
private final JButton siteAddButton;
private final JComboBox<MPSiteType> siteTypeField;
private final JSpinner siteCounterField;
private final JTextField passwordField;
private final JLabel tipLabel;
private boolean updatingUI;
private Site currentSite;
public PasswordFrame(User user)
throws HeadlessException {
@@ -38,7 +46,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
} );
// User
add( label = new JLabel( strf( "Generating passwords for: %s", user.getUserName() ) ), BorderLayout.NORTH );
add( label = new JLabel( strf( "Generating passwords for: %s", user.getFullName() ) ), BorderLayout.NORTH );
label.setFont( Res.exoRegular().deriveFont( 12f ) );
label.setAlignmentX( LEFT_ALIGNMENT );
@@ -53,21 +61,40 @@ public class PasswordFrame extends JFrame implements DocumentListener {
label.setFont( Res.exoRegular().deriveFont( 12f ) );
label.setAlignmentX( LEFT_ALIGNMENT );
sitePanel.add( siteNameField = new JTextField() {
JComponent siteControls = Components.boxLayout( BoxLayout.LINE_AXIS, //
siteNameField = new JTextField() {
@Override
public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
}
}, siteAddButton = new JButton( "Add Site" ) {
@Override
public Dimension getMaximumSize() {
return new Dimension( 20, getPreferredSize().height );
}
} );
siteAddButton.setVisible( false );
siteAddButton.setFont( Res.exoRegular().deriveFont( 12f ) );
siteAddButton.setAlignmentX( RIGHT_ALIGNMENT );
siteAddButton.setAlignmentY( CENTER_ALIGNMENT );
siteAddButton.addActionListener( new ActionListener() {
@Override
public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
public void actionPerformed(final ActionEvent e) {
PasswordFrame.this.user.addSite( currentSite );
siteAddButton.setVisible( false );
}
} );
siteNameField.setFont( Res.exoRegular().deriveFont( 12f ) );
siteControls.setAlignmentX( LEFT_ALIGNMENT );
sitePanel.add( siteControls );
siteNameField.setFont( Res.sourceCodeProRegular().deriveFont( 12f ) );
siteNameField.setAlignmentX( LEFT_ALIGNMENT );
siteNameField.getDocument().addDocumentListener( this );
siteNameField.addActionListener( new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
updatePassword( new PasswordCallback() {
Futures.addCallback( updatePassword(), new FutureCallback<String>() {
@Override
public void passwordGenerated(final String siteName, final String sitePassword) {
public void onSuccess(final String sitePassword) {
StringSelection clipboardContents = new StringSelection( sitePassword );
Toolkit.getDefaultToolkit().getSystemClipboard().setContents( clipboardContents, null );
@@ -84,12 +111,16 @@ public class PasswordFrame extends JFrame implements DocumentListener {
}
} );
}
@Override
public void onFailure(final Throwable t) {
}
} );
}
} );
// Site Type & Counter
MPElementType[] types = Iterables.toArray( MPElementType.forClass( MPElementTypeClass.Generated ), MPElementType.class );
MPSiteType[] types = Iterables.toArray( MPSiteType.forClass( MPSiteTypeClass.Generated ), MPSiteType.class );
JComponent siteSettings = Components.boxLayout( BoxLayout.LINE_AXIS, //
siteTypeField = new JComboBox<>( types ), //
siteCounterField = new JSpinner(
@@ -101,24 +132,24 @@ public class PasswordFrame extends JFrame implements DocumentListener {
} );
siteSettings.setAlignmentX( LEFT_ALIGNMENT );
sitePanel.add( siteSettings );
siteTypeField.setFont( Res.exoRegular().deriveFont( 12f ) );
siteTypeField.setFont( Res.sourceCodeProRegular().deriveFont( 12f ) );
siteTypeField.setAlignmentX( LEFT_ALIGNMENT );
siteTypeField.setAlignmentY( CENTER_ALIGNMENT );
siteTypeField.setSelectedItem( MPElementType.GeneratedLong );
siteTypeField.setSelectedItem( MPSiteType.GeneratedLong );
siteTypeField.addItemListener( new ItemListener() {
@Override
public void itemStateChanged(final ItemEvent e) {
updatePassword( null );
updatePassword();
}
} );
siteCounterField.setFont( Res.exoRegular().deriveFont( 12f ) );
siteCounterField.setFont( Res.sourceCodeProRegular().deriveFont( 12f ) );
siteCounterField.setAlignmentX( RIGHT_ALIGNMENT );
siteCounterField.setAlignmentY( CENTER_ALIGNMENT );
siteCounterField.addChangeListener( new ChangeListener() {
@Override
public void stateChanged(final ChangeEvent e) {
updatePassword( null );
updatePassword();
}
} );
@@ -131,7 +162,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
// Tip
tipLabel = new JLabel( " ", JLabel.CENTER );
tipLabel.setFont( Res.exoThin().deriveFont( 9f ) );
tipLabel.setFont( Res.exoRegular().deriveFont( 9f ) );
tipLabel.setAlignmentX( Component.CENTER_ALIGNMENT );
add( Components.boxLayout( BoxLayout.PAGE_AXIS, passwordField, tipLabel ), BorderLayout.SOUTH );
@@ -145,55 +176,76 @@ public class PasswordFrame extends JFrame implements DocumentListener {
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();
@Nonnull
private ListenableFuture<String> updatePassword() {
if (siteType.getTypeClass() != MPElementTypeClass.Generated || siteName == null || siteName.isEmpty() || !user.hasKey()) {
final String siteNameQuery = siteNameField.getText();
if (updatingUI)
return Futures.immediateCancelledFuture();
if (siteNameQuery == null || siteNameQuery.isEmpty() || !user.hasKey()) {
passwordField.setText( null );
tipLabel.setText( null );
return;
return Futures.immediateCancelledFuture();
}
Res.execute( new Runnable() {
@Override
public void run() {
final String sitePassword = user.getKey().encode( siteName, siteType, siteCounter );
if (callback != null)
callback.passwordGenerated( siteName, sitePassword );
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( 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() {
if (!siteName.equals( siteNameField.getText() ))
return;
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." );
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( null );
updatePassword();
}
@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);
updatePassword();
}
}

View File

@@ -1,17 +1,17 @@
package com.lyndir.masterpassword;
package com.lyndir.masterpassword.gui;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse;
import static com.lyndir.lhunath.opal.system.util.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.image.ImageObserver;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.*;
@@ -25,14 +25,15 @@ public abstract class Res {
private static final ExecutorService executor = Executors.newSingleThreadExecutor();
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 Runnable job) {
return executor.submit( new Runnable() {
@Override
public void run() {
try {
@@ -45,20 +46,52 @@ public abstract class Res {
} );
}
public static <V> ListenableFuture<V> execute(final Callable<V> job) {
return JdkFutureAdapters.listenInPoolThread( executor.submit( new Callable<V>() {
@Override
public V call()
throws Exception {
try {
return job.call();
}
catch (Throwable t) {
logger.err( t, "Unexpected: %s", t.getLocalizedMessage() );
throw t;
}
}
} ), executor );
}
public static Icon iconAdd() {
return new RetinaIcon( Resources.getResource( "media/icon_add@2x.png" ) );
}
public static Icon iconQuestion() {
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 +100,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 +110,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 +120,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 +130,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 +140,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;

View File

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

View File

@@ -1,4 +1,4 @@
package com.lyndir.masterpassword;
package com.lyndir.masterpassword.gui;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
@@ -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,20 +118,18 @@ 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..." );
@@ -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();
}
} );

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,7 +7,7 @@
<parent>
<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>

View File

@@ -169,6 +169,8 @@ PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObserve
for (id<MPInAppDelegate> productObserver in self.productObservers)
[productObserver updateWithTransaction:transaction];
}
if (![[NSUserDefaults standardUserDefaults] synchronize])
wrn( @"Couldn't synchronize after transaction updates." );
}
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error {

View File

@@ -122,10 +122,8 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
// No more methods left, fail if key still not known.
if (!tryKey) {
if (password) {
if (password)
inf( @"Login failed for: %@", user.userID );
MPCheckpoint( MPCheckpointSignInFailed, nil );
}
return NO;
}
@@ -159,7 +157,6 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
}];
[[NSNotificationCenter defaultCenter] postNotificationName:MPSignedInNotification object:self];
MPCheckpoint( MPCheckpointSignedIn, nil );
return YES;
}

View File

@@ -16,6 +16,7 @@
@property(strong, nonatomic, readonly) MPKey *key;
@property(strong, nonatomic, readonly) NSManagedObjectID *activeUserOID;
@property(strong, nonatomic, readonly) NSPersistentStoreCoordinator *storeCoordinator;
+ (instancetype)get;

View File

@@ -6,15 +6,16 @@
// Copyright (c) 2011 Lyndir. All rights reserved.
//
#import <StoreKit/StoreKit.h>
#import "MPAppDelegate_Shared.h"
#import "MPAppDelegate_Store.h"
#import "MPAppDelegate_Key.h"
#import "NSManagedObjectModel+KCOrderedAccessorFix.h"
@interface MPAppDelegate_Shared ()
@property(strong, nonatomic) MPKey *key;
@property(strong, nonatomic) NSManagedObjectID *activeUserOID;
@property(strong, nonatomic) NSPersistentStoreCoordinator *storeCoordinator;
@end
@@ -31,6 +32,18 @@
#endif
}
- (instancetype)init {
if (!(self = [super init]))
return nil;
NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil];
[model kc_generateOrderedSetAccessors];
self.storeCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
return self;
}
- (MPUserEntity *)activeUserForMainThread {
return [self activeUserInContext:[MPAppDelegate_Shared managedObjectContextForMainThreadIfReady]];

View File

@@ -7,8 +7,6 @@
//
#import "MPAppDelegate_Store.h"
#import "MPGeneratedSiteEntity.h"
#import "NSManagedObjectModel+KCOrderedAccessorFix.h"
#if TARGET_OS_IPHONE
#define STORE_OPTIONS NSPersistentStoreFileProtectionKey : NSFileProtectionComplete,
@@ -16,7 +14,7 @@
#define STORE_OPTIONS
#endif
#define MPStoreMigrationLevelKey @"MPMigrationLevelLocalStoreKey"
#define MPMigrationLevelLocalStoreKey @"MPMigrationLevelLocalStoreKey"
typedef NS_ENUM( NSInteger, MPStoreMigrationLevel ) {
MPStoreMigrationLevelV1,
@@ -27,9 +25,7 @@ typedef NS_ENUM( NSInteger, MPStoreMigrationLevel ) {
@implementation MPAppDelegate_Shared(Store)
PearlAssociatedObjectProperty( id, SaveObserver, saveObserver );
PearlAssociatedObjectProperty( NSPersistentStoreCoordinator*, PersistentStoreCoordinator, persistentStoreCoordinator );
PearlAssociatedObjectProperty( NSOperationQueue *, StoreQueue, storeQueue );
PearlAssociatedObjectProperty( NSManagedObjectContext*, PrivateManagedObjectContext, privateManagedObjectContext );
@@ -56,7 +52,12 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
return NO;
[mainManagedObjectContext performBlock:^{
mocBlock( mainManagedObjectContext );
@try {
mocBlock( mainManagedObjectContext );
}
@catch (NSException *exception) {
err( @"While performing managed block:\n%@", [exception fullDescription] );
}
}];
return YES;
@@ -69,7 +70,12 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
return NO;
[mainManagedObjectContext performBlockAndWait:^{
mocBlock( mainManagedObjectContext );
@try {
mocBlock( mainManagedObjectContext );
}
@catch (NSException *exception) {
err( @"While performing managed block:\n%@", [exception fullDescription] );
}
}];
return YES;
@@ -84,7 +90,12 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
moc.parentContext = privateManagedObjectContextIfReady;
[moc performBlock:^{
mocBlock( moc );
@try {
mocBlock( moc );
}
@catch (NSException *exception) {
err( @"While performing managed block:\n%@", [exception fullDescription] );
}
}];
return YES;
@@ -99,7 +110,12 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
moc.parentContext = privateManagedObjectContextIfReady;
[moc performBlockAndWait:^{
mocBlock( moc );
@try {
mocBlock( moc );
}
@catch (NSException *exception) {
err( @"While performing managed block:\n%@", [exception fullDescription] );
}
}];
return YES;
@@ -123,20 +139,28 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
inDomains:NSUserDomainMask] lastObject];
return [[[applicationSupportURL
URLByAppendingPathComponent:[NSBundle mainBundle].bundleIdentifier isDirectory:YES]
URLByAppendingPathComponent:@"UbiquityStore" isDirectory:NO]
URLByAppendingPathComponent:@"MasterPassword" isDirectory:NO]
URLByAppendingPathExtension:@"sqlite"];
}
- (void)loadStore {
@synchronized (self) {
static dispatch_once_t once = 0;
dispatch_once( &once, ^{
(self.storeQueue = [NSOperationQueue new]).maxConcurrentOperationCount = 1;
} );
// Do nothing if already fully set up, otherwise (re-)load the store.
if (self.storeCoordinator && self.mainManagedObjectContext && self.privateManagedObjectContext)
return;
[self.storeQueue addOperationWithBlock:^{
// Do nothing if already fully set up, otherwise (re-)load the store.
if (self.persistentStoreCoordinator && self.saveObserver && self.mainManagedObjectContext && self.privateManagedObjectContext)
if (self.storeCoordinator && self.mainManagedObjectContext && self.privateManagedObjectContext)
return;
// Unregister any existing observers and contexts.
if (self.saveObserver)
[[NSNotificationCenter defaultCenter] removeObserver:self.saveObserver];
PearlRemoveNotificationObserversFrom( self.mainManagedObjectContext );
[self.mainManagedObjectContext performBlockAndWait:^{
[self.mainManagedObjectContext reset];
self.mainManagedObjectContext = nil;
@@ -153,13 +177,31 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
// Check if migration is necessary.
[self migrateStore];
// Create a new store coordinator.
if (!self.persistentStoreCoordinator) {
NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil];
[model kc_generateOrderedSetAccessors];
self.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
}
// Install managed object contexts and observers.
self.privateManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[self.privateManagedObjectContext performBlockAndWait:^{
self.privateManagedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
self.privateManagedObjectContext.persistentStoreCoordinator = self.storeCoordinator;
}];
self.mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
self.mainManagedObjectContext.parentContext = self.privateManagedObjectContext;
// When privateManagedObjectContext is saved, import the changes into mainManagedObjectContext.
PearlAddNotificationObserverTo( self.mainManagedObjectContext, NSManagedObjectContextDidSaveNotification,
self.privateManagedObjectContext, nil, ^(NSManagedObjectContext *mainManagedObjectContext, NSNotification *note) {
[mainManagedObjectContext performBlock:^{
@try {
[mainManagedObjectContext mergeChangesFromContextDidSaveNotification:note];
}
@catch (NSException *exception) {
err( @"While merging changes:\n%@",[exception fullDescription] );
}
}];
} );
// Create a new store coordinator.
NSError *error = nil;
NSURL *localStoreURL = [self localStoreURL];
if (![[NSFileManager defaultManager] createDirectoryAtURL:[localStoreURL URLByDeletingLastPathComponent]
@@ -167,12 +209,12 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
err( @"Couldn't create our application support directory: %@", [error fullDescription] );
return;
}
if (![self.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[self localStoreURL]
options:@{
NSMigratePersistentStoresAutomaticallyOption : @YES,
NSInferMappingModelAutomaticallyOption : @YES,
STORE_OPTIONS
} error:&error]) {
if (![self.storeCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[self localStoreURL]
options:@{
NSMigratePersistentStoresAutomaticallyOption : @YES,
NSInferMappingModelAutomaticallyOption : @YES,
STORE_OPTIONS
} error:&error]) {
err( @"Failed to open store: %@", [error fullDescription] );
self.storeCorrupted = @YES;
[self handleCoordinatorError:error];
@@ -180,25 +222,6 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
}
self.storeCorrupted = @NO;
// Create our contexts and observer.
self.privateManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[self.privateManagedObjectContext performBlockAndWait:^{
self.privateManagedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
self.privateManagedObjectContext.persistentStoreCoordinator = self.persistentStoreCoordinator;
}];
self.mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
self.mainManagedObjectContext.parentContext = self.privateManagedObjectContext;
self.saveObserver = [[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification
object:self.privateManagedObjectContext queue:nil usingBlock:
^(NSNotification *note) {
// When privateManagedObjectContext is saved, import the changes into mainManagedObjectContext.
[self.mainManagedObjectContext performBlock:^{
[self.mainManagedObjectContext mergeChangesFromContextDidSaveNotification:note];
}];
}];
#if TARGET_OS_IPHONE
PearlAddNotificationObserver( UIApplicationWillTerminateNotification, UIApp, [NSOperationQueue mainQueue],
^(MPAppDelegate_Shared *self, NSNotification *note) {
@@ -220,15 +243,14 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
[MPAppDelegate_Shared managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
[self findAndFixInconsistenciesSaveInContext:context];
}];
}
}];
}
- (void)deleteAndResetStore {
@synchronized (self) {
// Unregister any existing observers and contexts.
if (self.saveObserver)
[[NSNotificationCenter defaultCenter] removeObserver:self.saveObserver];
PearlRemoveNotificationObserversFrom( self.mainManagedObjectContext );
[self.mainManagedObjectContext performBlockAndWait:^{
[self.mainManagedObjectContext reset];
self.mainManagedObjectContext = nil;
@@ -238,11 +260,10 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
self.privateManagedObjectContext = nil;
}];
NSError *error = nil;
for (NSPersistentStore *store in self.persistentStoreCoordinator.persistentStores) {
if (![self.persistentStoreCoordinator removePersistentStore:store error:&error])
for (NSPersistentStore *store in self.storeCoordinator.persistentStores) {
if (![self.storeCoordinator removePersistentStore:store error:&error])
err( @"Couldn't remove persistence store from coordinator: %@", [error fullDescription] );
}
self.persistentStoreCoordinator = nil;
if (![[NSFileManager defaultManager] removeItemAtURL:self.localStoreURL error:&error])
err( @"Couldn't remove persistence store at URL %@: %@", self.localStoreURL, [error fullDescription] );
@@ -287,23 +308,25 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
- (void)migrateStore {
MPStoreMigrationLevel migrationLevel = (signed)[[NSUserDefaults standardUserDefaults] integerForKey:MPStoreMigrationLevelKey];
MPStoreMigrationLevel migrationLevel = (signed)[[NSUserDefaults standardUserDefaults] integerForKey:MPMigrationLevelLocalStoreKey];
if (migrationLevel >= MPStoreMigrationLevelCurrent)
// Local store up-to-date.
return;
inf( @"Local store migration level: %d (current %d)", (signed)migrationLevel, (signed)MPStoreMigrationLevelCurrent );
if (migrationLevel == MPStoreMigrationLevelV1 && ![self migrateV1LocalStore]) {
if (migrationLevel <= MPStoreMigrationLevelV1 && ![self migrateV1LocalStore]) {
inf( @"Failed to migrate old V1 to new local store." );
return;
}
if (migrationLevel == MPStoreMigrationLevelV2 && ![self migrateV2LocalStore]) {
if (migrationLevel <= MPStoreMigrationLevelV2 && ![self migrateV2LocalStore]) {
inf( @"Failed to migrate old V2 to new local store." );
return;
}
[[NSUserDefaults standardUserDefaults] setInteger:MPStoreMigrationLevelCurrent forKey:MPStoreMigrationLevelKey];
[[NSUserDefaults standardUserDefaults] setInteger:MPStoreMigrationLevelCurrent forKey:MPMigrationLevelLocalStoreKey];
inf( @"Successfully migrated old to new local store." );
if (![[NSUserDefaults standardUserDefaults] synchronize])
wrn( @"Couldn't synchronize after store migration." );
}
- (BOOL)migrateV1LocalStore {
@@ -319,14 +342,16 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
inf( @"Migrating V1 local store" );
NSURL *newLocalStoreURL = [self localStoreURL];
NSError *error = nil;
if (![[NSFileManager defaultManager] createDirectoryAtURL:[newLocalStoreURL URLByDeletingLastPathComponent]
withIntermediateDirectories:YES attributes:nil error:&error]) {
err( @"Couldn't create our application support directory: %@", [error fullDescription] );
return NO;
if (![[NSFileManager defaultManager] fileExistsAtPath:newLocalStoreURL.path isDirectory:NULL]) {
inf( @"New local store already exists." );
return YES;
}
if (![[NSFileManager defaultManager] moveItemAtURL:oldLocalStoreURL toURL:newLocalStoreURL error:&error]) {
err( @"Couldn't move the old store to the new location: %@", [error fullDescription] );
NSError *error = nil;
if (![NSPersistentStore migrateStore:oldLocalStoreURL withOptions:@{ STORE_OPTIONS }
toStore:newLocalStoreURL withOptions:@{ STORE_OPTIONS }
model:nil error:&error]) {
err( @"Couldn't migrate the old store to the new location: %@", [error fullDescription] );
return NO;
}
@@ -358,14 +383,22 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
inf( @"Migrating V2 local store" );
NSURL *newLocalStoreURL = [self localStoreURL];
NSError *error = nil;
if (![[NSFileManager defaultManager] createDirectoryAtURL:[newLocalStoreURL URLByDeletingLastPathComponent]
withIntermediateDirectories:YES attributes:nil error:&error]) {
err( @"Couldn't create our application support directory: %@", [error fullDescription] );
return NO;
if ([[NSFileManager defaultManager] fileExistsAtPath:newLocalStoreURL.path isDirectory:NULL]) {
inf( @"New local store already exists." );
return YES;
}
if (![[NSFileManager defaultManager] moveItemAtURL:oldLocalStoreURL toURL:newLocalStoreURL error:&error]) {
err( @"Couldn't move the old store to the new location: %@", [error fullDescription] );
NSError *error = nil;
if (![NSPersistentStore migrateStore:oldLocalStoreURL withOptions:@{
NSMigratePersistentStoresAutomaticallyOption : @YES,
NSInferMappingModelAutomaticallyOption : @YES,
STORE_OPTIONS
} toStore:newLocalStoreURL withOptions:@{
NSMigratePersistentStoresAutomaticallyOption : @YES,
NSInferMappingModelAutomaticallyOption : @YES,
STORE_OPTIONS
} model:nil error:&error]) {
err( @"Couldn't migrate the old store to the new location: %@", [error fullDescription] );
return NO;
}
@@ -697,6 +730,10 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
// Create new site.
NSString *typeEntityName = [MPAlgorithmForVersion( version ) classNameOfType:type];
if (!typeEntityName) {
err( @"Invalid site type in import file: %@ has type %lu", siteName, (long)type );
return MPImportResultInternalError;
}
MPSiteEntity *site = [NSEntityDescription insertNewObjectForEntityForName:typeEntityName inManagedObjectContext:context];
site.name = siteName;
site.loginName = loginName;
@@ -721,7 +758,6 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
return MPImportResultInternalError;
inf( @"Import completed successfully." );
MPCheckpoint( MPCheckpointSitesImported, nil );
[[NSNotificationCenter defaultCenter] postNotificationName:MPSitesImportedNotification object:nil userInfo:@{
MPSitesImportedNotificationUserKey : user
@@ -789,10 +825,6 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
[(loginName?: @"") UTF8String], [siteName UTF8String], content?: @""];
}
MPCheckpoint( MPCheckpointSitesExported, @{
@"showPasswords" : @(revealPasswords)
} );
return export;
}

View File

@@ -23,7 +23,7 @@
}
@catch (NSException *exception) {
success = NO;
err( @"While saving: %@", exception );
err( @"While saving: %@", [exception fullDescription] );
}
}];
}
@@ -128,10 +128,15 @@
- (NSString *)debugDescription {
return strf( @"{%@: name=%@, user=%@, type=%lu, uses=%ld, lastUsed=%@, version=%ld, loginName=%@, requiresExplicitMigration=%d}",
NSStringFromClass( [self class] ), self.name, self.user.name, (long)self.type, (long)self.uses, self.lastUsed,
(long)self.version,
self.loginName, self.requiresExplicitMigration );
@try {
return strf( @"{%@: name=%@, user=%@, type=%lu, uses=%ld, lastUsed=%@, version=%ld, loginName=%@, requiresExplicitMigration=%d}",
NSStringFromClass( [self class] ), self.name, self.user.name, (long)self.type, (long)self.uses, self.lastUsed,
(long)self.version,
self.loginName, self.requiresExplicitMigration );
} @catch (NSException *exception) {
return strf( @"{%@: inaccessible: %@}",
NSStringFromClass( [self class] ), [exception fullDescription] );
}
}
- (BOOL)tryMigrateExplicitly:(BOOL)explicit {

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