2
0

Compare commits

...

51 Commits

Author SHA1 Message Date
Maarten Billemont
f8043ae16d Fix chdir test & make curses work when stdout is redirected. 2017-10-06 14:59:29 -04:00
Maarten Billemont
7150f2f5c5 Fix test to match context against question keyword. 2017-10-06 14:58:42 -04:00
Maarten Billemont
81bd2e3065 Make mpw_mkpw symlinkable. 2017-10-06 13:33:08 -04:00
Maarten Billemont
78c9618807 Test chdir. 2017-09-26 11:07:02 -04:00
Maarten Billemont
bed8939b8a Debug issue in testSiteState 2017-09-25 18:35:57 -04:00
Maarten Billemont
9443d93500 Remove MPW_COLOR from core, safer decryption, more standard password input & curses dialog. 2017-09-25 18:34:12 -04:00
Maarten Billemont
877eba66be strdup also isn't standard. 2017-09-25 10:51:14 -04:00
Maarten Billemont
3af8aba40c Make source more standard C11 w/POSIX:2008 CLI. 2017-09-25 10:33:31 -04:00
Maarten Billemont
7ece02c73d Remove stale 2.6-cli-3 2017-09-25 03:05:38 -04:00
Maarten Billemont
ebbd2b3ac4 Re-distribute 2.6-cli-3 with standard build fixes. 2017-09-25 03:01:24 -04:00
Maarten Billemont
a85eff4277 Include semi-standard getline. 2017-09-25 02:59:09 -04:00
Maarten Billemont
98f1c776be Fix some warnings. 2017-09-25 02:56:37 -04:00
Maarten Billemont
6b554c67ed More standard memset_s 2017-09-25 02:53:34 -04:00
Maarten Billemont
f2ae35080d mpw-2.6-cli-3 release. 2017-09-24 15:40:19 -04:00
Maarten Billemont
0ff6c93a95 Document default key size. 2017-09-24 13:18:33 -04:00
Maarten Billemont
9147600b97 Travis cache doesn't need to be disabled anymore. 2017-09-24 13:14:58 -04:00
Maarten Billemont
fafe56166e bzero is nonstandard. Replace with memset_s. 2017-09-24 13:14:16 -04:00
Maarten Billemont
0a024b2594 AES-CBC needs PKCS#7 padding. 2017-09-24 13:06:19 -04:00
Maarten Billemont
b4c2a393f1 Clean up aes state, default to 512 key size, improve log output. 2017-09-24 12:00:38 -04:00
Maarten Billemont
39dcef46d2 Show in Travis log why tests fail. 2017-09-24 00:26:58 -04:00
Maarten Billemont
d6a88583f5 AES needs to be CBC, not CTR. 2017-09-23 20:14:53 -04:00
Maarten Billemont
1c17b84dcf Some tweaks for Travis. 2017-09-23 19:24:06 -04:00
Maarten Billemont
cecaf1b5cc Log fixes, test improvements and some refactoring. 2017-09-23 19:11:06 -04:00
Maarten Billemont
888338e107 Fix siteKey algorithm for siteState. 2017-09-23 19:09:19 -04:00
Maarten Billemont
32055abf29 ciphers.plist is no longer needed. 2017-09-22 20:08:46 -04:00
Maarten Billemont
0f72dffaf1 Updated keyID in test case. 2017-09-22 19:05:59 -04:00
Maarten Billemont
5d1be43b65 Deep Java refactoring to match the C API logic and clean up some OO oddities. 2017-09-22 19:03:50 -04:00
Maarten Billemont
dc7089c38c mpw-tests was not checking mpw_tests.xml's keyID. 2017-09-22 18:23:08 -04:00
Maarten Billemont
34540f0844 Finish rename Marshall -> Marshal. 2017-09-22 15:20:14 -04:00
Maarten Billemont
e818713484 Fix filenames in build 2017-09-22 14:21:31 -04:00
Maarten Billemont
6e2289994c Fix gradle build with missing local.properties. 2017-09-21 10:32:38 -04:00
Maarten Billemont
05a9ba46d0 Marshal refactoring to prepare for new format. 2017-09-20 17:45:49 -04:00
Maarten Billemont
70bb30ba0c Skip masterpassword-android if not set up to build it. 2017-09-20 16:52:20 -04:00
Maarten Billemont
444d7e9b35 Source fix-ups: single l marshal, copyright, .travis. 2017-09-20 12:43:03 -04:00
Maarten Billemont
47164c7a92 Marshal has only one l. 2017-09-20 10:48:04 -04:00
Maarten Billemont
ad00ceb4ce Harmonize C/Java code more, WIP crypt/derive in Java. 2017-09-19 14:52:43 -04:00
Maarten Billemont
473e3ca11f Run gradle test in Travis. 2017-09-19 13:46:27 -04:00
Maarten Billemont
35c0431cec Update Java to match C's internal changes. 2017-09-19 13:45:51 -04:00
Maarten Billemont
70c784db83 Update MP_FULLNAME in mpw.bashrc. 2017-09-15 13:24:45 -04:00
Maarten Billemont
d448099a2d -s is -P now. 2017-09-14 23:14:00 -04:00
Maarten Billemont
e3a7ea57e0 Type key is K, subkey context is not bound by BYTES_MIN/MAX. 2017-09-14 23:03:39 -04:00
Maarten Billemont
fa6133200e Added bashlib. 2017-09-14 16:44:32 -04:00
Maarten Billemont
dfa67bdca9 Added some scripts to do math on password strength and generate random dictionary passphrases. 2017-09-14 16:31:11 -04:00
Maarten Billemont
8c9c4ef7b2 Describe how to use the cmake alternative build system. 2017-09-10 14:17:06 -04:00
Maarten Billemont
1adb18a7e7 Fixed #206 - 'p' trigger for phrase was missing. 2017-09-10 14:03:56 -04:00
Maarten Billemont
f50fdb7777 Some build tool updates, primarily cmake. 2017-09-10 13:57:14 -04:00
Maarten Billemont
33bf2c93d0 Some fixes to the CSS for videos. 2017-09-08 11:19:10 -04:00
Maarten Billemont
f2abcc9e43 A few fixes to the about video. 2017-09-08 10:31:43 -04:00
Maarten Billemont
5ef69aa045 Make bcrypt code more standard. 2017-09-07 00:05:55 -04:00
Maarten Billemont
1c0f274868 Include for waitpid. 2017-09-06 23:54:52 -04:00
Maarten Billemont
1f592f50a9 Release 2.6-cli-2 2017-09-06 00:34:09 -04:00
142 changed files with 24812 additions and 4500 deletions

View File

@@ -1,4 +1,5 @@
language: objective-c language: objective-c
os: osx
osx_image: xcode8.3 osx_image: xcode8.3
env: TERM=dumb SHLVL=0 env: TERM=dumb SHLVL=0
git: git:
@@ -6,5 +7,6 @@ git:
script: script:
- "( brew install libsodium json-c )" - "( brew install libsodium json-c )"
- "( cd ./platform-independent/cli-c && ./clean && targets='mpw mpw-bench mpw-tests' ./build && ./mpw-tests && ./mpw-cli-tests )" - "( cd ./platform-independent/cli-c && ./clean && targets='mpw mpw-bench mpw-tests' ./build && ./mpw-tests && ./mpw-cli-tests )"
- "( cd ./gradle && ./gradlew --info clean test )"
- "( xcodebuild -workspace platform-darwin/MasterPassword.xcworkspace -configuration 'Test' -scheme 'MasterPassword iOS' -sdk iphonesimulator )" - "( xcodebuild -workspace platform-darwin/MasterPassword.xcworkspace -configuration 'Test' -scheme 'MasterPassword iOS' -sdk iphonesimulator )"
- "( xcodebuild -workspace platform-darwin/MasterPassword.xcworkspace -configuration 'Test' -scheme 'MasterPassword macOS' )" - "( xcodebuild -workspace platform-darwin/MasterPassword.xcworkspace -configuration 'Test' -scheme 'MasterPassword macOS' )"

606
core/c/aes.c Normal file
View File

@@ -0,0 +1,606 @@
/*
Source: https://github.com/kokke/tiny-AES-c
This is an implementation of the AES algorithm, specifically ECB and CBC mode.
Block size can be chosen in aes.h - available choices are AES128, AES192, AES256.
The implementation is verified against the test vectors in:
National Institute of Standards and Technology Special Publication 800-38A 2001 ED
ECB-AES128
----------
plain-text:
6bc1bee22e409f96e93d7e117393172a
ae2d8a571e03ac9c9eb76fac45af8e51
30c81c46a35ce411e5fbc1191a0a52ef
f69f2445df4f9b17ad2b417be66c3710
key:
2b7e151628aed2a6abf7158809cf4f3c
resulting cipher
3ad77bb40d7a3660a89ecaf32466ef97
f5d3d58503b9699de785895a96fdbaaf
43b1cd7f598ece23881b00e3ed030688
7b0c785e27e8ad3f8223207104725dd4
NOTE: String length must be evenly divisible by 16byte (str_len % 16 == 0)
You should pad the end of the string with zeros if this is not the case.
For AES192/256 the block size is proportionally larger.
*/
/*****************************************************************************/
/* Includes: */
/*****************************************************************************/
#include <string.h>
#include "aes.h"
#include "mpw-util.h"
/*****************************************************************************/
/* Defines: */
/*****************************************************************************/
// The number of columns comprising a state in AES. This is a constant in AES. Value=4
#define Nb 4
#if defined(AES_256) && (AES_256 == 1)
#define Nk 8
#define KEYLEN 32
#define Nr 14
#define keyExpSize 240
#elif defined(AES_192) && (AES_192 == 1)
#define Nk 6
#define KEYLEN 24
#define Nr 12
#define keyExpSize 208
#elif defined(AES_128) && (AES_128 == 1)
#define Nk 4 // The number of 32 bit words in a key.
#define KEYLEN 16 // Key length in bytes
#define Nr 10 // The number of rounds in AES Cipher.
#define keyExpSize 176
#else
#error Must define either AES_128, AES_192 or AES_256.
#endif
// jcallan@github points out that declaring Multiply as a function
// reduces code size considerably with the Keil ARM compiler.
// See this link for more information: https://github.com/kokke/tiny-AES128-C/pull/3
#ifndef MULTIPLY_AS_A_FUNCTION
#define MULTIPLY_AS_A_FUNCTION 0
#endif
/*****************************************************************************/
/* Private variables: */
/*****************************************************************************/
// state - array holding the intermediate results during decryption.
typedef uint8_t state_t[4][4];
static state_t* state;
// The array that stores the round keys.
static uint8_t RoundKey[keyExpSize];
// The Key input to the AES Program
static const uint8_t* Key;
#if defined(AES_CBC) && AES_CBC
// Initial Vector used only for CBC mode
static uint8_t* Iv;
#endif
// The lookup-tables are marked const so they can be placed in read-only storage instead of RAM
// The numbers below can be computed dynamically trading ROM for RAM -
// This can be useful in (embedded) bootloader applications, where ROM is often limited.
static const uint8_t sbox[256] = {
//0 1 2 3 4 5 6 7 8 9 A B C D E F
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 };
static const uint8_t rsbox[256] = {
0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d };
// The round constant word array, Rcon[i], contains the values given by
// x to th e power (i-1) being powers of x (x is denoted as {02}) in the field GF(2^8)
static const uint8_t Rcon[11] = {
0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 };
/*
* Jordan Goulder points out in PR #12 (https://github.com/kokke/tiny-AES128-C/pull/12),
* that you can remove most of the elements in the Rcon array, because they are unused.
*
* From Wikipedia's article on the Rijndael key schedule @ https://en.wikipedia.org/wiki/Rijndael_key_schedule#Rcon
*
* "Only the first some of these constants are actually used up to rcon[10] for AES-128 (as 11 round keys are needed),
* up to rcon[8] for AES-192, up to rcon[7] for AES-256. rcon[0] is not used in AES algorithm."
*
* ... which is why the full array below has been 'disabled' below.
*/
#if 0
static const uint8_t Rcon[256] = {
0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a,
0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39,
0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a,
0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8,
0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef,
0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc,
0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b,
0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3,
0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94,
0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20,
0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35,
0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f,
0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04,
0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63,
0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd,
0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d };
#endif
/*****************************************************************************/
/* Private functions: */
/*****************************************************************************/
static uint8_t getSBoxValue(uint8_t num)
{
return sbox[num];
}
static uint8_t getSBoxInvert(uint8_t num)
{
return rsbox[num];
}
// This function produces Nb(Nr+1) round keys. The round keys are used in each round to decrypt the states.
static void KeyExpansion(void)
{
uint32_t i;
uint8_t k, tempa[4]; // Used for the column/row operations
// The first round key is the key itself.
for (i = 0; i < Nk; ++i)
{
RoundKey[(i * 4) + 0] = Key[(i * 4) + 0];
RoundKey[(i * 4) + 1] = Key[(i * 4) + 1];
RoundKey[(i * 4) + 2] = Key[(i * 4) + 2];
RoundKey[(i * 4) + 3] = Key[(i * 4) + 3];
}
// All other round keys are found from the previous round keys.
//i == Nk
for (; i < Nb * (Nr + 1); ++i)
{
{
tempa[0]=RoundKey[(i-1) * 4 + 0];
tempa[1]=RoundKey[(i-1) * 4 + 1];
tempa[2]=RoundKey[(i-1) * 4 + 2];
tempa[3]=RoundKey[(i-1) * 4 + 3];
}
if (i % Nk == 0)
{
// This function shifts the 4 bytes in a word to the left once.
// [a0,a1,a2,a3] becomes [a1,a2,a3,a0]
// Function RotWord()
{
k = tempa[0];
tempa[0] = tempa[1];
tempa[1] = tempa[2];
tempa[2] = tempa[3];
tempa[3] = k;
}
// SubWord() is a function that takes a four-byte input word and
// applies the S-box to each of the four bytes to produce an output word.
// Function Subword()
{
tempa[0] = getSBoxValue(tempa[0]);
tempa[1] = getSBoxValue(tempa[1]);
tempa[2] = getSBoxValue(tempa[2]);
tempa[3] = getSBoxValue(tempa[3]);
}
tempa[0] = tempa[0] ^ Rcon[i/Nk];
}
#if defined(AES256) && (AES256 == 1)
if (i % Nk == 4)
{
// Function Subword()
{
tempa[0] = getSBoxValue(tempa[0]);
tempa[1] = getSBoxValue(tempa[1]);
tempa[2] = getSBoxValue(tempa[2]);
tempa[3] = getSBoxValue(tempa[3]);
}
}
#endif
RoundKey[i * 4 + 0] = RoundKey[(i - Nk) * 4 + 0] ^ tempa[0];
RoundKey[i * 4 + 1] = RoundKey[(i - Nk) * 4 + 1] ^ tempa[1];
RoundKey[i * 4 + 2] = RoundKey[(i - Nk) * 4 + 2] ^ tempa[2];
RoundKey[i * 4 + 3] = RoundKey[(i - Nk) * 4 + 3] ^ tempa[3];
}
}
// This function adds the round key to state.
// The round key is added to the state by an XOR function.
static void AddRoundKey(uint8_t round)
{
uint8_t i,j;
for (i=0;i<4;++i)
{
for (j = 0; j < 4; ++j)
{
(*state)[i][j] ^= RoundKey[round * Nb * 4 + i * Nb + j];
}
}
}
// The SubBytes Function Substitutes the values in the
// state matrix with values in an S-box.
static void SubBytes(void)
{
uint8_t i, j;
for (i = 0; i < 4; ++i)
{
for (j = 0; j < 4; ++j)
{
(*state)[j][i] = getSBoxValue((*state)[j][i]);
}
}
}
// The ShiftRows() function shifts the rows in the state to the left.
// Each row is shifted with different offset.
// Offset = Row number. So the first row is not shifted.
static void ShiftRows(void)
{
uint8_t temp;
// Rotate first row 1 columns to left
temp = (*state)[0][1];
(*state)[0][1] = (*state)[1][1];
(*state)[1][1] = (*state)[2][1];
(*state)[2][1] = (*state)[3][1];
(*state)[3][1] = temp;
// Rotate second row 2 columns to left
temp = (*state)[0][2];
(*state)[0][2] = (*state)[2][2];
(*state)[2][2] = temp;
temp = (*state)[1][2];
(*state)[1][2] = (*state)[3][2];
(*state)[3][2] = temp;
// Rotate third row 3 columns to left
temp = (*state)[0][3];
(*state)[0][3] = (*state)[3][3];
(*state)[3][3] = (*state)[2][3];
(*state)[2][3] = (*state)[1][3];
(*state)[1][3] = temp;
}
static uint8_t xtime(uint8_t x)
{
return (uint8_t)((x << 1) ^ (((x >> 7) & 1) * 0x1b));
}
// MixColumns function mixes the columns of the state matrix
static void MixColumns(void)
{
uint8_t i;
uint8_t Tmp,Tm,t;
for (i = 0; i < 4; ++i)
{
t = (*state)[i][0];
Tmp = (*state)[i][0] ^ (*state)[i][1] ^ (*state)[i][2] ^ (*state)[i][3] ;
Tm = (*state)[i][0] ^ (*state)[i][1] ; Tm = xtime(Tm); (*state)[i][0] ^= Tm ^ Tmp ;
Tm = (*state)[i][1] ^ (*state)[i][2] ; Tm = xtime(Tm); (*state)[i][1] ^= Tm ^ Tmp ;
Tm = (*state)[i][2] ^ (*state)[i][3] ; Tm = xtime(Tm); (*state)[i][2] ^= Tm ^ Tmp ;
Tm = (*state)[i][3] ^ t ; Tm = xtime(Tm); (*state)[i][3] ^= Tm ^ Tmp ;
}
}
// Multiply is used to multiply numbers in the field GF(2^8)
#if MULTIPLY_AS_A_FUNCTION
static uint8_t Multiply(uint8_t x, uint8_t y)
{
return (((y & 1) * x) ^
((y>>1 & 1) * xtime(x)) ^
((y>>2 & 1) * xtime(xtime(x))) ^
((y>>3 & 1) * xtime(xtime(xtime(x)))) ^
((y>>4 & 1) * xtime(xtime(xtime(xtime(x))))));
}
#else
#define Multiply(x, y) (uint8_t) \
( ((y & 1) * x) ^ \
((y>>1 & 1) * xtime(x)) ^ \
((y>>2 & 1) * xtime(xtime(x))) ^ \
((y>>3 & 1) * xtime(xtime(xtime(x)))) ^ \
((y>>4 & 1) * xtime(xtime(xtime(xtime(x)))))) \
#endif
// MixColumns function mixes the columns of the state matrix.
// The method used to multiply may be difficult to understand for the inexperienced.
// Please use the references to gain more information.
static void InvMixColumns(void)
{
int i;
uint8_t a, b, c, d;
for (i = 0; i < 4; ++i)
{
a = (*state)[i][0];
b = (*state)[i][1];
c = (*state)[i][2];
d = (*state)[i][3];
(*state)[i][0] = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ Multiply(d, 0x09);
(*state)[i][1] = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ Multiply(d, 0x0d);
(*state)[i][2] = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ Multiply(d, 0x0b);
(*state)[i][3] = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ Multiply(d, 0x0e);
}
}
// The SubBytes Function Substitutes the values in the
// state matrix with values in an S-box.
static void InvSubBytes(void)
{
uint8_t i,j;
for (i = 0; i < 4; ++i)
{
for (j = 0; j < 4; ++j)
{
(*state)[j][i] = getSBoxInvert((*state)[j][i]);
}
}
}
static void InvShiftRows(void)
{
uint8_t temp;
// Rotate first row 1 columns to right
temp = (*state)[3][1];
(*state)[3][1] = (*state)[2][1];
(*state)[2][1] = (*state)[1][1];
(*state)[1][1] = (*state)[0][1];
(*state)[0][1] = temp;
// Rotate second row 2 columns to right
temp = (*state)[0][2];
(*state)[0][2] = (*state)[2][2];
(*state)[2][2] = temp;
temp = (*state)[1][2];
(*state)[1][2] = (*state)[3][2];
(*state)[3][2] = temp;
// Rotate third row 3 columns to right
temp = (*state)[0][3];
(*state)[0][3] = (*state)[1][3];
(*state)[1][3] = (*state)[2][3];
(*state)[2][3] = (*state)[3][3];
(*state)[3][3] = temp;
}
// Cipher is the main function that encrypts the PlainText.
static void Cipher(void)
{
uint8_t round = 0;
// Add the First round key to the state before starting the rounds.
AddRoundKey(0);
// There will be Nr rounds.
// The first Nr-1 rounds are identical.
// These Nr-1 rounds are executed in the loop below.
for (round = 1; round < Nr; ++round)
{
SubBytes();
ShiftRows();
MixColumns();
AddRoundKey(round);
}
// The last round is given below.
// The MixColumns function is not here in the last round.
SubBytes();
ShiftRows();
AddRoundKey(Nr);
}
static void InvCipher(void)
{
uint8_t round=0;
// Add the First round key to the state before starting the rounds.
AddRoundKey(Nr);
// There will be Nr rounds.
// The first Nr-1 rounds are identical.
// These Nr-1 rounds are executed in the loop below.
for (round = (Nr - 1); round > 0; --round)
{
InvShiftRows();
InvSubBytes();
AddRoundKey(round);
InvMixColumns();
}
// The last round is given below.
// The MixColumns function is not here in the last round.
InvShiftRows();
InvSubBytes();
AddRoundKey(0);
}
/*****************************************************************************/
/* Public functions: */
/*****************************************************************************/
#if defined(AES_ECB) && (AES_ECB == 1)
void AES_ECB_encrypt(uint8_t *output, const uint8_t *input, const uint32_t length, const uint8_t *key)
{
// Copy input to output, and work in-memory on output
memcpy(output, input, length);
state = (state_t*)output;
Key = key;
KeyExpansion();
// The next function call encrypts the PlainText with the Key using AES algorithm.
Cipher();
mpw_zero( RoundKey, keyExpSize );
}
void AES_ECB_decrypt(uint8_t *output, const uint8_t *input, const uint32_t length, const uint8_t *key)
{
// Copy input to output, and work in-memory on output
memcpy(output, input, length);
state = (state_t*)output;
// The KeyExpansion routine must be called before encryption.
Key = key;
KeyExpansion();
InvCipher();
mpw_zero( RoundKey, keyExpSize );
}
#endif // #if defined(AES_ECB) && (AES_ECB == 1)
#if defined(AES_CBC) && (AES_CBC == 1)
static void XorWithIv(uint8_t* buf)
{
uint8_t i;
for (i = 0; i < AES_BLOCKLEN; ++i) //WAS for(i = 0; i < KEYLEN; ++i) but the block in AES is always 128bit so 16 bytes!
{
buf[i] ^= Iv[i];
}
}
void AES_CBC_encrypt_buffer(uint8_t* output, uint8_t* input, uint32_t length, const uint8_t* key, const uint8_t* iv)
{
uintptr_t i;
uint8_t extra = (uint8_t)(length % AES_BLOCKLEN); /* Remaining bytes in the last non-full block */
// Skip the key expansion if key is passed as 0
if (0 != key)
{
Key = key;
KeyExpansion();
}
if (iv != 0)
{
Iv = (uint8_t*)iv;
}
for (i = 0; i < length; i += AES_BLOCKLEN)
{
XorWithIv(input);
memcpy(output, input, AES_BLOCKLEN);
state = (state_t*)output;
Cipher();
Iv = output;
input += AES_BLOCKLEN;
output += AES_BLOCKLEN;
//printf("Step %d - %d", i/16, i);
}
if (extra)
{
memcpy(output, input, extra);
state = (state_t*)output;
Cipher();
}
mpw_zero( RoundKey, keyExpSize );
}
void AES_CBC_decrypt_buffer(uint8_t* output, uint8_t* input, uint32_t length, const uint8_t* key, const uint8_t* iv)
{
uintptr_t i;
uint8_t extra = (uint8_t)(length % AES_BLOCKLEN); /* Remaining bytes in the last non-full block */
// Skip the key expansion if key is passed as 0
if (0 != key)
{
Key = key;
KeyExpansion();
}
// If iv is passed as 0, we continue to encrypt without re-setting the Iv
if (iv != 0)
{
Iv = (uint8_t*)iv;
}
for (i = 0; i < length; i += AES_BLOCKLEN)
{
memcpy(output, input, AES_BLOCKLEN);
state = (state_t*)output;
InvCipher();
XorWithIv(output);
Iv = input;
input += AES_BLOCKLEN;
output += AES_BLOCKLEN;
}
if (extra)
{
memcpy(output, input, extra);
state = (state_t*)output;
InvCipher();
}
mpw_zero( RoundKey, keyExpSize );
}
#endif // #if defined(AES_CBC) && (AES_CBC == 1)

50
core/c/aes.h Normal file
View File

@@ -0,0 +1,50 @@
/*
Source: https://github.com/kokke/tiny-AES-c
This is an implementation of the AES algorithm, specifically ECB and CBC mode.
*/
#ifndef _AES_H_
#define _AES_H_
#include <stdint.h>
// #define the macros below to 1/0 to enable/disable the mode of operation.
//
// AES_CBC enables AES encryption in CBC-mode of operation.
// AES_ECB enables the basic ECB 16-byte block algorithm. Both can be enabled simultaneously.
// The #ifndef-guard allows it to be configured before #include'ing or at compile time.
#ifndef AES_CBC
#define AES_CBC 1
#endif
#ifndef AES_ECB
#define AES_ECB 1
#endif
#define AES_128 1
//#define AES_192 1
//#define AES_256 1
#define AES_BLOCKLEN 16 //Block length in bytes AES is 128b block only
#if defined(AES_ECB) && (AES_ECB == 1)
void AES_ECB_encrypt(uint8_t *output, const uint8_t *input, const uint32_t length, const uint8_t *key);
void AES_ECB_decrypt(uint8_t *output, const uint8_t *input, const uint32_t length, const uint8_t *key);
#endif // #if defined(AES_ECB) && (AES_ECB == !)
#if defined(AES_CBC) && (AES_CBC == 1)
void AES_CBC_encrypt_buffer(uint8_t* output, uint8_t* input, const uint32_t length, const uint8_t* key, const uint8_t* iv);
void AES_CBC_decrypt_buffer(uint8_t* output, uint8_t* input, const uint32_t length, const uint8_t* key, const uint8_t* iv);
#endif // #if defined(AES_CBC) && (AES_CBC == 1)
#endif //_AES_H_

View File

@@ -169,13 +169,13 @@ const char *mpw_siteState(
if (resultParam && !strlen( resultParam )) if (resultParam && !strlen( resultParam ))
resultParam = NULL; resultParam = NULL;
MPSiteKey siteKey = mpw_siteKey_v0( masterKey, siteName, siteCounter, keyPurpose, keyContext ); MPSiteKey siteKey = mpw_siteKey( masterKey, siteName, siteCounter, keyPurpose, keyContext, algorithmVersion );
if (!siteKey) if (!siteKey)
return NULL; return NULL;
trc( "-- mpw_siteState (algorithm: %u)\n", algorithmVersion ); trc( "-- mpw_siteState (algorithm: %u)\n", algorithmVersion );
trc( "resultType: %d (%s)\n", resultType, mpw_nameForType( resultType ) ); trc( "resultType: %d (%s)\n", resultType, mpw_nameForType( resultType ) );
trc( "resultParam: %s\n", resultParam ); trc( "resultParam: %zu bytes = %s\n", sizeof( resultParam ), resultParam );
if (!masterKey || !resultParam) if (!masterKey || !resultParam)
return NULL; return NULL;

View File

@@ -165,7 +165,7 @@ static const char *mpw_sitePasswordFromCrypt_v0(
// Base64-decode // Base64-decode
uint8_t *cipherBuf = calloc( 1, mpw_base64_decode_max( cipherText ) ); uint8_t *cipherBuf = calloc( 1, mpw_base64_decode_max( cipherText ) );
size_t bufSize = (size_t)mpw_base64_decode( cipherBuf, cipherText ); size_t bufSize = (size_t)mpw_base64_decode( cipherBuf, cipherText ), cipherBufSize = bufSize;
if ((int)bufSize < 0) { if ((int)bufSize < 0) {
err( "Base64 decoding error." ); err( "Base64 decoding error." );
mpw_free( &cipherBuf, mpw_base64_decode_max( cipherText ) ); mpw_free( &cipherBuf, mpw_base64_decode_max( cipherText ) );
@@ -174,13 +174,13 @@ static const char *mpw_sitePasswordFromCrypt_v0(
trc( "b64 decoded: %zu bytes = %s\n", bufSize, mpw_hex( cipherBuf, bufSize ) ); trc( "b64 decoded: %zu bytes = %s\n", bufSize, mpw_hex( cipherBuf, bufSize ) );
// Decrypt // Decrypt
const uint8_t *plainBytes = mpw_aes_decrypt( masterKey, MPMasterKeySize, cipherBuf, bufSize ); const uint8_t *plainBytes = mpw_aes_decrypt( masterKey, MPMasterKeySize, cipherBuf, &bufSize );
mpw_free( &cipherBuf, bufSize ); mpw_free( &cipherBuf, cipherBufSize );
const char *plainText = strndup( (char *)plainBytes, bufSize ); const char *plainText = mpw_strndup( (char *)plainBytes, bufSize );
mpw_free( &plainBytes, bufSize ); mpw_free( &plainBytes, bufSize );
if (!plainText) if (!plainText)
err( "AES decryption error: %s\n", strerror( errno ) ); err( "AES decryption error: %s\n", strerror( errno ) );
trc( "decrypted -> plainText: %s = %s\n", plainText, mpw_hex( plainText, sizeof( plainText ) ) ); trc( "decrypted -> plainText: %zu bytes = %s = %s\n", strlen( plainText ), plainText, mpw_hex( plainText, strlen( plainText ) ) );
return plainText; return plainText;
} }
@@ -195,6 +195,8 @@ static const char *mpw_sitePasswordFromDerive_v0(
return NULL; return NULL;
} }
int resultParamInt = atoi( resultParam ); int resultParamInt = atoi( resultParam );
if (!resultParamInt)
resultParamInt = 512;
if (resultParamInt < 128 || resultParamInt > 512 || resultParamInt % 8 != 0) { if (resultParamInt < 128 || resultParamInt > 512 || resultParamInt % 8 != 0) {
err( "Parameter is not a valid key size (should be 128 - 512): %s\n", resultParam ); err( "Parameter is not a valid key size (should be 128 - 512): %s\n", resultParam );
return NULL; return NULL;
@@ -217,7 +219,7 @@ static const char *mpw_sitePasswordFromDerive_v0(
mpw_free_string( &b64Key ); mpw_free_string( &b64Key );
} }
else else
trc( "b64 encoded -> key.id: %s\n", mpw_id_buf( b64Key, strlen( b64Key ) ) ); trc( "b64 encoded -> key: %s\n", b64Key );
mpw_free( &resultKey, keySize ); mpw_free( &resultKey, keySize );
return b64Key; return b64Key;
@@ -233,7 +235,7 @@ static const char *mpw_siteState_v0(
// Encrypt // Encrypt
size_t bufSize = strlen( plainText ); size_t bufSize = strlen( plainText );
const uint8_t *cipherBuf = mpw_aes_encrypt( masterKey, MPMasterKeySize, (const uint8_t *)plainText, bufSize ); const uint8_t *cipherBuf = mpw_aes_encrypt( masterKey, MPMasterKeySize, (const uint8_t *)plainText, &bufSize );
if (!cipherBuf) { if (!cipherBuf) {
err( "AES encryption error: %s\n", strerror( errno ) ); err( "AES encryption error: %s\n", strerror( errno ) );
return NULL; return NULL;
@@ -248,7 +250,7 @@ static const char *mpw_siteState_v0(
mpw_free_string( &cipherText ); mpw_free_string( &cipherText );
} }
else else
trc( "b64 encoded -> cipherText: %s = %s\n", cipherText, mpw_hex( cipherText, sizeof( cipherText ) ) ); trc( "b64 encoded -> cipherText: %s\n", cipherText );
mpw_free( &cipherBuf, bufSize ); mpw_free( &cipherBuf, bufSize );
return cipherText; return cipherText;

View File

@@ -18,7 +18,7 @@
#include <string.h> #include <string.h>
#include "mpw-marshall-util.h" #include "mpw-marshal-util.h"
#include "mpw-util.h" #include "mpw-util.h"
char *mpw_get_token(const char **in, const char *eol, char *delim) { char *mpw_get_token(const char **in, const char *eol, char *delim) {
@@ -28,7 +28,7 @@ char *mpw_get_token(const char **in, const char *eol, char *delim) {
// Find characters up to the first delim. // Find characters up to the first delim.
size_t len = strcspn( *in, delim ); size_t len = strcspn( *in, delim );
char *token = len && len <= (size_t)(eol - *in)? strndup( *in, len ): NULL; char *token = len && len <= (size_t)(eol - *in)? mpw_strndup( *in, len ): NULL;
// Advance past the delimitor. // Advance past the delimitor.
*in = min( eol, *in + len + 1 ); *in = min( eol, *in + len + 1 );
@@ -38,7 +38,7 @@ char *mpw_get_token(const char **in, const char *eol, char *delim) {
time_t mpw_mktime( time_t mpw_mktime(
const char *time) { const char *time) {
struct tm tm = { .tm_isdst = -1, .tm_gmtoff = 0 }; struct tm tm = { .tm_isdst = -1 };
if (time && sscanf( time, "%4d-%2d-%2dT%2d:%2d:%2dZ", if (time && sscanf( time, "%4d-%2d-%2dT%2d:%2d:%2dZ",
&tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
&tm.tm_hour, &tm.tm_min, &tm.tm_sec ) == 6) { &tm.tm_hour, &tm.tm_min, &tm.tm_sec ) == 6) {
@@ -55,7 +55,7 @@ json_object *mpw_get_json_section(
json_object *obj, const char *section) { json_object *obj, const char *section) {
json_object *json_value = obj; json_object *json_value = obj;
char *sectionTokenizer = strdup( section ), *sectionToken = sectionTokenizer; char *sectionTokenizer = mpw_strdup( section ), *sectionToken = sectionTokenizer;
for (sectionToken = strtok( sectionToken, "." ); sectionToken; sectionToken = strtok( NULL, "." )) for (sectionToken = strtok( sectionToken, "." ); sectionToken; sectionToken = strtok( NULL, "." ))
if (!json_object_object_get_ex( json_value, sectionToken, &json_value ) || !json_value) { if (!json_object_object_get_ex( json_value, sectionToken, &json_value ) || !json_value) {
trc( "While resolving: %s: Missing value for: %s\n", section, sectionToken ); trc( "While resolving: %s: Missing value for: %s\n", section, sectionToken );

View File

@@ -16,8 +16,8 @@
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>. // LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//============================================================================== //==============================================================================
#ifndef _MPW_MARSHALL_UTIL_H #ifndef _MPW_MARSHAL_UTIL_H
#define _MPW_MARSHALL_UTIL_H #define _MPW_MARSHAL_UTIL_H
#include <time.h> #include <time.h>
#if MPW_JSON #if MPW_JSON
@@ -70,4 +70,4 @@ bool mpw_update_masterKey(
MPMasterKey *masterKey, MPAlgorithmVersion *masterKeyAlgorithm, MPAlgorithmVersion targetKeyAlgorithm, MPMasterKey *masterKey, MPAlgorithmVersion *masterKeyAlgorithm, MPAlgorithmVersion targetKeyAlgorithm,
const char *fullName, const char *masterPassword); const char *fullName, const char *masterPassword);
#endif // _MPW_MARSHALL_UTIL_H #endif // _MPW_MARSHAL_UTIL_H

View File

@@ -21,11 +21,11 @@
#include <string.h> #include <string.h>
#include <ctype.h> #include <ctype.h>
#include "mpw-marshall.h" #include "mpw-marshal.h"
#include "mpw-util.h" #include "mpw-util.h"
#include "mpw-marshall-util.h" #include "mpw-marshal-util.h"
MPMarshalledUser *mpw_marshall_user( MPMarshalledUser *mpw_marshal_user(
const char *fullName, const char *masterPassword, const MPAlgorithmVersion algorithmVersion) { const char *fullName, const char *masterPassword, const MPAlgorithmVersion algorithmVersion) {
MPMarshalledUser *user; MPMarshalledUser *user;
@@ -33,8 +33,8 @@ MPMarshalledUser *mpw_marshall_user(
return NULL; return NULL;
*user = (MPMarshalledUser){ *user = (MPMarshalledUser){
.fullName = strdup( fullName ), .fullName = mpw_strdup( fullName ),
.masterPassword = strdup( masterPassword ), .masterPassword = mpw_strdup( masterPassword ),
.algorithm = algorithmVersion, .algorithm = algorithmVersion,
.redacted = true, .redacted = true,
@@ -46,9 +46,9 @@ MPMarshalledUser *mpw_marshall_user(
.sites = NULL, .sites = NULL,
}; };
return user; return user;
}; }
MPMarshalledSite *mpw_marshall_site( MPMarshalledSite *mpw_marshal_site(
MPMarshalledUser *user, const char *siteName, const MPResultType resultType, MPMarshalledUser *user, const char *siteName, const MPResultType resultType,
const MPCounterValue siteCounter, const MPAlgorithmVersion algorithmVersion) { const MPCounterValue siteCounter, const MPAlgorithmVersion algorithmVersion) {
@@ -57,7 +57,7 @@ MPMarshalledSite *mpw_marshall_site(
MPMarshalledSite *site = &user->sites[user->sites_count - 1]; MPMarshalledSite *site = &user->sites[user->sites_count - 1];
*site = (MPMarshalledSite){ *site = (MPMarshalledSite){
.name = strdup( siteName ), .name = mpw_strdup( siteName ),
.content = NULL, .content = NULL,
.type = resultType, .type = resultType,
.counter = siteCounter, .counter = siteCounter,
@@ -74,7 +74,7 @@ MPMarshalledSite *mpw_marshall_site(
.questions = NULL, .questions = NULL,
}; };
return site; return site;
}; }
MPMarshalledQuestion *mpw_marshal_question( MPMarshalledQuestion *mpw_marshal_question(
MPMarshalledSite *site, const char *keyword) { MPMarshalledSite *site, const char *keyword) {
@@ -86,7 +86,7 @@ MPMarshalledQuestion *mpw_marshal_question(
MPMarshalledQuestion *question = &site->questions[site->questions_count - 1]; MPMarshalledQuestion *question = &site->questions[site->questions_count - 1];
*question = (MPMarshalledQuestion){ *question = (MPMarshalledQuestion){
.keyword = strdup( keyword ), .keyword = mpw_strdup( keyword ),
.content = NULL, .content = NULL,
.type = MPResultTypeTemplatePhrase, .type = MPResultTypeTemplatePhrase,
}; };
@@ -94,14 +94,14 @@ MPMarshalledQuestion *mpw_marshal_question(
} }
bool mpw_marshal_info_free( bool mpw_marshal_info_free(
MPMarshallInfo **info) { MPMarshalInfo **info) {
if (!info || !*info) if (!info || !*info)
return true; return true;
bool success = true; bool success = true;
success &= mpw_free_strings( &(*info)->fullName, &(*info)->keyID, NULL ); success &= mpw_free_strings( &(*info)->fullName, &(*info)->keyID, NULL );
success &= mpw_free( info, sizeof( MPMarshallInfo ) ); success &= mpw_free( info, sizeof( MPMarshalInfo ) );
return success; return success;
} }
@@ -132,22 +132,22 @@ bool mpw_marshal_free(
return success; return success;
} }
static bool mpw_marshall_write_flat( static bool mpw_marshal_write_flat(
char **out, const MPMarshalledUser *user, MPMarshallError *error) { char **out, const MPMarshalledUser *user, MPMarshalError *error) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Unexpected internal error." }; *error = (MPMarshalError){ MPMarshalErrorInternal, "Unexpected internal error." };
if (!user->fullName || !strlen( user->fullName )) { if (!user->fullName || !strlen( user->fullName )) {
*error = (MPMarshallError){ MPMarshallErrorMissing, "Missing full name." }; *error = (MPMarshalError){ MPMarshalErrorMissing, "Missing full name." };
return false; return false;
} }
if (!user->masterPassword || !strlen( user->masterPassword )) { if (!user->masterPassword || !strlen( user->masterPassword )) {
*error = (MPMarshallError){ MPMarshallErrorMasterPassword, "Missing master password." }; *error = (MPMarshalError){ MPMarshalErrorMasterPassword, "Missing master password." };
return false; return false;
} }
MPMasterKey masterKey = NULL; MPMasterKey masterKey = NULL;
MPAlgorithmVersion masterKeyAlgorithm = user->algorithm - 1; MPAlgorithmVersion masterKeyAlgorithm = user->algorithm - 1;
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, user->algorithm, user->fullName, user->masterPassword )) { if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, user->algorithm, user->fullName, user->masterPassword )) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." }; *error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't derive master key." };
return false; return false;
} }
@@ -186,7 +186,7 @@ static bool mpw_marshall_write_flat(
if (!user->redacted) { if (!user->redacted) {
// Clear Text // Clear Text
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, user->fullName, user->masterPassword )) { if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, user->fullName, user->masterPassword )) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." }; *error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't derive master key." };
return false; return false;
} }
@@ -198,9 +198,9 @@ static bool mpw_marshall_write_flat(
else { else {
// Redacted // Redacted
if (site->type & MPSiteFeatureExportContent && site->content && strlen( site->content )) if (site->type & MPSiteFeatureExportContent && site->content && strlen( site->content ))
content = strdup( site->content ); content = mpw_strdup( site->content );
if (site->loginType & MPSiteFeatureExportContent && site->loginContent && strlen( site->loginContent )) if (site->loginType & MPSiteFeatureExportContent && site->loginContent && strlen( site->loginContent ))
loginContent = strdup( site->loginContent ); loginContent = mpw_strdup( site->loginContent );
} }
if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &site->lastUsed ) )) if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &site->lastUsed ) ))
@@ -211,27 +211,27 @@ static bool mpw_marshall_write_flat(
} }
mpw_free( &masterKey, MPMasterKeySize ); mpw_free( &masterKey, MPMasterKeySize );
*error = (MPMarshallError){ .type = MPMarshallSuccess }; *error = (MPMarshalError){ .type = MPMarshalSuccess };
return true; return true;
} }
#if MPW_JSON #if MPW_JSON
static bool mpw_marshall_write_json( static bool mpw_marshal_write_json(
char **out, const MPMarshalledUser *user, MPMarshallError *error) { char **out, const MPMarshalledUser *user, MPMarshalError *error) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Unexpected internal error." }; *error = (MPMarshalError){ MPMarshalErrorInternal, "Unexpected internal error." };
if (!user->fullName || !strlen( user->fullName )) { if (!user->fullName || !strlen( user->fullName )) {
*error = (MPMarshallError){ MPMarshallErrorMissing, "Missing full name." }; *error = (MPMarshalError){ MPMarshalErrorMissing, "Missing full name." };
return false; return false;
} }
if (!user->masterPassword || !strlen( user->masterPassword )) { if (!user->masterPassword || !strlen( user->masterPassword )) {
*error = (MPMarshallError){ MPMarshallErrorMasterPassword, "Missing master password." }; *error = (MPMarshalError){ MPMarshalErrorMasterPassword, "Missing master password." };
return false; return false;
} }
MPMasterKey masterKey = NULL; MPMasterKey masterKey = NULL;
MPAlgorithmVersion masterKeyAlgorithm = user->algorithm - 1; MPAlgorithmVersion masterKeyAlgorithm = user->algorithm - 1;
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, user->algorithm, user->fullName, user->masterPassword )) { if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, user->algorithm, user->fullName, user->masterPassword )) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." }; *error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't derive master key." };
return false; return false;
} }
@@ -272,7 +272,7 @@ static bool mpw_marshall_write_json(
if (!user->redacted) { if (!user->redacted) {
// Clear Text // Clear Text
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, user->fullName, user->masterPassword )) { if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, user->fullName, user->masterPassword )) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." }; *error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't derive master key." };
return false; return false;
} }
@@ -284,9 +284,9 @@ static bool mpw_marshall_write_json(
else { else {
// Redacted // Redacted
if (site->type & MPSiteFeatureExportContent && site->content && strlen( site->content )) if (site->type & MPSiteFeatureExportContent && site->content && strlen( site->content ))
content = strdup( site->content ); content = mpw_strdup( site->content );
if (site->loginType & MPSiteFeatureExportContent && site->loginContent && strlen( site->loginContent )) if (site->loginType & MPSiteFeatureExportContent && site->loginContent && strlen( site->loginContent ))
loginContent = strdup( site->loginContent ); loginContent = mpw_strdup( site->loginContent );
} }
json_object *json_site = json_object_new_object(); json_object *json_site = json_object_new_object();
@@ -340,32 +340,32 @@ static bool mpw_marshall_write_json(
mpw_free( &masterKey, MPMasterKeySize ); mpw_free( &masterKey, MPMasterKeySize );
json_object_put( json_file ); json_object_put( json_file );
*error = (MPMarshallError){ .type = MPMarshallSuccess }; *error = (MPMarshalError){ .type = MPMarshalSuccess };
return true; return true;
} }
#endif #endif
bool mpw_marshall_write( bool mpw_marshal_write(
char **out, const MPMarshallFormat outFormat, const MPMarshalledUser *user, MPMarshallError *error) { char **out, const MPMarshalFormat outFormat, const MPMarshalledUser *user, MPMarshalError *error) {
switch (outFormat) { switch (outFormat) {
case MPMarshallFormatNone: case MPMarshalFormatNone:
*error = (MPMarshallError){ .type = MPMarshallSuccess }; *error = (MPMarshalError){ .type = MPMarshalSuccess };
return false; return false;
case MPMarshallFormatFlat: case MPMarshalFormatFlat:
return mpw_marshall_write_flat( out, user, error ); return mpw_marshal_write_flat( out, user, error );
#if MPW_JSON #if MPW_JSON
case MPMarshallFormatJSON: case MPMarshalFormatJSON:
return mpw_marshall_write_json( out, user, error ); return mpw_marshal_write_json( out, user, error );
#endif #endif
default: default:
*error = (MPMarshallError){ MPMarshallErrorFormat, mpw_str( "Unsupported output format: %u", outFormat ) }; *error = (MPMarshalError){ MPMarshalErrorFormat, mpw_str( "Unsupported output format: %u", outFormat ) };
return false; return false;
} }
} }
static void mpw_marshall_read_flat_info( static void mpw_marshal_read_flat_info(
const char *in, MPMarshallInfo *info) { const char *in, MPMarshalInfo *info) {
info->algorithm = MPAlgorithmVersionCurrent; info->algorithm = MPAlgorithmVersionCurrent;
@@ -397,9 +397,9 @@ static void mpw_marshall_read_flat_info(
if (strcmp( headerName, "Algorithm" ) == 0) if (strcmp( headerName, "Algorithm" ) == 0)
info->algorithm = (MPAlgorithmVersion)atoi( headerValue ); info->algorithm = (MPAlgorithmVersion)atoi( headerValue );
if (strcmp( headerName, "Full Name" ) == 0 || strcmp( headerName, "User Name" ) == 0) if (strcmp( headerName, "Full Name" ) == 0 || strcmp( headerName, "User Name" ) == 0)
info->fullName = strdup( headerValue ); info->fullName = mpw_strdup( headerValue );
if (strcmp( headerName, "Key ID" ) == 0) if (strcmp( headerName, "Key ID" ) == 0)
info->keyID = strdup( headerValue ); info->keyID = mpw_strdup( headerValue );
if (strcmp( headerName, "Passwords" ) == 0) if (strcmp( headerName, "Passwords" ) == 0)
info->redacted = strcmp( headerValue, "VISIBLE" ) != 0; info->redacted = strcmp( headerValue, "VISIBLE" ) != 0;
if (strcmp( headerName, "Date" ) == 0) if (strcmp( headerName, "Date" ) == 0)
@@ -411,12 +411,12 @@ static void mpw_marshall_read_flat_info(
} }
} }
static MPMarshalledUser *mpw_marshall_read_flat( static MPMarshalledUser *mpw_marshal_read_flat(
const char *in, const char *masterPassword, MPMarshallError *error) { const char *in, const char *masterPassword, MPMarshalError *error) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Unexpected internal error." }; *error = (MPMarshalError){ MPMarshalErrorInternal, "Unexpected internal error." };
if (!in || !strlen( in )) { if (!in || !strlen( in )) {
error->type = MPMarshallErrorStructure; error->type = MPMarshalErrorStructure;
error->description = mpw_str( "No input data." ); error->description = mpw_str( "No input data." );
return NULL; return NULL;
} }
@@ -455,23 +455,23 @@ static MPMarshalledUser *mpw_marshall_read_flat(
char *headerName = mpw_get_token( &positionInLine, endOfLine, ":\n" ); char *headerName = mpw_get_token( &positionInLine, endOfLine, ":\n" );
char *headerValue = mpw_get_token( &positionInLine, endOfLine, "\n" ); char *headerValue = mpw_get_token( &positionInLine, endOfLine, "\n" );
if (!headerName || !headerValue) { if (!headerName || !headerValue) {
error->type = MPMarshallErrorStructure; error->type = MPMarshalErrorStructure;
error->description = mpw_str( "Invalid header: %s", strndup( positionInLine, (size_t)(endOfLine - positionInLine) ) ); error->description = mpw_str( "Invalid header: %s", mpw_strndup( positionInLine, (size_t)(endOfLine - positionInLine) ) );
return NULL; return NULL;
} }
if (strcmp( headerName, "Format" ) == 0) if (strcmp( headerName, "Format" ) == 0)
format = (unsigned int)atoi( headerValue ); format = (unsigned int)atoi( headerValue );
if (strcmp( headerName, "Full Name" ) == 0 || strcmp( headerName, "User Name" ) == 0) if (strcmp( headerName, "Full Name" ) == 0 || strcmp( headerName, "User Name" ) == 0)
fullName = strdup( headerValue ); fullName = mpw_strdup( headerValue );
if (strcmp( headerName, "Avatar" ) == 0) if (strcmp( headerName, "Avatar" ) == 0)
avatar = (unsigned int)atoi( headerValue ); avatar = (unsigned int)atoi( headerValue );
if (strcmp( headerName, "Key ID" ) == 0) if (strcmp( headerName, "Key ID" ) == 0)
keyID = strdup( headerValue ); keyID = mpw_strdup( headerValue );
if (strcmp( headerName, "Algorithm" ) == 0) { if (strcmp( headerName, "Algorithm" ) == 0) {
int value = atoi( headerValue ); int value = atoi( headerValue );
if (value < MPAlgorithmVersionFirst || value > MPAlgorithmVersionLast) { if (value < MPAlgorithmVersionFirst || value > MPAlgorithmVersionLast) {
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid user algorithm version: %s", headerValue ) }; *error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user algorithm version: %s", headerValue ) };
return NULL; return NULL;
} }
algorithm = (MPAlgorithmVersion)value; algorithm = (MPAlgorithmVersion)value;
@@ -479,7 +479,7 @@ static MPMarshalledUser *mpw_marshall_read_flat(
if (strcmp( headerName, "Default Type" ) == 0) { if (strcmp( headerName, "Default Type" ) == 0) {
int value = atoi( headerValue ); int value = atoi( headerValue );
if (!mpw_nameForType( (MPResultType)value )) { if (!mpw_nameForType( (MPResultType)value )) {
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid user default type: %s", headerValue ) }; *error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user default type: %s", headerValue ) };
return NULL; return NULL;
} }
defaultType = (MPResultType)value; defaultType = (MPResultType)value;
@@ -493,7 +493,7 @@ static MPMarshalledUser *mpw_marshall_read_flat(
if (!headerEnded) if (!headerEnded)
continue; continue;
if (!fullName) { if (!fullName) {
*error = (MPMarshallError){ MPMarshallErrorMissing, "Missing header: Full Name" }; *error = (MPMarshalError){ MPMarshalErrorMissing, "Missing header: Full Name" };
return NULL; return NULL;
} }
if (positionInLine >= endOfLine) if (positionInLine >= endOfLine)
@@ -501,15 +501,15 @@ static MPMarshalledUser *mpw_marshall_read_flat(
if (!user) { if (!user) {
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, algorithm, fullName, masterPassword )) { if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, algorithm, fullName, masterPassword )) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." }; *error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't derive master key." };
return NULL; return NULL;
} }
if (keyID && !mpw_id_buf_equals( keyID, mpw_id_buf( masterKey, MPMasterKeySize ) )) { if (keyID && !mpw_id_buf_equals( keyID, mpw_id_buf( masterKey, MPMasterKeySize ) )) {
*error = (MPMarshallError){ MPMarshallErrorMasterPassword, "Master password doesn't match key ID." }; *error = (MPMarshalError){ MPMarshalErrorMasterPassword, "Master password doesn't match key ID." };
return NULL; return NULL;
} }
if (!(user = mpw_marshall_user( fullName, masterPassword, algorithm ))) { if (!(user = mpw_marshal_user( fullName, masterPassword, algorithm ))) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't allocate a new user." }; *error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't allocate a new user." };
return NULL; return NULL;
} }
@@ -527,11 +527,11 @@ static MPMarshalledUser *mpw_marshall_read_flat(
str_uses = mpw_get_token( &positionInLine, endOfLine, " \t\n" ); str_uses = mpw_get_token( &positionInLine, endOfLine, " \t\n" );
char *typeAndVersion = mpw_get_token( &positionInLine, endOfLine, " \t\n" ); char *typeAndVersion = mpw_get_token( &positionInLine, endOfLine, " \t\n" );
if (typeAndVersion) { if (typeAndVersion) {
str_type = strdup( strtok( typeAndVersion, ":" ) ); str_type = mpw_strdup( strtok( typeAndVersion, ":" ) );
str_algorithm = strdup( strtok( NULL, "" ) ); str_algorithm = mpw_strdup( strtok( NULL, "" ) );
mpw_free_string( &typeAndVersion ); mpw_free_string( &typeAndVersion );
} }
str_counter = strdup( "1" ); str_counter = mpw_strdup( "1" );
siteLoginName = NULL; siteLoginName = NULL;
siteName = mpw_get_token( &positionInLine, endOfLine, "\t\n" ); siteName = mpw_get_token( &positionInLine, endOfLine, "\t\n" );
siteContent = mpw_get_token( &positionInLine, endOfLine, "\n" ); siteContent = mpw_get_token( &positionInLine, endOfLine, "\n" );
@@ -542,9 +542,9 @@ static MPMarshalledUser *mpw_marshall_read_flat(
str_uses = mpw_get_token( &positionInLine, endOfLine, " \t\n" ); str_uses = mpw_get_token( &positionInLine, endOfLine, " \t\n" );
char *typeAndVersionAndCounter = mpw_get_token( &positionInLine, endOfLine, " \t\n" ); char *typeAndVersionAndCounter = mpw_get_token( &positionInLine, endOfLine, " \t\n" );
if (typeAndVersionAndCounter) { if (typeAndVersionAndCounter) {
str_type = strdup( strtok( typeAndVersionAndCounter, ":" ) ); str_type = mpw_strdup( strtok( typeAndVersionAndCounter, ":" ) );
str_algorithm = strdup( strtok( NULL, ":" ) ); str_algorithm = mpw_strdup( strtok( NULL, ":" ) );
str_counter = strdup( strtok( NULL, "" ) ); str_counter = mpw_strdup( strtok( NULL, "" ) );
mpw_free_string( &typeAndVersionAndCounter ); mpw_free_string( &typeAndVersionAndCounter );
} }
siteLoginName = mpw_get_token( &positionInLine, endOfLine, "\t\n" ); siteLoginName = mpw_get_token( &positionInLine, endOfLine, "\t\n" );
@@ -553,7 +553,7 @@ static MPMarshalledUser *mpw_marshall_read_flat(
break; break;
} }
default: { default: {
*error = (MPMarshallError){ MPMarshallErrorFormat, mpw_str( "Unexpected import format: %u", format ) }; *error = (MPMarshalError){ MPMarshalErrorFormat, mpw_str( "Unexpected import format: %u", format ) };
return NULL; return NULL;
} }
} }
@@ -561,31 +561,31 @@ static MPMarshalledUser *mpw_marshall_read_flat(
if (siteName && str_type && str_counter && str_algorithm && str_uses && str_lastUsed) { if (siteName && str_type && str_counter && str_algorithm && str_uses && str_lastUsed) {
MPResultType siteType = (MPResultType)atoi( str_type ); MPResultType siteType = (MPResultType)atoi( str_type );
if (!mpw_nameForType( siteType )) { if (!mpw_nameForType( siteType )) {
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid site type: %s: %s", siteName, str_type ) }; *error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site type: %s: %s", siteName, str_type ) };
return NULL; return NULL;
} }
long long int value = atoll( str_counter ); long long int value = atoll( str_counter );
if (value < MPCounterValueFirst || value > MPCounterValueLast) { if (value < MPCounterValueFirst || value > MPCounterValueLast) {
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid site counter: %s: %s", siteName, str_counter ) }; *error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site counter: %s: %s", siteName, str_counter ) };
return NULL; return NULL;
} }
MPCounterValue siteCounter = (MPCounterValue)value; MPCounterValue siteCounter = (MPCounterValue)value;
value = atoll( str_algorithm ); value = atoll( str_algorithm );
if (value < MPAlgorithmVersionFirst || value > MPAlgorithmVersionLast) { if (value < MPAlgorithmVersionFirst || value > MPAlgorithmVersionLast) {
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid site algorithm: %s: %s", siteName, str_algorithm ) }; *error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site algorithm: %s: %s", siteName, str_algorithm ) };
return NULL; return NULL;
} }
MPAlgorithmVersion siteAlgorithm = (MPAlgorithmVersion)value; MPAlgorithmVersion siteAlgorithm = (MPAlgorithmVersion)value;
time_t siteLastUsed = mpw_mktime( str_lastUsed ); time_t siteLastUsed = mpw_mktime( str_lastUsed );
if (!siteLastUsed) { if (!siteLastUsed) {
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid site last used: %s: %s", siteName, str_lastUsed ) }; *error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site last used: %s: %s", siteName, str_lastUsed ) };
return NULL; return NULL;
} }
MPMarshalledSite *site = mpw_marshall_site( MPMarshalledSite *site = mpw_marshal_site(
user, siteName, siteType, siteCounter, siteAlgorithm ); user, siteName, siteType, siteCounter, siteAlgorithm );
if (!site) { if (!site) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't allocate a new site." }; *error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't allocate a new site." };
return NULL; return NULL;
} }
@@ -594,7 +594,7 @@ static MPMarshalledUser *mpw_marshall_read_flat(
if (!user->redacted) { if (!user->redacted) {
// Clear Text // Clear Text
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, fullName, masterPassword )) { if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, fullName, masterPassword )) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." }; *error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't derive master key." };
return NULL; return NULL;
} }
@@ -608,13 +608,13 @@ static MPMarshalledUser *mpw_marshall_read_flat(
else { else {
// Redacted // Redacted
if (siteContent && strlen( siteContent )) if (siteContent && strlen( siteContent ))
site->content = strdup( siteContent ); site->content = mpw_strdup( siteContent );
if (siteLoginName && strlen( siteLoginName )) if (siteLoginName && strlen( siteLoginName ))
site->loginContent = strdup( siteLoginName ); site->loginContent = mpw_strdup( siteLoginName );
} }
} }
else { else {
error->type = MPMarshallErrorMissing; error->type = MPMarshalErrorMissing;
error->description = mpw_str( error->description = mpw_str(
"Missing one of: lastUsed=%s, uses=%s, type=%s, version=%s, counter=%s, loginName=%s, siteName=%s", "Missing one of: lastUsed=%s, uses=%s, type=%s, version=%s, counter=%s, loginName=%s, siteName=%s",
str_lastUsed, str_uses, str_type, str_algorithm, str_counter, siteLoginName, siteName ); str_lastUsed, str_uses, str_type, str_algorithm, str_counter, siteLoginName, siteName );
@@ -627,13 +627,13 @@ static MPMarshalledUser *mpw_marshall_read_flat(
mpw_free_strings( &fullName, &keyID, NULL ); mpw_free_strings( &fullName, &keyID, NULL );
mpw_free( &masterKey, MPMasterKeySize ); mpw_free( &masterKey, MPMasterKeySize );
*error = (MPMarshallError){ .type = MPMarshallSuccess }; *error = (MPMarshalError){ .type = MPMarshalSuccess };
return user; return user;
} }
#if MPW_JSON #if MPW_JSON
static void mpw_marshall_read_json_info( static void mpw_marshal_read_json_info(
const char *in, MPMarshallInfo *info) { const char *in, MPMarshalInfo *info) {
// Parse JSON. // Parse JSON.
enum json_tokener_error json_error = json_tokener_success; enum json_tokener_error json_error = json_tokener_success;
@@ -650,18 +650,18 @@ static void mpw_marshall_read_json_info(
// Section: "user" // Section: "user"
info->algorithm = (MPAlgorithmVersion)mpw_get_json_int( json_file, "user.algorithm", MPAlgorithmVersionCurrent ); info->algorithm = (MPAlgorithmVersion)mpw_get_json_int( json_file, "user.algorithm", MPAlgorithmVersionCurrent );
info->fullName = strdup( mpw_get_json_string( json_file, "user.full_name", NULL ) ); info->fullName = mpw_strdup( mpw_get_json_string( json_file, "user.full_name", NULL ) );
info->keyID = strdup( mpw_get_json_string( json_file, "user.key_id", NULL ) ); info->keyID = mpw_strdup( mpw_get_json_string( json_file, "user.key_id", NULL ) );
json_object_put( json_file ); json_object_put( json_file );
} }
static MPMarshalledUser *mpw_marshall_read_json( static MPMarshalledUser *mpw_marshal_read_json(
const char *in, const char *masterPassword, MPMarshallError *error) { const char *in, const char *masterPassword, MPMarshalError *error) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Unexpected internal error." }; *error = (MPMarshalError){ MPMarshalErrorInternal, "Unexpected internal error." };
if (!in || !strlen( in )) { if (!in || !strlen( in )) {
error->type = MPMarshallErrorStructure; error->type = MPMarshalErrorStructure;
error->description = mpw_str( "No input data." ); error->description = mpw_str( "No input data." );
return NULL; return NULL;
} }
@@ -670,7 +670,7 @@ static MPMarshalledUser *mpw_marshall_read_json(
enum json_tokener_error json_error = json_tokener_success; enum json_tokener_error json_error = json_tokener_success;
json_object *json_file = json_tokener_parse_verbose( in, &json_error ); json_object *json_file = json_tokener_parse_verbose( in, &json_error );
if (!json_file || json_error != json_tokener_success) { if (!json_file || json_error != json_tokener_success) {
*error = (MPMarshallError){ MPMarshallErrorStructure, mpw_str( "JSON error: %s", json_tokener_error_desc( json_error ) ) }; *error = (MPMarshalError){ MPMarshalErrorStructure, mpw_str( "JSON error: %s", json_tokener_error_desc( json_error ) ) };
return NULL; return NULL;
} }
@@ -682,7 +682,7 @@ static MPMarshalledUser *mpw_marshall_read_json(
// Section: "export" // Section: "export"
int64_t fileFormat = mpw_get_json_int( json_file, "export.format", 0 ); int64_t fileFormat = mpw_get_json_int( json_file, "export.format", 0 );
if (fileFormat < 1) { if (fileFormat < 1) {
*error = (MPMarshallError){ MPMarshallErrorFormat, mpw_str( "Unsupported format: %u", fileFormat ) }; *error = (MPMarshalError){ MPMarshalErrorFormat, mpw_str( "Unsupported format: %u", fileFormat ) };
return NULL; return NULL;
} }
bool fileRedacted = mpw_get_json_boolean( json_file, "export.redacted", true ); bool fileRedacted = mpw_get_json_boolean( json_file, "export.redacted", true );
@@ -694,34 +694,34 @@ static MPMarshalledUser *mpw_marshall_read_json(
const char *keyID = mpw_get_json_string( json_file, "user.key_id", NULL ); const char *keyID = mpw_get_json_string( json_file, "user.key_id", NULL );
int64_t value = mpw_get_json_int( json_file, "user.algorithm", MPAlgorithmVersionCurrent ); int64_t value = mpw_get_json_int( json_file, "user.algorithm", MPAlgorithmVersionCurrent );
if (value < MPAlgorithmVersionFirst || value > MPAlgorithmVersionLast) { if (value < MPAlgorithmVersionFirst || value > MPAlgorithmVersionLast) {
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid user algorithm version: %u", value ) }; *error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user algorithm version: %u", value ) };
return NULL; return NULL;
} }
MPAlgorithmVersion algorithm = (MPAlgorithmVersion)value; MPAlgorithmVersion algorithm = (MPAlgorithmVersion)value;
MPResultType defaultType = (MPResultType)mpw_get_json_int( json_file, "user.default_type", MPResultTypeDefault ); MPResultType defaultType = (MPResultType)mpw_get_json_int( json_file, "user.default_type", MPResultTypeDefault );
if (!mpw_nameForType( defaultType )) { if (!mpw_nameForType( defaultType )) {
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid user default type: %u", defaultType ) }; *error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user default type: %u", defaultType ) };
return NULL; return NULL;
} }
time_t lastUsed = mpw_mktime( str_lastUsed ); time_t lastUsed = mpw_mktime( str_lastUsed );
if (!lastUsed) { if (!lastUsed) {
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid user last used: %s", str_lastUsed ) }; *error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user last used: %s", str_lastUsed ) };
return NULL; return NULL;
} }
if (!fullName || !strlen( fullName )) { if (!fullName || !strlen( fullName )) {
*error = (MPMarshallError){ MPMarshallErrorMissing, "Missing value for full name." }; *error = (MPMarshalError){ MPMarshalErrorMissing, "Missing value for full name." };
return NULL; return NULL;
} }
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, algorithm, fullName, masterPassword )) { if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, algorithm, fullName, masterPassword )) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." }; *error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't derive master key." };
return NULL; return NULL;
} }
if (keyID && !mpw_id_buf_equals( keyID, mpw_id_buf( masterKey, MPMasterKeySize ) )) { if (keyID && !mpw_id_buf_equals( keyID, mpw_id_buf( masterKey, MPMasterKeySize ) )) {
*error = (MPMarshallError){ MPMarshallErrorMasterPassword, "Master password doesn't match key ID." }; *error = (MPMarshalError){ MPMarshalErrorMasterPassword, "Master password doesn't match key ID." };
return NULL; return NULL;
} }
if (!(user = mpw_marshall_user( fullName, masterPassword, algorithm ))) { if (!(user = mpw_marshal_user( fullName, masterPassword, algorithm ))) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't allocate a new user." }; *error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't allocate a new user." };
return NULL; return NULL;
} }
user->redacted = fileRedacted; user->redacted = fileRedacted;
@@ -736,18 +736,18 @@ static MPMarshalledUser *mpw_marshall_read_json(
const char *siteName = json_site.key; const char *siteName = json_site.key;
value = mpw_get_json_int( json_site.val, "algorithm", (int32_t)user->algorithm ); value = mpw_get_json_int( json_site.val, "algorithm", (int32_t)user->algorithm );
if (value < MPAlgorithmVersionFirst || value > MPAlgorithmVersionLast) { if (value < MPAlgorithmVersionFirst || value > MPAlgorithmVersionLast) {
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid site algorithm version: %s: %d", siteName, value ) }; *error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site algorithm version: %s: %d", siteName, value ) };
return NULL; return NULL;
} }
MPAlgorithmVersion siteAlgorithm = (MPAlgorithmVersion)value; MPAlgorithmVersion siteAlgorithm = (MPAlgorithmVersion)value;
MPResultType siteType = (MPResultType)mpw_get_json_int( json_site.val, "type", (int32_t)user->defaultType ); MPResultType siteType = (MPResultType)mpw_get_json_int( json_site.val, "type", (int32_t)user->defaultType );
if (!mpw_nameForType( siteType )) { if (!mpw_nameForType( siteType )) {
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid site type: %s: %u", siteName, siteType ) }; *error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site type: %s: %u", siteName, siteType ) };
return NULL; return NULL;
} }
value = mpw_get_json_int( json_site.val, "counter", 1 ); value = mpw_get_json_int( json_site.val, "counter", 1 );
if (value < MPCounterValueFirst || value > MPCounterValueLast) { if (value < MPCounterValueFirst || value > MPCounterValueLast) {
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid site counter: %s: %d", siteName, value ) }; *error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site counter: %s: %d", siteName, value ) };
return NULL; return NULL;
} }
MPCounterValue siteCounter = (MPCounterValue)value; MPCounterValue siteCounter = (MPCounterValue)value;
@@ -758,27 +758,27 @@ static MPMarshalledUser *mpw_marshall_read_json(
str_lastUsed = mpw_get_json_string( json_site.val, "last_used", NULL ); str_lastUsed = mpw_get_json_string( json_site.val, "last_used", NULL );
time_t siteLastUsed = mpw_mktime( str_lastUsed ); time_t siteLastUsed = mpw_mktime( str_lastUsed );
if (!siteLastUsed) { if (!siteLastUsed) {
*error = (MPMarshallError){ MPMarshallErrorIllegal, mpw_str( "Invalid site last used: %s: %s", siteName, str_lastUsed ) }; *error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site last used: %s: %s", siteName, str_lastUsed ) };
return NULL; return NULL;
} }
json_object *json_site_mpw = mpw_get_json_section( json_site.val, "_ext_mpw" ); json_object *json_site_mpw = mpw_get_json_section( json_site.val, "_ext_mpw" );
const char *siteURL = mpw_get_json_string( json_site_mpw, "url", NULL ); const char *siteURL = mpw_get_json_string( json_site_mpw, "url", NULL );
MPMarshalledSite *site = mpw_marshall_site( user, siteName, siteType, siteCounter, siteAlgorithm ); MPMarshalledSite *site = mpw_marshal_site( user, siteName, siteType, siteCounter, siteAlgorithm );
if (!site) { if (!site) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't allocate a new site." }; *error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't allocate a new site." };
return NULL; return NULL;
} }
site->loginType = siteLoginType; site->loginType = siteLoginType;
site->url = siteURL? strdup( siteURL ): NULL; site->url = siteURL? mpw_strdup( siteURL ): NULL;
site->uses = siteUses; site->uses = siteUses;
site->lastUsed = siteLastUsed; site->lastUsed = siteLastUsed;
if (!user->redacted) { if (!user->redacted) {
// Clear Text // Clear Text
if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, fullName, masterPassword )) { if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, fullName, masterPassword )) {
*error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." }; *error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't derive master key." };
return NULL; return NULL;
} }
@@ -792,9 +792,9 @@ static MPMarshalledUser *mpw_marshall_read_json(
else { else {
// Redacted // Redacted
if (siteContent && strlen( siteContent )) if (siteContent && strlen( siteContent ))
site->content = strdup( siteContent ); site->content = mpw_strdup( siteContent );
if (siteLoginName && strlen( siteLoginName )) if (siteLoginName && strlen( siteLoginName ))
site->loginContent = strdup( siteLoginName ); site->loginContent = mpw_strdup( siteLoginName );
} }
json_object_iter json_site_question; json_object_iter json_site_question;
@@ -813,32 +813,32 @@ static MPMarshalledUser *mpw_marshall_read_json(
else { else {
// Redacted // Redacted
if (answerContent && strlen( answerContent )) if (answerContent && strlen( answerContent ))
question->content = strdup( answerContent ); question->content = mpw_strdup( answerContent );
} }
} }
} }
json_object_put( json_file ); json_object_put( json_file );
*error = (MPMarshallError){ .type = MPMarshallSuccess }; *error = (MPMarshalError){ .type = MPMarshalSuccess };
return user; return user;
} }
#endif #endif
MPMarshallInfo *mpw_marshall_read_info( MPMarshalInfo *mpw_marshal_read_info(
const char *in) { const char *in) {
MPMarshallInfo *info = malloc( sizeof( MPMarshallInfo ) ); MPMarshalInfo *info = malloc( sizeof( MPMarshalInfo ) );
*info = (MPMarshallInfo){ .format = MPMarshallFormatNone }; *info = (MPMarshalInfo){ .format = MPMarshalFormatNone };
if (in && strlen( in )) { if (in && strlen( in )) {
if (in[0] == '#') { if (in[0] == '#') {
*info = (MPMarshallInfo){ .format = MPMarshallFormatFlat }; *info = (MPMarshalInfo){ .format = MPMarshalFormatFlat };
mpw_marshall_read_flat_info( in, info ); mpw_marshal_read_flat_info( in, info );
} }
else if (in[0] == '{') { else if (in[0] == '{') {
*info = (MPMarshallInfo){ .format = MPMarshallFormatJSON }; *info = (MPMarshalInfo){ .format = MPMarshalFormatJSON };
#if MPW_JSON #if MPW_JSON
mpw_marshall_read_json_info( in, info ); mpw_marshal_read_json_info( in, info );
#endif #endif
} }
} }
@@ -846,30 +846,30 @@ MPMarshallInfo *mpw_marshall_read_info(
return info; return info;
} }
MPMarshalledUser *mpw_marshall_read( MPMarshalledUser *mpw_marshal_read(
const char *in, const MPMarshallFormat inFormat, const char *masterPassword, MPMarshallError *error) { const char *in, const MPMarshalFormat inFormat, const char *masterPassword, MPMarshalError *error) {
switch (inFormat) { switch (inFormat) {
case MPMarshallFormatNone: case MPMarshalFormatNone:
*error = (MPMarshallError){ .type = MPMarshallSuccess }; *error = (MPMarshalError){ .type = MPMarshalSuccess };
return false; return false;
case MPMarshallFormatFlat: case MPMarshalFormatFlat:
return mpw_marshall_read_flat( in, masterPassword, error ); return mpw_marshal_read_flat( in, masterPassword, error );
#if MPW_JSON #if MPW_JSON
case MPMarshallFormatJSON: case MPMarshalFormatJSON:
return mpw_marshall_read_json( in, masterPassword, error ); return mpw_marshal_read_json( in, masterPassword, error );
#endif #endif
default: default:
*error = (MPMarshallError){ MPMarshallErrorFormat, mpw_str( "Unsupported input format: %u", inFormat ) }; *error = (MPMarshalError){ MPMarshalErrorFormat, mpw_str( "Unsupported input format: %u", inFormat ) };
return NULL; return NULL;
} }
} }
const MPMarshallFormat mpw_formatWithName( const MPMarshalFormat mpw_formatWithName(
const char *formatName) { const char *formatName) {
if (!formatName || !strlen( formatName )) if (!formatName || !strlen( formatName ))
return MPMarshallFormatNone; return MPMarshalFormatNone;
// Lower-case to standardize it. // Lower-case to standardize it.
size_t stdFormatNameSize = strlen( formatName ); size_t stdFormatNameSize = strlen( formatName );
@@ -878,26 +878,26 @@ const MPMarshallFormat mpw_formatWithName(
stdFormatName[c] = (char)tolower( formatName[c] ); stdFormatName[c] = (char)tolower( formatName[c] );
stdFormatName[stdFormatNameSize] = '\0'; stdFormatName[stdFormatNameSize] = '\0';
if (strncmp( mpw_nameForFormat( MPMarshallFormatNone ), stdFormatName, strlen( stdFormatName ) ) == 0) if (strncmp( mpw_nameForFormat( MPMarshalFormatNone ), stdFormatName, strlen( stdFormatName ) ) == 0)
return MPMarshallFormatNone; return MPMarshalFormatNone;
if (strncmp( mpw_nameForFormat( MPMarshallFormatFlat ), stdFormatName, strlen( stdFormatName ) ) == 0) if (strncmp( mpw_nameForFormat( MPMarshalFormatFlat ), stdFormatName, strlen( stdFormatName ) ) == 0)
return MPMarshallFormatFlat; return MPMarshalFormatFlat;
if (strncmp( mpw_nameForFormat( MPMarshallFormatJSON ), stdFormatName, strlen( stdFormatName ) ) == 0) if (strncmp( mpw_nameForFormat( MPMarshalFormatJSON ), stdFormatName, strlen( stdFormatName ) ) == 0)
return MPMarshallFormatJSON; return MPMarshalFormatJSON;
dbg( "Not a format name: %s\n", stdFormatName ); dbg( "Not a format name: %s\n", stdFormatName );
return (MPMarshallFormat)ERR; return (MPMarshalFormat)ERR;
} }
const char *mpw_nameForFormat( const char *mpw_nameForFormat(
const MPMarshallFormat format) { const MPMarshalFormat format) {
switch (format) { switch (format) {
case MPMarshallFormatNone: case MPMarshalFormatNone:
return "none"; return "none";
case MPMarshallFormatFlat: case MPMarshalFormatFlat:
return "flat"; return "flat";
case MPMarshallFormatJSON: case MPMarshalFormatJSON:
return "json"; return "json";
default: { default: {
dbg( "Unknown format: %d\n", format ); dbg( "Unknown format: %d\n", format );
@@ -906,15 +906,15 @@ const char *mpw_nameForFormat(
} }
} }
const char *mpw_marshall_format_extension( const char *mpw_marshal_format_extension(
const MPMarshallFormat format) { const MPMarshalFormat format) {
switch (format) { switch (format) {
case MPMarshallFormatNone: case MPMarshalFormatNone:
return NULL; return NULL;
case MPMarshallFormatFlat: case MPMarshalFormatFlat:
return "mpsites"; return "mpsites";
case MPMarshallFormatJSON: case MPMarshalFormatJSON:
return "mpsites.json"; return "mpsites.json";
default: { default: {
dbg( "Unknown format: %d\n", format ); dbg( "Unknown format: %d\n", format );

View File

@@ -16,8 +16,8 @@
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>. // LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//============================================================================== //==============================================================================
#ifndef _MPW_MARSHALL_H #ifndef _MPW_MARSHAL_H
#define _MPW_MARSHALL_H #define _MPW_MARSHAL_H
#include <time.h> #include <time.h>
@@ -25,41 +25,41 @@
//// Types. //// Types.
typedef mpw_enum( unsigned int, MPMarshallFormat ) { typedef mpw_enum( unsigned int, MPMarshalFormat ) {
/** Generate a key for authentication. */ /** Do not marshal. */
MPMarshallFormatNone, MPMarshalFormatNone,
/** Generate a key for authentication. */ /** Marshal using the line-based plain-text format. */
MPMarshallFormatFlat, MPMarshalFormatFlat,
/** Generate a name for identification. */ /** Marshal using the JSON structured format. */
MPMarshallFormatJSON, MPMarshalFormatJSON,
#if MPW_JSON #if MPW_JSON
MPMarshallFormatDefault = MPMarshallFormatJSON, MPMarshalFormatDefault = MPMarshalFormatJSON,
#else #else
MPMarshallFormatDefault = MPMarshallFormatFlat, MPMarshalFormatDefault = MPMarshalFormatFlat,
#endif #endif
}; };
typedef mpw_enum( unsigned int, MPMarshallErrorType ) { typedef mpw_enum( unsigned int, MPMarshalErrorType ) {
/** The marshalling operation completed successfully. */ /** The marshalling operation completed successfully. */
MPMarshallSuccess, MPMarshalSuccess,
/** An error in the structure of the marshall file interrupted marshalling. */ /** An error in the structure of the marshall file interrupted marshalling. */
MPMarshallErrorStructure, MPMarshalErrorStructure,
/** The marshall file uses an unsupported format version. */ /** The marshall file uses an unsupported format version. */
MPMarshallErrorFormat, MPMarshalErrorFormat,
/** A required value is missing or not specified. */ /** A required value is missing or not specified. */
MPMarshallErrorMissing, MPMarshalErrorMissing,
/** The given master password is not valid. */ /** The given master password is not valid. */
MPMarshallErrorMasterPassword, MPMarshalErrorMasterPassword,
/** An illegal value was specified. */ /** An illegal value was specified. */
MPMarshallErrorIllegal, MPMarshalErrorIllegal,
/** An internal system error interrupted marshalling. */ /** An internal system error interrupted marshalling. */
MPMarshallErrorInternal, MPMarshalErrorInternal,
}; };
typedef struct MPMarshallError { typedef struct MPMarshalError {
MPMarshallErrorType type; MPMarshalErrorType type;
const char *description; const char *description;
} MPMarshallError; } MPMarshalError;
typedef struct MPMarshalledQuestion { typedef struct MPMarshalledQuestion {
const char *keyword; const char *keyword;
@@ -99,34 +99,34 @@ typedef struct MPMarshalledUser {
MPMarshalledSite *sites; MPMarshalledSite *sites;
} MPMarshalledUser; } MPMarshalledUser;
typedef struct MPMarshallInfo { typedef struct MPMarshalInfo {
MPMarshallFormat format; MPMarshalFormat format;
MPAlgorithmVersion algorithm; MPAlgorithmVersion algorithm;
const char *fullName; const char *fullName;
const char *keyID; const char *keyID;
bool redacted; bool redacted;
time_t date; time_t date;
} MPMarshallInfo; } MPMarshalInfo;
//// Marshalling. //// Marshalling.
/** Write the user and all associated data out to the given output buffer using the given marshalling format. */ /** Write the user and all associated data out to the given output buffer using the given marshalling format. */
bool mpw_marshall_write( bool mpw_marshal_write(
char **out, const MPMarshallFormat outFormat, const MPMarshalledUser *user, MPMarshallError *error); char **out, const MPMarshalFormat outFormat, const MPMarshalledUser *user, MPMarshalError *error);
/** Try to read metadata on the sites in the input buffer. */ /** Try to read metadata on the sites in the input buffer. */
MPMarshallInfo *mpw_marshall_read_info( MPMarshalInfo *mpw_marshal_read_info(
const char *in); const char *in);
/** Unmarshall sites in the given input buffer by parsing it using the given marshalling format. */ /** Unmarshall sites in the given input buffer by parsing it using the given marshalling format. */
MPMarshalledUser *mpw_marshall_read( MPMarshalledUser *mpw_marshal_read(
const char *in, const MPMarshallFormat inFormat, const char *masterPassword, MPMarshallError *error); const char *in, const MPMarshalFormat inFormat, const char *masterPassword, MPMarshalError *error);
//// Utilities. //// Utilities.
/** Create a new user object ready for marshalling. */ /** Create a new user object ready for marshalling. */
MPMarshalledUser *mpw_marshall_user( MPMarshalledUser *mpw_marshal_user(
const char *fullName, const char *masterPassword, const MPAlgorithmVersion algorithmVersion); const char *fullName, const char *masterPassword, const MPAlgorithmVersion algorithmVersion);
/** Create a new site attached to the given user object, ready for marshalling. */ /** Create a new site attached to the given user object, ready for marshalling. */
MPMarshalledSite *mpw_marshall_site( MPMarshalledSite *mpw_marshal_site(
MPMarshalledUser *user, MPMarshalledUser *user,
const char *siteName, const MPResultType resultType, const MPCounterValue siteCounter, const MPAlgorithmVersion algorithmVersion); const char *siteName, const MPResultType resultType, const MPCounterValue siteCounter, const MPAlgorithmVersion algorithmVersion);
/** Create a new question attached to the given site object, ready for marshalling. */ /** Create a new question attached to the given site object, ready for marshalling. */
@@ -134,7 +134,7 @@ MPMarshalledQuestion *mpw_marshal_question(
MPMarshalledSite *site, const char *keyword); MPMarshalledSite *site, const char *keyword);
/** Free the given user object and all associated data. */ /** Free the given user object and all associated data. */
bool mpw_marshal_info_free( bool mpw_marshal_info_free(
MPMarshallInfo **info); MPMarshalInfo **info);
bool mpw_marshal_free( bool mpw_marshal_free(
MPMarshalledUser **user); MPMarshalledUser **user);
@@ -143,17 +143,17 @@ bool mpw_marshal_free(
/** /**
* @return The purpose represented by the given name. * @return The purpose represented by the given name.
*/ */
const MPMarshallFormat mpw_formatWithName( const MPMarshalFormat mpw_formatWithName(
const char *formatName); const char *formatName);
/** /**
* @return The standard name for the given purpose. * @return The standard name for the given purpose.
*/ */
const char *mpw_nameForFormat( const char *mpw_nameForFormat(
const MPMarshallFormat format); const MPMarshalFormat format);
/** /**
* @return The file extension that's recommended for files that use the given marshalling format. * @return The file extension that's recommended for files that use the given marshalling format.
*/ */
const char *mpw_marshall_format_extension( const char *mpw_marshal_format_extension(
const MPMarshallFormat format); const MPMarshalFormat format);
#endif // _MPW_MARSHALL_H #endif // _MPW_MARSHAL_H

View File

@@ -40,22 +40,21 @@ const MPResultType mpw_typeWithName(const char *typeName) {
return MPResultTypeTemplatePIN; return MPResultTypeTemplatePIN;
if ('n' == typeName[0]) if ('n' == typeName[0])
return MPResultTypeTemplateName; return MPResultTypeTemplateName;
if ('p' == typeName[0])
return MPResultTypeTemplatePhrase;
if ('P' == typeName[0]) if ('P' == typeName[0])
return MPResultTypeStatefulPersonal; return MPResultTypeStatefulPersonal;
if ('D' == typeName[0]) if ('D' == typeName[0])
return MPResultTypeStatefulDevice; return MPResultTypeStatefulDevice;
if ('k' == typeName[0]) if ('K' == typeName[0])
return MPResultTypeDeriveKey; return MPResultTypeDeriveKey;
} }
// Lower-case and trim optionally leading "Generated" string from typeName to standardize it. // Lower-case typeName to standardize it.
size_t stdTypeNameOffset = 0;
size_t stdTypeNameSize = strlen( typeName ); size_t stdTypeNameSize = strlen( typeName );
if (strstr( typeName, "Generated" ) == typeName)
stdTypeNameSize -= (stdTypeNameOffset = strlen( "Generated" ));
char stdTypeName[stdTypeNameSize + 1]; char stdTypeName[stdTypeNameSize + 1];
for (size_t c = 0; c < stdTypeNameSize; ++c) for (size_t c = 0; c < stdTypeNameSize; ++c)
stdTypeName[c] = (char)tolower( typeName[c + stdTypeNameOffset] ); stdTypeName[c] = (char)tolower( typeName[c] );
stdTypeName[stdTypeNameSize] = '\0'; stdTypeName[stdTypeNameSize] = '\0';
// Find what password type is represented by the type name. // Find what password type is represented by the type name.
@@ -263,3 +262,40 @@ const char mpw_characterFromClass(char characterClass, uint8_t seedByte) {
return classCharacters[seedByte % strlen( classCharacters )]; return classCharacters[seedByte % strlen( classCharacters )];
} }
MPIdenticon mpw_identicon(const char *fullName, const char *masterPassword) {
const char *leftArm[] = { "", "", "", "" };
const char *rightArm[] = { "", "", "", "" };
const char *body[] = { "", "", "", "", "", "" };
const char *accessory[] = {
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""
};
const uint8_t *identiconSeed = NULL;
if (fullName && strlen( fullName ) && masterPassword && strlen( masterPassword ))
identiconSeed = mpw_hash_hmac_sha256(
(const uint8_t *)masterPassword, strlen( masterPassword ),
(const uint8_t *)fullName, strlen( fullName ) );
if (!identiconSeed)
return (MPIdenticon){
.leftArm = "",
.body = "",
.rightArm = "",
.accessory = "",
.color=0,
};
MPIdenticon identicon = {
.leftArm = leftArm[identiconSeed[0] % (sizeof( leftArm ) / sizeof( leftArm[0] ))],
.body = body[identiconSeed[1] % (sizeof( body ) / sizeof( body[0] ))],
.rightArm = rightArm[identiconSeed[2] % (sizeof( rightArm ) / sizeof( rightArm[0] ))],
.accessory = accessory[identiconSeed[3] % (sizeof( accessory ) / sizeof( accessory[0] ))],
.color = (uint8_t)(identiconSeed[4] % 7 + 1),
};
mpw_free( &identiconSeed, 32 );
return identicon;
}

View File

@@ -111,6 +111,14 @@ typedef mpw_enum ( uint32_t, MPCounterValue ) {
MPCounterValueLast = UINT32_MAX, MPCounterValueLast = UINT32_MAX,
}; };
typedef struct {
const char *leftArm;
const char *body;
const char *rightArm;
const char *accessory;
uint8_t color;
} MPIdenticon;
//// Type utilities. //// Type utilities.
/** /**
@@ -157,4 +165,7 @@ const char *mpw_charactersInClass(char characterClass);
*/ */
const char mpw_characterFromClass(char characterClass, uint8_t seedByte); const char mpw_characterFromClass(char characterClass, uint8_t seedByte);
/** @return A fingerprint for a user. */
MPIdenticon mpw_identicon(const char *fullName, const char *masterPassword);
#endif // _MPW_TYPES_H #endif // _MPW_TYPES_H

View File

@@ -20,18 +20,15 @@
#include <ctype.h> #include <ctype.h>
#include <errno.h> #include <errno.h>
#if MPW_COLOR
#include <unistd.h>
#include <curses.h>
#include <term.h>
#endif
#if MPW_CPERCIVA #if MPW_CPERCIVA
#include <scrypt/crypto_scrypt.h> #include <scrypt/crypto_scrypt.h>
#include <scrypt/sha256.h> #include <scrypt/sha256.h>
#elif MPW_SODIUM #elif MPW_SODIUM
#include "sodium.h" #include "sodium.h"
#endif #endif
#define AES_ECB 0
#define AES_CBC 1
#include "aes.h"
#include "mpw-util.h" #include "mpw-util.h"
@@ -134,31 +131,38 @@ bool __mpw_realloc(const void **buffer, size_t *bufferSize, const size_t deltaSi
return true; return true;
} }
bool __mpw_free(const void **buffer, const size_t bufferSize) { void mpw_zero(void *buffer, size_t bufferSize) {
uint8_t *b = buffer;
for (; bufferSize > 0; --bufferSize)
*b++ = 0;
}
bool __mpw_free(void **buffer, const size_t bufferSize) {
if (!buffer || !*buffer) if (!buffer || !*buffer)
return false; return false;
memset( (void *)*buffer, 0, bufferSize ); mpw_zero( *buffer, bufferSize );
free( (void *)*buffer ); free( *buffer );
*buffer = NULL; *buffer = NULL;
return true; return true;
} }
bool __mpw_free_string(const char **string) { bool __mpw_free_string(char **string) {
return *string && __mpw_free( (const void **)string, strlen( *string ) ); return *string && __mpw_free( (void **)string, strlen( *string ) );
} }
bool __mpw_free_strings(const char **strings, ...) { bool __mpw_free_strings(char **strings, ...) {
bool success = true; bool success = true;
va_list args; va_list args;
va_start( args, strings ); va_start( args, strings );
success &= mpw_free_string( strings ); success &= mpw_free_string( strings );
for (const char **string; (string = va_arg( args, const char ** ));) for (char **string; (string = va_arg( args, char ** ));)
success &= mpw_free_string( string ); success &= mpw_free_string( string );
va_end( args ); va_end( args );
@@ -207,7 +211,6 @@ uint8_t const *mpw_kdf_blake2b(const size_t subkeySize, const uint8_t *key, cons
#if MPW_SODIUM #if MPW_SODIUM
if (keySize < crypto_generichash_blake2b_KEYBYTES_MIN || keySize > crypto_generichash_blake2b_KEYBYTES_MAX || if (keySize < crypto_generichash_blake2b_KEYBYTES_MIN || keySize > crypto_generichash_blake2b_KEYBYTES_MAX ||
subkeySize < crypto_generichash_blake2b_KEYBYTES_MIN || subkeySize > crypto_generichash_blake2b_KEYBYTES_MAX || subkeySize < crypto_generichash_blake2b_KEYBYTES_MIN || subkeySize > crypto_generichash_blake2b_KEYBYTES_MAX ||
contextSize < crypto_generichash_blake2b_BYTES_MIN || contextSize > crypto_generichash_blake2b_BYTES_MAX ||
(personal && strlen( personal ) > crypto_generichash_blake2b_PERSONALBYTES)) { (personal && strlen( personal ) > crypto_generichash_blake2b_PERSONALBYTES)) {
errno = EINVAL; errno = EINVAL;
free( subkey ); free( subkey );
@@ -215,12 +218,12 @@ uint8_t const *mpw_kdf_blake2b(const size_t subkeySize, const uint8_t *key, cons
} }
uint8_t saltBuf[crypto_generichash_blake2b_SALTBYTES]; uint8_t saltBuf[crypto_generichash_blake2b_SALTBYTES];
bzero( saltBuf, sizeof saltBuf ); mpw_zero( saltBuf, sizeof saltBuf );
if (id) if (id)
mpw_uint64( id, saltBuf ); mpw_uint64( id, saltBuf );
uint8_t personalBuf[crypto_generichash_blake2b_PERSONALBYTES]; uint8_t personalBuf[crypto_generichash_blake2b_PERSONALBYTES];
bzero( personalBuf, sizeof saltBuf ); mpw_zero( personalBuf, sizeof personalBuf );
if (personal && strlen( personal )) if (personal && strlen( personal ))
memcpy( personalBuf, personal, strlen( personal ) ); memcpy( personalBuf, personal, strlen( personal ) );
@@ -265,50 +268,47 @@ uint8_t const *mpw_hash_hmac_sha256(const uint8_t *key, const size_t keySize, co
return mac; return mac;
} }
static uint8_t const *mpw_aes(bool encrypt, const uint8_t *key, const size_t keySize, const uint8_t *buf, const size_t bufSize) { // We do our best to not fail on odd buf's, eg. non-padded cipher texts.
static uint8_t const *mpw_aes(bool encrypt, const uint8_t *key, const size_t keySize, const uint8_t *buf, size_t *bufSize) {
#if MPW_SODIUM if (!key || keySize < 16 || !*bufSize)
if (!key || keySize < crypto_stream_KEYBYTES)
return NULL; return NULL;
uint8_t nonce[crypto_stream_NONCEBYTES]; // IV = zero
bzero( (void *)nonce, sizeof( nonce ) ); uint8_t iv[16];
mpw_zero( iv, sizeof iv );
#pragma GCC diagnostic push // Add PKCS#7 padding
#pragma GCC diagnostic ignored "-Wdeprecated-declarations" uint32_t aesSize = ((uint32_t)*bufSize + 15 / 16) * 16; // round up to block size.
#pragma clang diagnostic push if (encrypt && !(*bufSize % 16)) // add pad block if plain text fits block size.
#pragma clang diagnostic ignored "-Wdeprecated-declarations" encrypt += 16;
if (encrypt) { uint8_t aesBuf[aesSize];
uint8_t *const cipherBuf = malloc( bufSize ); memcpy( aesBuf, buf, *bufSize );
if (crypto_stream_aes128ctr_xor( cipherBuf, buf, bufSize, nonce, key ) != 0) { memset( aesBuf + *bufSize, aesSize - *bufSize, aesSize - *bufSize );
mpw_free( &cipherBuf, bufSize ); uint8_t *resultBuf = malloc( aesSize );
return NULL;
} if (encrypt)
return cipherBuf; AES_CBC_encrypt_buffer( resultBuf, aesBuf, aesSize, key, iv );
} else
else { AES_CBC_decrypt_buffer( resultBuf, aesBuf, aesSize, key, iv );
uint8_t *const plainBuf = malloc( bufSize ); mpw_zero( aesBuf, aesSize );
if (crypto_stream_aes128ctr( plainBuf, bufSize, nonce, key ) != 0) { mpw_zero( iv, 16 );
mpw_free( &plainBuf, bufSize );
return NULL; // Truncate PKCS#7 padding
} if (encrypt)
for (size_t c = 0; c < bufSize; ++c) *bufSize = aesSize;
plainBuf[c] = buf[c] ^ plainBuf[c]; else if (*bufSize % 16 == 0 && resultBuf[aesSize - 1] < 16)
return plainBuf; *bufSize -= resultBuf[aesSize - 1];
}
#pragma clang diagnostic pop return resultBuf;
#pragma GCC diagnostic pop
#else
#error No crypto support for mpw_aes.
#endif
} }
uint8_t const *mpw_aes_encrypt(const uint8_t *key, const size_t keySize, const uint8_t *plainBuf, const size_t bufSize) { uint8_t const *mpw_aes_encrypt(const uint8_t *key, const size_t keySize, const uint8_t *plainBuf, size_t *bufSize) {
return mpw_aes( true, key, keySize, plainBuf, bufSize ); return mpw_aes( true, key, keySize, plainBuf, bufSize );
} }
uint8_t const *mpw_aes_decrypt(const uint8_t *key, const size_t keySize, const uint8_t *cipherBuf, const size_t bufSize) { uint8_t const *mpw_aes_decrypt(const uint8_t *key, const size_t keySize, const uint8_t *cipherBuf, size_t *bufSize) {
return mpw_aes( false, key, keySize, cipherBuf, bufSize ); return mpw_aes( false, key, keySize, cipherBuf, bufSize );
} }
@@ -338,7 +338,7 @@ const char *mpw_hotp(const uint8_t *key, size_t keySize, uint64_t movingFactor,
// Render the OTP as `digits` decimal digits. // Render the OTP as `digits` decimal digits.
otp %= (int)pow(10, digits); otp %= (int)pow(10, digits);
return strdup( mpw_str( "%0*d", digits, otp ) ); return mpw_strdup( mpw_str( "%0*d", digits, otp ) );
} }
#endif #endif
@@ -438,99 +438,6 @@ const char *mpw_hex_l(uint32_t number) {
return mpw_hex( &buf, sizeof( buf ) ); return mpw_hex( &buf, sizeof( buf ) );
} }
#if MPW_COLOR
static char *str_tputs;
static int str_tputs_cursor;
static const int str_tputs_max = 256;
static bool mpw_setupterm() {
if (!isatty( STDERR_FILENO ))
return false;
static bool termsetup;
if (!termsetup) {
int errret;
if (!(termsetup = (setupterm( NULL, STDERR_FILENO, &errret ) == OK))) {
wrn( "Terminal doesn't support color (setupterm errret %d).\n", errret );
return false;
}
}
return true;
}
static int mpw_tputc(int c) {
if (++str_tputs_cursor < str_tputs_max) {
str_tputs[str_tputs_cursor] = (char)c;
return OK;
}
return ERR;
}
static char *mpw_tputs(const char *str, int affcnt) {
if (str_tputs)
mpw_free( &str_tputs, str_tputs_max );
str_tputs = calloc( str_tputs_max, sizeof( char ) );
str_tputs_cursor = -1;
char *result = tputs( str, affcnt, mpw_tputc ) == ERR? NULL: strndup( str_tputs, str_tputs_max );
if (str_tputs)
mpw_free( &str_tputs, str_tputs_max );
return result;
}
#endif
const char *mpw_identicon(const char *fullName, const char *masterPassword) {
const char *leftArm[] = { "", "", "", "" };
const char *rightArm[] = { "", "", "", "" };
const char *body[] = { "", "", "", "", "", "" };
const char *accessory[] = {
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""
};
const uint8_t *identiconSeed = mpw_hash_hmac_sha256(
(const uint8_t *)masterPassword, strlen( masterPassword ),
(const uint8_t *)fullName, strlen( fullName ) );
if (!identiconSeed)
return NULL;
char *colorString, *resetString;
#ifdef MPW_COLOR
if (mpw_setupterm()) {
uint8_t colorIdentifier = (uint8_t)(identiconSeed[4] % 7 + 1);
colorString = mpw_tputs( tparm( tgetstr( "AF", NULL ), colorIdentifier ), 1 );
resetString = mpw_tputs( tgetstr( "me", NULL ), 1 );
}
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 );
mpw_free( &identiconSeed, 32 );
mpw_free_strings( &colorString, &resetString, NULL );
return identicon;
}
/** /**
* @return the amount of bytes used by UTF-8 to encode a single character that starts with the given byte. * @return the amount of bytes used by UTF-8 to encode a single character that starts with the given byte.
*/ */
@@ -561,3 +468,31 @@ const size_t mpw_utf8_strlen(const char *utf8String) {
return charlen; return charlen;
} }
char *mpw_strdup(const char *src) {
if (!src)
return NULL;
size_t len = strlen( src );
char *dst = malloc( len + 1 );
memcpy( dst, src, len );
dst[len] = '\0';
return dst;
}
char *mpw_strndup(const char *src, size_t max) {
if (!src)
return NULL;
size_t len = 0;
for (; len < max && src[len] != '\0'; ++len);
char *dst = malloc( len + 1 );
memcpy( dst, src, len );
dst[len] = '\0';
return dst;
}

View File

@@ -136,23 +136,25 @@ bool mpw_push_int(
* @return true if successful, false if reallocation failed. * @return true if successful, false if reallocation failed.
*/ */
#define mpw_realloc(buffer, bufferSize, deltaSize) \ #define mpw_realloc(buffer, bufferSize, deltaSize) \
({ typeof(buffer) _b = buffer; const void *__b = *_b; (void)__b; __mpw_realloc( (const void **)_b, bufferSize, deltaSize ); }) ({ __typeof__(buffer) _b = buffer; const void *__b = *_b; (void)__b; __mpw_realloc( (const void **)_b, bufferSize, deltaSize ); })
bool __mpw_realloc(const void **buffer, size_t *bufferSize, const size_t deltaSize); bool __mpw_realloc(const void **buffer, size_t *bufferSize, const size_t deltaSize);
void mpw_zero(
void *buffer, size_t bufferSize);
/** Free a buffer after zero'ing its contents, then set the reference to NULL. */ /** Free a buffer after zero'ing its contents, then set the reference to NULL. */
#define mpw_free(buffer, bufferSize) \ #define mpw_free(buffer, bufferSize) \
({ typeof(buffer) _b = buffer; const void *__b = *_b; (void)__b; __mpw_free( (const void **)_b, bufferSize ); }) ({ __typeof__(buffer) _b = buffer; const void *__b = *_b; (void)__b; __mpw_free( (void **)_b, bufferSize ); })
bool __mpw_free( bool __mpw_free(
const void **buffer, const size_t bufferSize); void **buffer, size_t bufferSize);
/** Free a string after zero'ing its contents, then set the reference to NULL. */ /** Free a string after zero'ing its contents, then set the reference to NULL. */
#define mpw_free_string(string) \ #define mpw_free_string(string) \
({ typeof(string) _s = string; const char *__s = *_s; (void)__s; __mpw_free_string( (const char **)_s ); }) ({ __typeof__(string) _s = string; const char *__s = *_s; (void)__s; __mpw_free_string( (char **)_s ); })
bool __mpw_free_string( bool __mpw_free_string(
const char **string); char **string);
/** Free strings after zero'ing their contents, then set the references to NULL. Terminate the va_list with NULL. */ /** Free strings after zero'ing their contents, then set the references to NULL. Terminate the va_list with NULL. */
#define mpw_free_strings(strings, ...) \ #define mpw_free_strings(strings, ...) \
({ typeof(strings) _s = strings; const char *__s = *_s; (void)__s; __mpw_free_strings( (const char **)_s, __VA_ARGS__ ); }) ({ __typeof__(strings) _s = strings; const char *__s = *_s; (void)__s; __mpw_free_strings( (char **)_s, __VA_ARGS__ ); })
bool __mpw_free_strings( bool __mpw_free_strings(
const char **strings, ...); char **strings, ...);
//// Cryptographic functions. //// Cryptographic functions.
@@ -173,11 +175,11 @@ uint8_t const *mpw_hash_hmac_sha256(
/** Encrypt a plainBuf with the given key using AES-128-CBC. /** Encrypt a plainBuf with the given key using AES-128-CBC.
* @return A new bufSize allocated buffer containing the cipherBuf. */ * @return A new bufSize allocated buffer containing the cipherBuf. */
uint8_t const *mpw_aes_encrypt( uint8_t const *mpw_aes_encrypt(
const uint8_t *key, const size_t keySize, const uint8_t *plainBuf, const size_t bufSize); const uint8_t *key, const size_t keySize, const uint8_t *plainBuf, size_t *bufSize);
/** Decrypt a cipherBuf with the given key using AES-128-CBC. /** Decrypt a cipherBuf with the given key using AES-128-CBC.
* @return A new bufSize allocated buffer containing the plainBuf. */ * @return A new bufSize allocated buffer containing the plainBuf. */
uint8_t const *mpw_aes_decrypt( uint8_t const *mpw_aes_decrypt(
const uint8_t *key, const size_t keySize, const uint8_t *cipherBuf, const size_t bufSize); const uint8_t *key, const size_t keySize, const uint8_t *cipherBuf, size_t *bufSize);
/** Calculate an OTP using RFC-4226. /** Calculate an OTP using RFC-4226.
* @return A newly allocated string containing exactly `digits` decimal OTP digits. */ * @return A newly allocated string containing exactly `digits` decimal OTP digits. */
#if UNUSED #if UNUSED
@@ -201,13 +203,14 @@ MPKeyID mpw_id_buf(const void *buf, size_t length);
/** Compare two fingerprints for equality. /** Compare two fingerprints for equality.
* @return true if the buffers represent identical fingerprints. */ * @return true if the buffers represent identical fingerprints. */
bool mpw_id_buf_equals(const char *id1, const char *id2); bool mpw_id_buf_equals(const char *id1, const char *id2);
/** Encode a visual fingerprint for a user.
* @return A newly allocated string. */
const char *mpw_identicon(const char *fullName, const char *masterPassword);
//// String utilities. //// String utilities.
/** @return The amount of display characters in the given UTF-8 string. */ /** @return The amount of display characters in the given UTF-8 string. */
const size_t mpw_utf8_strlen(const char *utf8String); const size_t mpw_utf8_strlen(const char *utf8String);
/** Drop-in for POSIX strdup(3). */
char *mpw_strdup(const char *src);
/** Drop-in for POSIX strndup(3). */
char *mpw_strndup(const char *src, size_t max);
#endif // _MPW_UTIL_H #endif // _MPW_UTIL_H

View File

@@ -5,9 +5,10 @@ plugins {
description = 'Master Password Algorithm Implementation' description = 'Master Password Algorithm Implementation'
dependencies { dependencies {
compile (group: 'com.lyndir.lhunath.opal', name: 'opal-system', version: '1.6-p10') { compile (group: 'com.lyndir.lhunath.opal', name: 'opal-system', version: '1.6-p11') {
exclude( module: 'joda-time' ) exclude( module: 'joda-time' )
} }
compile group: 'com.lyndir.lhunath.opal', name: 'opal-crypto', version: '1.6-p11'
compile group: 'com.lambdaworks', name: 'scrypt', version: '1.4.0' compile group: 'com.lambdaworks', name: 'scrypt', version: '1.4.0'
compile group: 'org.jetbrains', name: 'annotations', version: '13.0' compile group: 'org.jetbrains', name: 'annotations', version: '13.0'

View File

@@ -23,7 +23,7 @@
<dependency> <dependency>
<groupId>com.lyndir.lhunath.opal</groupId> <groupId>com.lyndir.lhunath.opal</groupId>
<artifactId>opal-system</artifactId> <artifactId>opal-system</artifactId>
<version>1.6-p9</version> <version>1.6-p11</version>
<exclusions> <exclusions>
<exclusion> <exclusion>
<groupId>joda-time</groupId> <groupId>joda-time</groupId>
@@ -31,6 +31,11 @@
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<dependency>
<groupId>com.lyndir.lhunath.opal</groupId>
<artifactId>opal-crypto</artifactId>
<version>1.6-p11</version>
</dependency>
<!-- EXTERNAL DEPENDENCIES --> <!-- EXTERNAL DEPENDENCIES -->
<dependency> <dependency>

View File

@@ -0,0 +1,99 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword;
import com.google.common.base.Charsets;
import com.google.common.primitives.UnsignedInteger;
import com.lyndir.lhunath.opal.system.MessageAuthenticationDigests;
import com.lyndir.lhunath.opal.system.MessageDigests;
import java.io.Serializable;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import javax.annotation.Nullable;
/**
* @see MPMasterKey.Version
*/
public interface MPAlgorithm {
/**
* mpw: validity for the time-based rolling counter.
*/
int mpw_otp_window = 5 * 60 /* s */;
/**
* mpw: Key ID hash.
*/
MessageDigests mpw_hash = MessageDigests.SHA256;
/**
* mpw: Site digest.
*/
MessageAuthenticationDigests mpw_digest = MessageAuthenticationDigests.HmacSHA256;
/**
* mpw: Platform-agnostic byte order.
*/
ByteOrder mpw_byteOrder = ByteOrder.BIG_ENDIAN;
/**
* mpw: Input character encoding.
*/
Charset mpw_charset = Charsets.UTF_8;
/**
* mpw: Master key size (byte).
*/
int mpw_dkLen = 64;
/**
* scrypt: Parallelization parameter.
*/
int scrypt_p = 2;
/**
* scrypt: Memory cost parameter.
*/
int scrypt_r = 8;
/**
* scrypt: CPU cost parameter.
*/
int scrypt_N = 32768;
MPMasterKey.Version getAlgorithmVersion();
byte[] masterKey(String fullName, char[] masterPassword);
byte[] siteKey(byte[] masterKey, String siteName, UnsignedInteger siteCounter, MPKeyPurpose keyPurpose,
@Nullable String keyContext);
String siteResult(byte[] masterKey, final byte[] siteKey, String siteName, UnsignedInteger siteCounter, MPKeyPurpose keyPurpose,
@Nullable String keyContext, MPResultType resultType, @Nullable String resultParam);
String sitePasswordFromTemplate(byte[] masterKey, byte[] siteKey, MPResultType resultType, @Nullable String resultParam);
String sitePasswordFromCrypt(byte[] masterKey, byte[] siteKey, MPResultType resultType, @Nullable String resultParam);
String sitePasswordFromDerive(byte[] masterKey, byte[] siteKey, MPResultType resultType, @Nullable String resultParam);
String siteState(byte[] masterKey, final byte[] siteKey, String siteName, UnsignedInteger siteCounter, MPKeyPurpose keyPurpose,
@Nullable String keyContext, MPResultType resultType, String resultParam);
}

View File

@@ -0,0 +1,248 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword;
import static com.lyndir.masterpassword.MPUtils.*;
import com.google.common.base.*;
import com.google.common.primitives.Bytes;
import com.google.common.primitives.UnsignedInteger;
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 com.lyndir.lhunath.opal.system.util.ConversionUtils;
import java.nio.*;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import javax.annotation.Nullable;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
/**
* @author lhunath, 2014-08-30
* @see MPMasterKey.Version#V0
*/
public class MPAlgorithmV0 implements MPAlgorithm {
protected final Logger logger = Logger.get( getClass() );
@Override
public MPMasterKey.Version getAlgorithmVersion() {
return MPMasterKey.Version.V0;
}
@Override
public byte[] masterKey(final String fullName, final char[] masterPassword) {
byte[] fullNameBytes = fullName.getBytes( mpw_charset );
byte[] fullNameLengthBytes = bytesForInt( fullName.length() );
String keyScope = MPKeyPurpose.Authentication.getScope();
logger.trc( "keyScope: %s", keyScope );
// Calculate the master key salt.
logger.trc( "masterKeySalt: keyScope=%s | #fullName=%s | fullName=%s",
keyScope, CodeUtils.encodeHex( fullNameLengthBytes ), fullName );
byte[] masterKeySalt = Bytes.concat( keyScope.getBytes( mpw_charset ), fullNameLengthBytes, fullNameBytes );
logger.trc( " => masterKeySalt.id: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) );
// Calculate the master key.
logger.trc( "masterKey: scrypt( masterPassword, masterKeySalt, N=%d, r=%d, p=%d )",
scrypt_N, scrypt_r, scrypt_p );
byte[] masterPasswordBytes = bytesForChars( masterPassword );
byte[] masterKey = scrypt( masterKeySalt, masterPasswordBytes );
Arrays.fill( masterKeySalt, (byte) 0 );
Arrays.fill( masterPasswordBytes, (byte) 0 );
logger.trc( " => masterKey.id: %s", CodeUtils.encodeHex( idForBytes( masterKey ) ) );
return masterKey;
}
protected byte[] scrypt(final byte[] masterKeySalt, final byte[] mpBytes) {
try {
//if (isAllowNative())
return SCrypt.scrypt( mpBytes, masterKeySalt, scrypt_N, scrypt_r, scrypt_p, mpw_dkLen );
//else
// return SCrypt.scryptJ( mpBytes, masterKeySalt, scrypt_N, scrypt_r, scrypt_p, mpw_dkLen );
}
catch (final GeneralSecurityException e) {
throw logger.bug( e );
}
}
@Override
public byte[] siteKey(final byte[] masterKey, final String siteName, UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose,
@Nullable final String keyContext) {
String keyScope = keyPurpose.getScope();
logger.trc( "keyScope: %s", keyScope );
// OTP counter value.
if (siteCounter.longValue() == 0)
siteCounter = UnsignedInteger.valueOf( (System.currentTimeMillis() / (mpw_otp_window * 1000)) * mpw_otp_window );
// Calculate the site seed.
byte[] siteNameBytes = siteName.getBytes( mpw_charset );
byte[] siteNameLengthBytes = bytesForInt( siteName.length() );
byte[] siteCounterBytes = bytesForInt( siteCounter );
byte[] keyContextBytes = ((keyContext == null) || keyContext.isEmpty())? null: keyContext.getBytes( mpw_charset );
byte[] keyContextLengthBytes = (keyContextBytes == null)? null: bytesForInt( keyContextBytes.length );
logger.trc( "siteSalt: keyScope=%s | #siteName=%s | siteName=%s | siteCounter=%s | #keyContext=%s | keyContext=%s",
keyScope, CodeUtils.encodeHex( siteNameLengthBytes ), siteName, CodeUtils.encodeHex( siteCounterBytes ),
(keyContextLengthBytes == null)? null: CodeUtils.encodeHex( keyContextLengthBytes ), keyContext );
byte[] sitePasswordInfo = Bytes.concat( keyScope.getBytes( mpw_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
if (keyContextBytes != null)
sitePasswordInfo = Bytes.concat( sitePasswordInfo, keyContextLengthBytes, keyContextBytes );
logger.trc( " => siteSalt.id: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) );
logger.trc( "siteKey: hmac-sha256( masterKey.id=%s, siteSalt )", CodeUtils.encodeHex( idForBytes( masterKey ) ) );
byte[] sitePasswordSeedBytes = mpw_digest.of( masterKey, sitePasswordInfo );
logger.trc( " => siteKey.id: %s", CodeUtils.encodeHex( idForBytes( sitePasswordSeedBytes ) ) );
return sitePasswordSeedBytes;
}
@Override
public String siteResult(final byte[] masterKey, final byte[] siteKey, final String siteName, final UnsignedInteger siteCounter,
final MPKeyPurpose keyPurpose,
@Nullable final String keyContext, final MPResultType resultType, @Nullable final String resultParam) {
switch (resultType.getTypeClass()) {
case Template:
return sitePasswordFromTemplate( masterKey, siteKey, resultType, resultParam );
case Stateful:
return sitePasswordFromCrypt( masterKey, siteKey, resultType, resultParam );
case Derive:
return sitePasswordFromDerive( masterKey, siteKey, resultType, resultParam );
}
throw logger.bug( "Unsupported result type class: %s", resultType.getTypeClass() );
}
@Override
public String sitePasswordFromTemplate(final byte[] masterKey, final byte[] siteKey, final MPResultType resultType,
@Nullable final String resultParam) {
int[] _siteKey = new int[siteKey.length];
for (int i = 0; i < siteKey.length; ++i) {
ByteBuffer buf = ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( mpw_byteOrder );
Arrays.fill( buf.array(), (byte) ((siteKey[i] > 0)? 0x00: 0xFF) );
buf.position( 2 );
buf.put( siteKey[i] ).rewind();
_siteKey[i] = buf.getInt() & 0xFFFF;
}
// Determine the template.
Preconditions.checkState( _siteKey.length > 0 );
int templateIndex = _siteKey[0];
MPTemplate template = resultType.getTemplateAtRollingIndex( templateIndex );
logger.trc( "template: %d => %s", templateIndex, template.getTemplateString() );
// Encode the password from the seed using the template.
StringBuilder password = new StringBuilder( template.length() );
for (int i = 0; i < template.length(); ++i) {
int characterIndex = _siteKey[i + 1];
MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i );
char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex );
logger.trc( " - class: %c, index: %5d (0x%2H) => character: %c",
characterClass.getIdentifier(), characterIndex, _siteKey[i + 1], passwordCharacter );
password.append( passwordCharacter );
}
logger.trc( " => password: %s", password );
return password.toString();
}
@Override
public String sitePasswordFromCrypt(final byte[] masterKey, final byte[] siteKey, final MPResultType resultType,
@Nullable final String resultParam) {
Preconditions.checkNotNull( resultParam );
Preconditions.checkArgument( !resultParam.isEmpty() );
try {
// Base64-decode
byte[] cipherBuf = CryptUtils.decodeBase64( resultParam );
logger.trc( "b64 decoded: %d bytes = %s", cipherBuf.length, CodeUtils.encodeHex( cipherBuf ) );
// Decrypt
byte[] plainBuf = CryptUtils.decrypt( cipherBuf, masterKey, true );
String plainText = mpw_charset.decode( ByteBuffer.wrap( plainBuf ) ).toString();
logger.trc( "decrypted -> plainText: %d bytes = %s = %s", plainBuf.length, plainText, CodeUtils.encodeHex( plainBuf ) );
return plainText;
}
catch (final BadPaddingException e) {
throw Throwables.propagate( e );
}
}
@Override
public String sitePasswordFromDerive(final byte[] masterKey, final byte[] siteKey, final MPResultType resultType,
@Nullable final String resultParam) {
if (resultType == MPResultType.DeriveKey) {
int resultParamInt = ConversionUtils.toIntegerNN( resultParam );
if (resultParamInt == 0)
resultParamInt = 512;
if ((resultParamInt < 128) || (resultParamInt > 512) || ((resultParamInt % 8) != 0))
throw logger.bug( "Parameter is not a valid key size (should be 128 - 512): %s", resultParam );
int keySize = resultParamInt / 8;
logger.trc( "keySize: %d", keySize );
// Derive key
byte[] resultKey = null; // TODO: mpw_kdf_blake2b( keySize, siteKey, MPSiteKeySize, NULL, 0, 0, NULL );
if (resultKey == null)
throw logger.bug( "Could not derive result key." );
// Base64-encode
String b64Key = Verify.verifyNotNull( CryptUtils.encodeBase64( resultKey ) );
logger.trc( "b64 encoded -> key: %s", b64Key );
return b64Key;
} else
throw logger.bug( "Unsupported derived password type: %s", resultType );
}
@Override
public String siteState(final byte[] masterKey, final byte[] siteKey, final String siteName, final UnsignedInteger siteCounter,
final MPKeyPurpose keyPurpose,
@Nullable final String keyContext, final MPResultType resultType, final String resultParam) {
try {
// Encrypt
byte[] cipherBuf = CryptUtils.encrypt( resultParam.getBytes( mpw_charset ), masterKey, true );
logger.trc( "cipherBuf: %d bytes = %s", cipherBuf.length, CodeUtils.encodeHex( cipherBuf ) );
// Base64-encode
String cipherText = Verify.verifyNotNull( CryptUtils.encodeBase64( cipherBuf ) );
logger.trc( "b64 encoded -> cipherText: %s", cipherText );
return cipherText;
}
catch (final IllegalBlockSizeException e) {
throw logger.bug( e );
}
}
}

View File

@@ -0,0 +1,62 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword;
import com.google.common.base.Preconditions;
import javax.annotation.Nullable;
/**
* @see MPMasterKey.Version#V1
*
* @author lhunath, 2014-08-30
*/
public class MPAlgorithmV1 extends MPAlgorithmV0 {
@Override
public MPMasterKey.Version getAlgorithmVersion() {
return MPMasterKey.Version.V1;
}
@Override
public String sitePasswordFromTemplate(final byte[] masterKey, final byte[] siteKey, final MPResultType resultType, @Nullable final String resultParam) {
// Determine the template.
Preconditions.checkState( siteKey.length > 0 );
int templateIndex = siteKey[0] & 0xFF; // Convert to unsigned int.
MPTemplate template = resultType.getTemplateAtRollingIndex( templateIndex );
logger.trc( "template: %d => %s", templateIndex, template.getTemplateString() );
// Encode the password from the seed using the template.
StringBuilder password = new StringBuilder( template.length() );
for (int i = 0; i < template.length(); ++i) {
int characterIndex = siteKey[i + 1] & 0xFF; // Convert to unsigned int.
MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i );
char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex );
logger.trc( " - class: %c, index: %3d (0x%2H) => character: %c",
characterClass.getIdentifier(), characterIndex, siteKey[i + 1], passwordCharacter );
password.append( passwordCharacter );
}
logger.trc( " => password: %s", password );
return password.toString();
}
}

View File

@@ -0,0 +1,74 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword;
import static com.lyndir.masterpassword.MPUtils.*;
import com.google.common.primitives.Bytes;
import com.google.common.primitives.UnsignedInteger;
import com.lyndir.lhunath.opal.system.CodeUtils;
import javax.annotation.Nullable;
/**
* @see MPMasterKey.Version#V2
*
* @author lhunath, 2014-08-30
*/
public class MPAlgorithmV2 extends MPAlgorithmV1 {
@Override
public MPMasterKey.Version getAlgorithmVersion() {
return MPMasterKey.Version.V2;
}
@Override
public byte[] siteKey(final byte[] masterKey, final String siteName, UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose,
@Nullable final String keyContext) {
String keyScope = keyPurpose.getScope();
logger.trc( "keyScope: %s", keyScope );
// OTP counter value.
if (siteCounter.longValue() == 0)
siteCounter = UnsignedInteger.valueOf( (System.currentTimeMillis() / (MPAlgorithm.mpw_otp_window * 1000)) * MPAlgorithm.mpw_otp_window );
// Calculate the site seed.
byte[] siteNameBytes = siteName.getBytes( MPAlgorithm.mpw_charset );
byte[] siteNameLengthBytes = bytesForInt( siteNameBytes.length );
byte[] siteCounterBytes = bytesForInt( siteCounter );
byte[] keyContextBytes = ((keyContext == null) || keyContext.isEmpty())? null: keyContext.getBytes( MPAlgorithm.mpw_charset );
byte[] keyContextLengthBytes = (keyContextBytes == null)? null: bytesForInt( keyContextBytes.length );
logger.trc( "siteSalt: keyScope=%s | #siteName=%s | siteName=%s | siteCounter=%s | #keyContext=%s | keyContext=%s",
keyScope, CodeUtils.encodeHex( siteNameLengthBytes ), siteName, CodeUtils.encodeHex( siteCounterBytes ),
(keyContextLengthBytes == null)? null: CodeUtils.encodeHex( keyContextLengthBytes ), keyContext );
byte[] sitePasswordInfo = Bytes.concat( keyScope.getBytes( MPAlgorithm.mpw_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
if (keyContextBytes != null)
sitePasswordInfo = Bytes.concat( sitePasswordInfo, keyContextLengthBytes, keyContextBytes );
logger.trc( " => siteSalt.id: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) );
logger.trc( "siteKey: hmac-sha256( masterKey.id=%s, siteSalt )", CodeUtils.encodeHex( idForBytes( masterKey ) ) );
byte[] sitePasswordSeedBytes = MPAlgorithm.mpw_digest.of( masterKey, sitePasswordInfo );
logger.trc( " => siteKey.id: %s", CodeUtils.encodeHex( idForBytes( sitePasswordSeedBytes ) ) );
return sitePasswordSeedBytes;
}
}

View File

@@ -0,0 +1,67 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword;
import static com.lyndir.masterpassword.MPUtils.*;
import com.google.common.primitives.Bytes;
import com.lyndir.lhunath.opal.system.CodeUtils;
import java.util.Arrays;
/**
* @see MPMasterKey.Version#V3
*
* @author lhunath, 2014-08-30
*/
public class MPAlgorithmV3 extends MPAlgorithmV2 {
@Override
public MPMasterKey.Version getAlgorithmVersion() {
return MPMasterKey.Version.V3;
}
@Override
public byte[] masterKey(final String fullName, final char[] masterPassword) {
byte[] fullNameBytes = fullName.getBytes( MPAlgorithm.mpw_charset );
byte[] fullNameLengthBytes = MPUtils.bytesForInt( fullNameBytes.length );
String keyScope = MPKeyPurpose.Authentication.getScope();
logger.trc( "keyScope: %s", keyScope );
// Calculate the master key salt.
logger.trc( "masterKeySalt: keyScope=%s | #fullName=%s | fullName=%s",
keyScope, CodeUtils.encodeHex( fullNameLengthBytes ), fullName );
byte[] masterKeySalt = Bytes.concat( keyScope.getBytes( MPAlgorithm.mpw_charset ), fullNameLengthBytes, fullNameBytes );
logger.trc( " => masterKeySalt.id: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) );
// Calculate the master key.
logger.trc( "masterKey: scrypt( masterPassword, masterKeySalt, N=%d, r=%d, p=%d )",
MPAlgorithm.scrypt_N, MPAlgorithm.scrypt_r, MPAlgorithm.scrypt_p );
byte[] mpBytes = bytesForChars( masterPassword );
byte[] masterKey = scrypt( masterKeySalt, mpBytes );
Arrays.fill( masterKeySalt, (byte) 0 );
Arrays.fill( mpBytes, (byte) 0 );
logger.trc( " => masterKey.id: %s", CodeUtils.encodeHex( idForBytes( masterKey ) ) );
return masterKey;
}
}

View File

@@ -18,11 +18,8 @@
package com.lyndir.masterpassword; package com.lyndir.masterpassword;
import com.google.common.base.Charsets; import org.joda.time.format.DateTimeFormatter;
import com.lyndir.lhunath.opal.system.MessageAuthenticationDigests; import org.joda.time.format.ISODateTimeFormat;
import com.lyndir.lhunath.opal.system.MessageDigests;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
/** /**
@@ -32,68 +29,18 @@ public final class MPConstant {
/* Environment */ /* Environment */
/**
* mpw: default user name if one is not provided.
*/
public static final String env_userName = "MP_USERNAME";
/**
* mpw: default site type if one is not provided.
*
* @see MPSiteType#forOption(String)
*/
public static final String env_siteType = "MP_SITETYPE";
/**
* mpw: default site counter value if one is not provided.
*/
public static final String env_siteCounter = "MP_SITECOUNTER";
/** /**
* mpw: default path to look for run configuration files if the platform default is not desired. * mpw: default path to look for run configuration files if the platform default is not desired.
*/ */
public static final String env_rcDir = "MP_RCDIR"; public static final String env_rcDir = "MPW_RCDIR";
/** /**
* mpw: permit automatic update checks. * mpw: permit automatic update checks.
*/ */
public static final String env_checkUpdates = "MP_CHECKUPDATES"; public static final String env_checkUpdates = "MPW_CHECKUPDATES";
/* Algorithm */ /* Algorithm */
/**
* scrypt: CPU cost parameter.
*/
public static final int scrypt_N = 32768;
/**
* scrypt: Memory cost parameter.
*/
public static final int scrypt_r = 8;
/**
* scrypt: Parallelization parameter.
*/
public static final int scrypt_p = 2;
/**
* mpw: Master key size (byte).
*/
public static final int mpw_dkLen = 64;
/**
* mpw: Input character encoding.
*/
public static final Charset mpw_charset = Charsets.UTF_8;
/**
* mpw: Platform-agnostic byte order.
*/
public static final ByteOrder mpw_byteOrder = ByteOrder.BIG_ENDIAN;
/**
* mpw: Site digest.
*/
public static final MessageAuthenticationDigests mpw_digest = MessageAuthenticationDigests.HmacSHA256;
/**
* mpw: Key ID hash.
*/
public static final MessageDigests mpw_hash = MessageDigests.SHA256;
/**
* mpw: validity for the time-based rolling counter.
*/
public static final int mpw_counter_timeout = 5 * 60 /* s */;
public static final int MS_PER_S = 1000; public static final int MS_PER_S = 1000;
public static final DateTimeFormatter dateTimeFormatter = ISODateTimeFormat.dateTimeNoMillis();
} }

View File

@@ -16,13 +16,10 @@
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>. // LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//============================================================================== //==============================================================================
/**
*
* @author lhunath, 15-02-04
*/
@ParametersAreNonnullByDefault
package com.lyndir.masterpassword; package com.lyndir.masterpassword;
import javax.annotation.ParametersAreNonnullByDefault; /**
* @author lhunath, 2017-09-21
*/
public class MPInvalidatedException extends Exception {
}

View File

@@ -0,0 +1,97 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.util.Locale;
import javax.annotation.Nullable;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NonNls;
/**
* @author lhunath, 14-12-02
*/
public enum MPKeyPurpose {
/**
* Generate a key for authentication.
*/
Authentication( "authentication", "Generate a key for authentication.", "com.lyndir.masterpassword" ),
/**
* Generate a name for identification.
*/
Identification( "identification", "Generate a name for identification.", "com.lyndir.masterpassword.login" ),
/**
* Generate a recovery token.
*/
Recovery( "recovery", "Generate a recovery token.", "com.lyndir.masterpassword.answer" );
static final Logger logger = Logger.get( MPResultType.class );
private final String shortName;
private final String description;
private final String scope;
MPKeyPurpose(final String shortName, final String description, @NonNls final String scope) {
this.shortName = shortName;
this.description = description;
this.scope = scope;
}
public String getShortName() {
return shortName;
}
public String getDescription() {
return description;
}
public String getScope() {
return scope;
}
/**
* @param shortNamePrefix The name for the purpose to look up. It is a case insensitive prefix of the purpose's short name.
*
* @return The purpose registered with the given name.
*/
@Nullable
@Contract("!null -> !null")
public static MPKeyPurpose forName(@Nullable final String shortNamePrefix) {
if (shortNamePrefix == null)
return null;
for (final MPKeyPurpose type : values())
if (type.getShortName().toLowerCase( Locale.ROOT ).startsWith( shortNamePrefix.toLowerCase( Locale.ROOT ) ))
return type;
throw logger.bug( "No purpose for name: %s", shortNamePrefix );
}
public static MPKeyPurpose forInt(final int keyPurpose) {
return values()[keyPurpose];
}
public int toInt() {
return ordinal();
}
}

View File

@@ -0,0 +1,246 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword;
import static com.lyndir.masterpassword.MPUtils.*;
import com.google.common.base.Preconditions;
import com.google.common.primitives.UnsignedInteger;
import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.util.Arrays;
import java.util.EnumMap;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* @author lhunath, 2014-08-30
*/
public class MPMasterKey {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MPMasterKey.class );
private final EnumMap<Version, byte[]> keyByVersion = new EnumMap<>( Version.class );
private final String fullName;
private final char[] masterPassword;
private boolean invalidated;
/**
* @param masterPassword The characters of the user's master password. Note: this array is held by reference and its contents
* invalidated on {@link #invalidate()}.
*/
@SuppressWarnings("AssignmentToCollectionOrArrayFieldFromParameter")
public MPMasterKey(final String fullName, final char[] masterPassword) {
this.fullName = fullName;
this.masterPassword = masterPassword;
}
/**
* Derive the master key for a user based on their name and master password.
*
* @throws MPInvalidatedException {@link #invalidate()} has been called on this object.
*/
private byte[] masterKey(final Version algorithmVersion)
throws MPInvalidatedException {
Preconditions.checkArgument( masterPassword.length > 0 );
if (invalidated)
throw new MPInvalidatedException();
byte[] key = keyByVersion.get( algorithmVersion );
if (key == null) {
logger.trc( "-- mpw_masterKey (algorithm: %d)", algorithmVersion.toInt() );
logger.trc( "fullName: %s", fullName );
logger.trc( "masterPassword.id: %s", CodeUtils.encodeHex( idForBytes( bytesForChars( masterPassword ) ) ) );
keyByVersion.put( algorithmVersion, key = algorithmVersion.getAlgorithm().masterKey( fullName, masterPassword ) );
}
return key;
}
/**
* Derive the master key for a user based on their name and master password.
*
* @throws MPInvalidatedException {@link #invalidate()} has been called on this object.
*/
private byte[] siteKey(final String siteName, final UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose,
@Nullable final String keyContext, final Version algorithmVersion)
throws MPInvalidatedException {
Preconditions.checkArgument( !siteName.isEmpty() );
byte[] masterKey = masterKey( algorithmVersion );
logger.trc( "-- mpw_siteKey (algorithm: %d)", algorithmVersion.toInt() );
logger.trc( "siteName: %s", siteName );
logger.trc( "siteCounter: %s", siteCounter );
logger.trc( "keyPurpose: %d (%s)", keyPurpose.toInt(), keyPurpose.getShortName() );
logger.trc( "keyContext: %s", keyContext );
return algorithmVersion.getAlgorithm().siteKey( masterKey, siteName, siteCounter, keyPurpose, keyContext );
}
/**
* Generate a site result token.
*
* @param siteName A site identifier.
* @param siteCounter The result identifier.
* @param keyPurpose The intended purpose for this site result.
* @param keyContext A site-scoped result modifier.
* @param resultType The type of result to generate.
* @param resultParam A parameter for the resultType. For stateful result types, the output of
* {@link #siteState(String, UnsignedInteger, MPKeyPurpose, String, MPResultType, String, Version)}.
*
* @throws MPInvalidatedException {@link #invalidate()} has been called on this object.
*/
public String siteResult(final String siteName, final UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose,
@Nullable final String keyContext, final MPResultType resultType, @Nullable final String resultParam,
final Version algorithmVersion)
throws MPInvalidatedException {
byte[] masterKey = masterKey( algorithmVersion );
byte[] siteKey = siteKey( siteName, siteCounter, keyPurpose, keyContext, algorithmVersion );
logger.trc( "-- mpw_siteResult (algorithm: %d)", algorithmVersion.toInt() );
logger.trc( "resultType: %d (%s)", resultType.getType(), resultType.getShortName() );
logger.trc( "resultParam: %s", resultParam );
return algorithmVersion.getAlgorithm().siteResult(
masterKey, siteKey, siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam );
}
/**
* Encrypt a stateful site token for persistence.
*
* @param siteName A site identifier.
* @param siteCounter The result identifier.
* @param keyPurpose The intended purpose for the site token.
* @param keyContext A site-scoped key modifier.
* @param resultType The type of result token to encrypt.
* @param resultParam The result token desired from
* {@link #siteResult(String, UnsignedInteger, MPKeyPurpose, String, MPResultType, String, Version)}.
*
* @throws MPInvalidatedException {@link #invalidate()} has been called on this object.
*/
public String siteState(final String siteName, final UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose,
@Nullable final String keyContext, final MPResultType resultType, @Nullable final String resultParam,
final Version algorithmVersion)
throws MPInvalidatedException {
Preconditions.checkNotNull( resultParam );
Preconditions.checkArgument( !resultParam.isEmpty() );
byte[] masterKey = masterKey( algorithmVersion );
byte[] siteKey = siteKey( siteName, siteCounter, keyPurpose, keyContext, algorithmVersion );
logger.trc( "-- mpw_siteState (algorithm: %d)", algorithmVersion.toInt() );
logger.trc( "resultType: %d (%s)", resultType.getType(), resultType.getShortName() );
logger.trc( "resultParam: %d bytes = %s", resultParam.getBytes( MPAlgorithm.mpw_charset ).length, resultParam );
return algorithmVersion.getAlgorithm().siteState(
masterKey, siteKey, siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam );
}
@Nonnull
public String getFullName() {
return fullName;
}
/**
* Calculate an identifier for the master key.
*
* @throws MPInvalidatedException {@link #invalidate()} has been called on this object.
*/
public byte[] getKeyID(final Version algorithmVersion)
throws MPInvalidatedException {
return idForBytes( masterKey( algorithmVersion ) );
}
/**
* Wipe this key's secrets from memory, making the object permanently unusable.
*/
public void invalidate() {
invalidated = true;
for (final byte[] key : keyByVersion.values())
Arrays.fill( key, (byte) 0 );
Arrays.fill( masterPassword, (char) 0 );
}
/**
* The algorithm iterations.
*/
public enum Version {
/**
* bugs:
* - does math with chars whose signedness was platform-dependent.
* - miscounted the byte-length for multi-byte site names.
* - miscounted the byte-length for multi-byte user names.
*/
V0( new MPAlgorithmV0() ),
/**
* bugs:
* - miscounted the byte-length for multi-byte site names.
* - miscounted the byte-length for multi-byte user names.
*/
V1( new MPAlgorithmV1() ),
/**
* bugs:
* - miscounted the byte-length for multi-byte user names.
*/
V2( new MPAlgorithmV2() ),
/**
* bugs:
* - no known issues.
*/
V3( new MPAlgorithmV3() );
public static final Version CURRENT = V3;
private final MPAlgorithm algorithm;
Version(final MPAlgorithm algorithm) {
this.algorithm = algorithm;
}
public MPAlgorithm getAlgorithm() {
return algorithm;
}
public static Version fromInt(final int algorithmVersion) {
return values()[algorithmVersion];
}
public int toInt() {
return ordinal();
}
}
}

View File

@@ -24,7 +24,6 @@ import com.lyndir.lhunath.opal.system.logging.Logger;
import java.util.*; import java.util.*;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NonNls;
/** /**
@@ -32,15 +31,21 @@ import org.jetbrains.annotations.NonNls;
* *
* @author lhunath * @author lhunath
*/ */
public enum MPSiteType { public enum MPResultType {
// bit 0-3 | MPResultTypeClass | MPSiteFeature
GeneratedMaximum( "Max", "20 characters, contains symbols.", // /**
ImmutableList.of( "x", "max", "maximum" ), // NON-NLS * pg^VMAUBk5x3p%HP%i4=
ImmutableList.of( new MPTemplate( "anoxxxxxxxxxxxxxxxxx" ), new MPTemplate( "axxxxxxxxxxxxxxxxxno" ) ), // */
MPSiteTypeClass.Generated, 0x0 ), GeneratedMaximum( "maximum", "20 characters, contains symbols.", //
ImmutableList.of( new MPTemplate( "anoxxxxxxxxxxxxxxxxx" ),
new MPTemplate( "axxxxxxxxxxxxxxxxxno" ) ), //
MPResultTypeClass.Template, 0x0 ),
GeneratedLong( "Long", "Copy-friendly, 14 characters, contains symbols.", // /**
ImmutableList.of( "l", "long" ), // NON-NLS * BiroYena8:Kixa
*/
GeneratedLong( "long", "Copy-friendly, 14 characters, contains symbols.", //
ImmutableList.of( new MPTemplate( "CvcvnoCvcvCvcv" ), new MPTemplate( "CvcvCvcvnoCvcv" ), ImmutableList.of( new MPTemplate( "CvcvnoCvcvCvcv" ), new MPTemplate( "CvcvCvcvnoCvcv" ),
new MPTemplate( "CvcvCvcvCvcvno" ), new MPTemplate( "CvccnoCvcvCvcv" ), new MPTemplate( "CvcvCvcvCvcvno" ), new MPTemplate( "CvccnoCvcvCvcv" ),
new MPTemplate( "CvccCvcvnoCvcv" ), new MPTemplate( "CvccCvcvCvcvno" ), new MPTemplate( "CvccCvcvnoCvcv" ), new MPTemplate( "CvccCvcvCvcvno" ),
@@ -52,65 +57,92 @@ public enum MPSiteType {
new MPTemplate( "CvcvCvccnoCvcc" ), new MPTemplate( "CvcvCvccCvccno" ), new MPTemplate( "CvcvCvccnoCvcc" ), new MPTemplate( "CvcvCvccCvccno" ),
new MPTemplate( "CvccnoCvcvCvcc" ), new MPTemplate( "CvccCvcvnoCvcc" ), new MPTemplate( "CvccnoCvcvCvcc" ), new MPTemplate( "CvccCvcvnoCvcc" ),
new MPTemplate( "CvccCvcvCvccno" ) ), // new MPTemplate( "CvccCvcvCvccno" ) ), //
MPSiteTypeClass.Generated, 0x1 ), MPResultTypeClass.Template, 0x1 ),
GeneratedMedium( "Medium", "Copy-friendly, 8 characters, contains symbols.", // /**
ImmutableList.of( "m", "med", "medium" ), // NON-NLS * BirSuj0-
ImmutableList.of( new MPTemplate( "CvcnoCvc" ), new MPTemplate( "CvcCvcno" ) ), // */
MPSiteTypeClass.Generated, 0x2 ), GeneratedMedium( "medium", "Copy-friendly, 8 characters, contains symbols.", //
ImmutableList.of( new MPTemplate( "CvcnoCvc" ),
new MPTemplate( "CvcCvcno" ) ), //
MPResultTypeClass.Template, 0x2 ),
GeneratedBasic( "Basic", "8 characters, no symbols.", // /**
ImmutableList.of( "b", "basic" ), // NON-NLS * pO98MoD0
ImmutableList.of( new MPTemplate( "aaanaaan" ), new MPTemplate( "aannaaan" ), new MPTemplate( "aaannaaa" ) ), // */
MPSiteTypeClass.Generated, 0x3 ), GeneratedBasic( "basic", "8 characters, no symbols.", //
ImmutableList.of( new MPTemplate( "aaanaaan" ),
new MPTemplate( "aannaaan" ),
new MPTemplate( "aaannaaa" ) ), //
MPResultTypeClass.Template, 0x3 ),
GeneratedShort( "Short", "Copy-friendly, 4 characters, no symbols.", // /**
ImmutableList.of( "s", "short" ), // NON-NLS * Bir8
*/
GeneratedShort( "short", "Copy-friendly, 4 characters, no symbols.", //
ImmutableList.of( new MPTemplate( "Cvcn" ) ), // ImmutableList.of( new MPTemplate( "Cvcn" ) ), //
MPSiteTypeClass.Generated, 0x4 ), MPResultTypeClass.Template, 0x4 ),
GeneratedPIN( "PIN", "4 numbers.", // /**
ImmutableList.of( "i", "pin" ), // NON-NLS * 2798
*/
GeneratedPIN( "pin", "4 numbers.", //
ImmutableList.of( new MPTemplate( "nnnn" ) ), // ImmutableList.of( new MPTemplate( "nnnn" ) ), //
MPSiteTypeClass.Generated, 0x5 ), MPResultTypeClass.Template, 0x5 ),
GeneratedName( "Name", "9 letter name.", // /**
ImmutableList.of( "n", "name" ), // NON-NLS * birsujano
*/
GeneratedName( "name", "9 letter name.", //
ImmutableList.of( new MPTemplate( "cvccvcvcv" ) ), // ImmutableList.of( new MPTemplate( "cvccvcvcv" ) ), //
MPSiteTypeClass.Generated, 0xE ), MPResultTypeClass.Template, 0xE ),
GeneratedPhrase( "Phrase", "20 character sentence.", // /**
ImmutableList.of( "p", "phrase" ), // NON-NLS * bir yennoquce fefi
ImmutableList.of( new MPTemplate( "cvcc cvc cvccvcv cvc" ), new MPTemplate( "cvc cvccvcvcv cvcv" ), */
GeneratedPhrase( "phrase", "20 character sentence.", //
ImmutableList.of( new MPTemplate( "cvcc cvc cvccvcv cvc" ),
new MPTemplate( "cvc cvccvcvcv cvcv" ),
new MPTemplate( "cv cvccv cvc cvcvccv" ) ), // new MPTemplate( "cv cvccv cvc cvcvccv" ) ), //
MPSiteTypeClass.Generated, 0xF ), MPResultTypeClass.Template, 0xF ),
StoredPersonal( "Personal", "AES-encrypted, exportable.", // /**
ImmutableList.of( "personal" ), // NON-NLS * Custom saved password.
*/
StoredPersonal( "personal", "AES-encrypted, exportable.", //
ImmutableList.<MPTemplate>of(), // ImmutableList.<MPTemplate>of(), //
MPSiteTypeClass.Stored, 0x0, MPSiteFeature.ExportContent ), MPResultTypeClass.Stateful, 0x0, MPSiteFeature.ExportContent ),
StoredDevicePrivate( "Device", "AES-encrypted, not exported.", // /**
ImmutableList.of( "device" ), // NON-NLS * Custom saved password that should not be exported from the device.
*/
StoredDevicePrivate( "device", "AES-encrypted, not exported.", //
ImmutableList.<MPTemplate>of(), // ImmutableList.<MPTemplate>of(), //
MPSiteTypeClass.Stored, 0x1, MPSiteFeature.DevicePrivate ); MPResultTypeClass.Stateful, 0x1, MPSiteFeature.DevicePrivate ),
static final Logger logger = Logger.get( MPSiteType.class ); /**
* Derive a unique binary key.
*/
DeriveKey( "key", "Encryption key.", //
ImmutableList.<MPTemplate>of(), //
MPResultTypeClass.Derive, 0x0, MPSiteFeature.Alternative );
public static final MPResultType DEFAULT = GeneratedLong;
static final Logger logger = Logger.get( MPResultType.class );
private final String shortName; private final String shortName;
private final String description; private final String description;
private final List<String> options;
private final List<MPTemplate> templates; private final List<MPTemplate> templates;
private final MPSiteTypeClass typeClass; private final MPResultTypeClass typeClass;
private final int typeIndex; private final int typeIndex;
private final Set<MPSiteFeature> typeFeatures; private final Set<MPSiteFeature> typeFeatures;
MPSiteType(final String shortName, final String description, final List<String> options, final List<MPTemplate> templates, MPResultType(final String shortName, final String description, final List<MPTemplate> templates,
final MPSiteTypeClass typeClass, final int typeIndex, final MPSiteFeature... typeFeatures) { final MPResultTypeClass typeClass, final int typeIndex, final MPSiteFeature... typeFeatures) {
this.shortName = shortName; this.shortName = shortName;
this.description = description; this.description = description;
this.options = options;
this.templates = templates; this.templates = templates;
this.typeClass = typeClass; this.typeClass = typeClass;
this.typeIndex = typeIndex; this.typeIndex = typeIndex;
@@ -131,11 +163,7 @@ public enum MPSiteType {
return description; return description;
} }
public List<String> getOptions() { public MPResultTypeClass getTypeClass() {
return options;
}
public MPSiteTypeClass getTypeClass() {
return typeClass; return typeClass;
} }
@@ -154,35 +182,22 @@ public enum MPSiteType {
} }
/** /**
* @param option The option to select a type with. It is matched case insensitively. * @param shortNamePrefix The name for the type to look up. It is a case insensitive prefix of the type's short name.
*
* @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( Locale.ROOT ) ))
return type;
throw logger.bug( "No type for option: %s", option );
}
/**
* @param name The name fromInt the type to look up. It is matched case insensitively.
* *
* @return The type registered with the given name. * @return The type registered with the given name.
*/ */
@Nullable
@Contract("!null -> !null") @Contract("!null -> !null")
public static MPSiteType forName(@Nullable final String name) { public static MPResultType forName(@Nullable final String shortNamePrefix) {
if (name == null) if (shortNamePrefix == null)
return null; return null;
for (final MPSiteType type : values()) for (final MPResultType type : values())
if (type.name().equalsIgnoreCase( name )) if (type.getShortName().toLowerCase( Locale.ROOT ).startsWith( shortNamePrefix.toLowerCase( Locale.ROOT ) ))
return type; return type;
throw logger.bug( "No type for name: %s", name ); throw logger.bug( "No type for name: %s", shortNamePrefix );
} }
/** /**
@@ -190,10 +205,10 @@ public enum MPSiteType {
* *
* @return All types that support the given class. * @return All types that support the given class.
*/ */
public static ImmutableList<MPSiteType> forClass(final MPSiteTypeClass typeClass) { public static ImmutableList<MPResultType> forClass(final MPResultTypeClass typeClass) {
ImmutableList.Builder<MPSiteType> types = ImmutableList.builder(); ImmutableList.Builder<MPResultType> types = ImmutableList.builder();
for (final MPSiteType type : values()) for (final MPResultType type : values())
if (type.getTypeClass() == typeClass) if (type.getTypeClass() == typeClass)
types.add( type ); types.add( type );
@@ -205,27 +220,28 @@ public enum MPSiteType {
* *
* @return The type registered with the given type. * @return The type registered with the given type.
*/ */
public static MPSiteType forType(final int type) { public static MPResultType forType(final int type) {
for (final MPSiteType siteType : values()) for (final MPResultType resultType : values())
if (siteType.getType() == type) if (resultType.getType() == type)
return siteType; return resultType;
throw logger.bug( "No type: %s", type ); throw logger.bug( "No type: %s", type );
} }
/** /**
* @param mask The mask for which we look up types. * @param mask The type mask for which we look up types.
* *
* @return All types that support the given mask. * @return All types that support the given mask's class & features.
*/ */
public static ImmutableList<MPSiteType> forMask(final int mask) { public static ImmutableList<MPResultType> forMask(final int mask) {
int typeMask = mask & ~0xF; int typeMask = mask & ~0xF; // Ignore resultType bit 0-3
ImmutableList.Builder<MPSiteType> types = ImmutableList.builder();
for (final MPSiteType siteType : values()) ImmutableList.Builder<MPResultType> types = ImmutableList.builder();
if (((siteType.getType() & ~0xF) & typeMask) != 0) for (final MPResultType resultType : values())
types.add( siteType ); if (((resultType.getType() & ~0xF) & typeMask) != 0)
types.add( resultType );
return types.build(); return types.build();
} }

View File

@@ -0,0 +1,51 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword;
/**
* <i>07 04, 2012</i>
*
* @author lhunath
*/
public enum MPResultTypeClass {
// bit 4 - 9
/**
* Use the site key to generate a password from a template.
*/
Template( 1 << 4 ),
/**
* Use the site key to encrypt and decrypt a stateful entity.
*/
Stateful( 1 << 5 ),
/**
* Use the site key to derive a site-specific object.
*/
Derive( 1 << 6 );
private final int mask;
MPResultTypeClass(final int mask) {
this.mask = mask;
}
public int getMask() {
return mask;
}
}

View File

@@ -24,6 +24,7 @@ package com.lyndir.masterpassword;
* @author lhunath * @author lhunath
*/ */
public enum MPSiteFeature { public enum MPSiteFeature {
// bit 10 - 15
/** /**
* Export the key-protected content data. * Export the key-protected content data.
@@ -33,7 +34,12 @@ public enum MPSiteFeature {
/** /**
* Never export content. * Never export content.
*/ */
DevicePrivate( 1 << 11 ); DevicePrivate( 1 << 11 ),
/**
* Don't use this as the primary authentication result type.
*/
Alternative( 1 << 12 );
MPSiteFeature(final int mask) { MPSiteFeature(final int mask) {
this.mask = mask; this.mask = mask;

View File

@@ -1,103 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword;
import com.google.common.collect.ImmutableList;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.util.List;
import java.util.Locale;
import javax.annotation.Nullable;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NonNls;
/**
* @author lhunath, 14-12-02
*/
public enum MPSiteVariant {
Password( "Generate a key for authentication.", "Doesn't currently use a context.", //
ImmutableList.of( "p", "password" ), "com.lyndir.masterpassword" ), // NON-NLS
Login( "Generate a name for identification.", "Doesn't currently use a context.", //
ImmutableList.of( "l", "login" ), "com.lyndir.masterpassword.login" ), // NON-NLS
Answer( "Generate an 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" ); // NON-NLS
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, @NonNls 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( Locale.ROOT ) ))
return variant;
throw logger.bug( "No variant for option: %s", option );
}
/**
* @param name The name fromInt the variant to look up. It is matched case insensitively.
*
* @return The variant registered with the given name.
*/
@Contract("!null -> !null")
public static MPSiteVariant forName(@Nullable 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

@@ -0,0 +1,53 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword;
import com.google.common.primitives.UnsignedInteger;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.util.Arrays;
/**
* @author lhunath, 2017-09-20
*/
public final class MPUtils {
public static byte[] bytesForInt(final int number) {
return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( MPAlgorithm.mpw_byteOrder ).putInt( number ).array();
}
public static byte[] bytesForInt(final UnsignedInteger number) {
return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( MPAlgorithm.mpw_byteOrder ).putInt( number.intValue() ).array();
}
public static byte[] bytesForChars(final char[] characters) {
ByteBuffer byteBuffer = MPAlgorithm.mpw_charset.encode( CharBuffer.wrap( characters ) );
byte[] bytes = new byte[byteBuffer.remaining()];
byteBuffer.get( bytes );
Arrays.fill( byteBuffer.array(), (byte) 0 );
return bytes;
}
public static byte[] idForBytes(final byte[] bytes) {
return MPAlgorithm.mpw_hash.of( bytes );
}
}

View File

@@ -1,219 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import com.google.common.base.Preconditions;
import com.google.common.primitives.UnsignedInteger;
import com.lyndir.lhunath.opal.system.*;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.util.Arrays;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* @author lhunath, 2014-08-30
*/
public abstract class MasterKey {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MasterKey.class );
private static boolean allowNativeByDefault = true;
@Nonnull
private final String fullName;
private boolean allowNative = allowNativeByDefault;
@Nullable
private byte[] masterKey;
@SuppressWarnings("MethodCanBeVariableArityMethod")
public static MasterKey create(final String fullName, final char[] masterPassword) {
return create( Version.CURRENT, fullName, masterPassword );
}
@Nonnull
@SuppressWarnings("MethodCanBeVariableArityMethod")
public static MasterKey create(final Version version, final String fullName, final char[] masterPassword) {
switch (version) {
case V0:
return new MasterKeyV0( fullName ).revalidate( masterPassword );
case V1:
return new MasterKeyV1( fullName ).revalidate( masterPassword );
case V2:
return new MasterKeyV2( fullName ).revalidate( masterPassword );
case V3:
return new MasterKeyV3( fullName ).revalidate( masterPassword );
}
throw new UnsupportedOperationException( strf( "Unsupported version: %s", version ) );
}
public static boolean isAllowNativeByDefault() {
return allowNativeByDefault;
}
/**
* Native libraries are useful for speeding up the performance of cryptographical functions.
* Sometimes, however, we may prefer to use Java-only code.
* For instance, for auditability / trust or because the native code doesn't work on our CPU/platform.
* <p/>
* This setter affects the default setting for any newly created {@link MasterKey}s.
*
* @param allowNative false to disallow the use of native libraries.
*/
public static void setAllowNativeByDefault(final boolean allowNative) {
allowNativeByDefault = allowNative;
}
protected MasterKey(@Nonnull final String fullName) {
this.fullName = fullName;
logger.trc( "fullName: %s", fullName );
}
@Nullable
@SuppressWarnings("MethodCanBeVariableArityMethod")
protected abstract byte[] deriveKey(char[] masterPassword);
public abstract Version getAlgorithmVersion();
@Nonnull
public String getFullName() {
return fullName;
}
public boolean isAllowNative() {
return allowNative;
}
public MasterKey setAllowNative(final boolean allowNative) {
this.allowNative = allowNative;
return this;
}
@Nonnull
protected byte[] getKey() {
Preconditions.checkState( isValid() );
return Preconditions.checkNotNull( masterKey );
}
public byte[] getKeyID() {
return idForBytes( getKey() );
}
public abstract String encode(@Nonnull String siteName, MPSiteType siteType, @Nonnull UnsignedInteger siteCounter,
MPSiteVariant siteVariant, @Nullable String siteContext);
public boolean isValid() {
return masterKey != null;
}
public void invalidate() {
if (masterKey != null) {
Arrays.fill( masterKey, (byte) 0 );
masterKey = null;
}
}
@SuppressWarnings("MethodCanBeVariableArityMethod")
public MasterKey revalidate(final char[] masterPassword) {
invalidate();
logger.trc( "masterPassword: %s", new String( masterPassword ) );
long start = System.currentTimeMillis();
masterKey = deriveKey( masterPassword );
if (masterKey == null)
logger.dbg( "masterKey calculation failed after %.2fs.", (double)(System.currentTimeMillis() - start) / MPConstant.MS_PER_S );
else
logger.trc( "masterKey ID: %s (derived in %.2fs)", CodeUtils.encodeHex( idForBytes( masterKey ) ),
(double)(System.currentTimeMillis() - start) / MPConstant.MS_PER_S );
return this;
}
protected abstract byte[] bytesForInt(int number);
protected abstract byte[] bytesForInt(@Nonnull UnsignedInteger number);
protected abstract byte[] idForBytes(byte[] bytes);
public enum Version {
/**
* bugs:
* - does math with chars whose signedness was platform-dependent.
* - miscounted the byte-length fromInt multi-byte site names.
* - miscounted the byte-length fromInt multi-byte full names.
*/
V0,
/**
* bugs:
* - miscounted the byte-length fromInt multi-byte site names.
* - miscounted the byte-length fromInt multi-byte full names.
*/
V1,
/**
* bugs:
* - miscounted the byte-length fromInt multi-byte full names.
*/
V2,
/**
* bugs:
* - no known issues.
*/
V3;
public static final Version CURRENT = V3;
public static Version fromInt(final int algorithmVersion) {
return values()[algorithmVersion];
}
public int toInt() {
return ordinal();
}
public String toBundleVersion() {
switch (this) {
case V0:
return "1.0";
case V1:
return "2.0";
case V2:
return "2.1";
case V3:
return "2.2";
}
throw new UnsupportedOperationException( strf( "Unsupported version: %s", this ) );
}
}
}

View File

@@ -1,170 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword;
import com.google.common.base.Preconditions;
import com.google.common.primitives.Bytes;
import com.google.common.primitives.UnsignedInteger;
import com.lambdaworks.crypto.SCrypt;
import com.lyndir.lhunath.opal.system.*;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.nio.*;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* bugs:
* - V2: miscounted the byte-length fromInt multi-byte full names.
* - V1: miscounted the byte-length fromInt multi-byte site names.
* - V0: does math with chars whose signedness was platform-dependent.
*
* @author lhunath, 2014-08-30
*/
public class MasterKeyV0 extends MasterKey {
private static final int MP_intLen = 32;
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MasterKeyV0.class );
public MasterKeyV0(final String fullName) {
super( fullName );
}
@Override
public Version getAlgorithmVersion() {
return Version.V0;
}
@Nullable
@Override
protected byte[] deriveKey(final char[] masterPassword) {
String fullName = getFullName();
byte[] fullNameBytes = fullName.getBytes( MPConstant.mpw_charset );
byte[] fullNameLengthBytes = bytesForInt( fullName.length() );
String mpKeyScope = MPSiteVariant.Password.getScope();
byte[] masterKeySalt = Bytes.concat( mpKeyScope.getBytes( MPConstant.mpw_charset ), fullNameLengthBytes, fullNameBytes );
logger.trc( "key scope: %s", mpKeyScope );
logger.trc( "masterKeySalt ID: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) );
ByteBuffer mpBytesBuf = MPConstant.mpw_charset.encode( CharBuffer.wrap( masterPassword ) );
byte[] mpBytes = new byte[mpBytesBuf.remaining()];
mpBytesBuf.get( mpBytes, 0, mpBytes.length );
Arrays.fill( mpBytesBuf.array(), (byte) 0 );
return scrypt( masterKeySalt, mpBytes );
}
@Nullable
protected byte[] scrypt(final byte[] masterKeySalt, final byte[] mpBytes) {
try {
if (isAllowNative())
return SCrypt.scrypt( mpBytes, masterKeySalt, MPConstant.scrypt_N, MPConstant.scrypt_r, MPConstant.scrypt_p, MPConstant.mpw_dkLen );
else
return SCrypt.scryptJ( mpBytes, masterKeySalt, MPConstant.scrypt_N, MPConstant.scrypt_r, MPConstant.scrypt_p, MPConstant.mpw_dkLen );
}
catch (final GeneralSecurityException e) {
logger.bug( e );
return null;
}
finally {
Arrays.fill( mpBytes, (byte) 0 );
}
}
@Override
public String encode(@Nonnull final String siteName, final MPSiteType siteType, @Nonnull UnsignedInteger siteCounter,
final MPSiteVariant siteVariant, @Nullable final String siteContext) {
Preconditions.checkArgument( siteType.getTypeClass() == MPSiteTypeClass.Generated );
Preconditions.checkArgument( !siteName.isEmpty() );
logger.trc( "siteName: %s", siteName );
logger.trc( "siteCounter: %d", siteCounter.longValue() );
logger.trc( "siteVariant: %d (%s)", siteVariant.ordinal(), siteVariant );
logger.trc( "siteType: %d (%s)", siteType.ordinal(), siteType );
if (siteCounter.longValue() == 0)
siteCounter = UnsignedInteger.valueOf( (System.currentTimeMillis() / (MPConstant.mpw_counter_timeout * 1000)) * MPConstant.mpw_counter_timeout );
String siteScope = siteVariant.getScope();
byte[] siteNameBytes = siteName.getBytes( MPConstant.mpw_charset );
byte[] siteNameLengthBytes = bytesForInt( siteName.length() );
byte[] siteCounterBytes = bytesForInt( siteCounter );
byte[] siteContextBytes = ((siteContext == null) || siteContext.isEmpty())? null: siteContext.getBytes( MPConstant.mpw_charset );
byte[] siteContextLengthBytes = bytesForInt( (siteContextBytes == null)? 0: siteContextBytes.length );
logger.trc( "site scope: %s, context: %s", siteScope, (siteContextBytes == null)? "<empty>": siteContext );
logger.trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)", siteScope, CodeUtils.encodeHex( siteNameLengthBytes ),
siteName, CodeUtils.encodeHex( siteCounterBytes ), CodeUtils.encodeHex( siteContextLengthBytes ),
(siteContextBytes == null)? "(null)": siteContext );
byte[] sitePasswordInfo = Bytes.concat( siteScope.getBytes( MPConstant.mpw_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
if (siteContextBytes != null)
sitePasswordInfo = Bytes.concat( sitePasswordInfo, siteContextLengthBytes, siteContextBytes );
logger.trc( "sitePasswordInfo ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) );
byte[] sitePasswordSeedBytes = MPConstant.mpw_digest.of( getKey(), sitePasswordInfo );
int[] sitePasswordSeed = new int[sitePasswordSeedBytes.length];
for (int i = 0; i < sitePasswordSeedBytes.length; ++i) {
ByteBuffer buf = ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( ByteOrder.BIG_ENDIAN );
Arrays.fill( buf.array(), (byte) ((sitePasswordSeedBytes[i] > 0)? 0x00: 0xFF) );
buf.position( 2 );
buf.put( sitePasswordSeedBytes[i] ).rewind();
sitePasswordSeed[i] = buf.getInt() & 0xFFFF;
}
logger.trc( "sitePasswordSeed ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordSeedBytes ) ) );
Preconditions.checkState( sitePasswordSeed.length > 0 );
int templateIndex = sitePasswordSeed[0];
MPTemplate template = siteType.getTemplateAtRollingIndex( templateIndex );
logger.trc( "type %s, template: %s", siteType, template.getTemplateString() );
StringBuilder password = new StringBuilder( template.length() );
for (int i = 0; i < template.length(); ++i) {
int characterIndex = sitePasswordSeed[i + 1];
MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i );
char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex );
logger.trc( "class %c, index %d (0x%02X) -> character: %c", characterClass.getIdentifier(), characterIndex,
sitePasswordSeed[i + 1], passwordCharacter );
password.append( passwordCharacter );
}
return password.toString();
}
@Override
protected byte[] bytesForInt(final int number) {
return ByteBuffer.allocate( MP_intLen / Byte.SIZE ).order( MPConstant.mpw_byteOrder ).putInt( number ).array();
}
@Override
protected byte[] bytesForInt(@Nonnull final UnsignedInteger number) {
return ByteBuffer.allocate( MP_intLen / Byte.SIZE ).order( MPConstant.mpw_byteOrder ).putInt( number.intValue() ).array();
}
@Override
protected byte[] idForBytes(final byte[] bytes) {
return MPConstant.mpw_hash.of( bytes );
}
}

View File

@@ -1,103 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword;
import com.google.common.base.Preconditions;
import com.google.common.primitives.Bytes;
import com.google.common.primitives.UnsignedInteger;
import com.lyndir.lhunath.opal.system.*;
import com.lyndir.lhunath.opal.system.logging.Logger;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* bugs:
* - V2: miscounted the byte-length fromInt multi-byte full names.
* - V1: miscounted the byte-length fromInt multi-byte site names.
*
* @author lhunath, 2014-08-30
*/
public class MasterKeyV1 extends MasterKeyV0 {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MasterKeyV1.class );
public MasterKeyV1(final String fullName) {
super( fullName );
}
@Override
public Version getAlgorithmVersion() {
return Version.V1;
}
@Override
public String encode(@Nonnull final String siteName, final MPSiteType siteType, @Nonnull UnsignedInteger siteCounter,
final MPSiteVariant siteVariant, @Nullable final String siteContext) {
Preconditions.checkArgument( siteType.getTypeClass() == MPSiteTypeClass.Generated );
Preconditions.checkArgument( !siteName.isEmpty() );
logger.trc( "siteName: %s", siteName );
logger.trc( "siteCounter: %d", siteCounter.longValue() );
logger.trc( "siteVariant: %d (%s)", siteVariant.ordinal(), siteVariant );
logger.trc( "siteType: %d (%s)", siteType.ordinal(), siteType );
if (siteCounter.longValue() == 0)
siteCounter = UnsignedInteger.valueOf( (System.currentTimeMillis() / (MPConstant.mpw_counter_timeout * 1000)) * MPConstant.mpw_counter_timeout );
String siteScope = siteVariant.getScope();
byte[] siteNameBytes = siteName.getBytes( MPConstant.mpw_charset );
byte[] siteNameLengthBytes = bytesForInt( siteName.length() );
byte[] siteCounterBytes = bytesForInt( siteCounter );
byte[] siteContextBytes = ((siteContext == null) || siteContext.isEmpty())? null: siteContext.getBytes( MPConstant.mpw_charset );
byte[] siteContextLengthBytes = bytesForInt( (siteContextBytes == null)? 0: siteContextBytes.length );
logger.trc( "site scope: %s, context: %s", siteScope, (siteContextBytes == null)? "<empty>": siteContext );
logger.trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)", siteScope, CodeUtils.encodeHex( siteNameLengthBytes ),
siteName, CodeUtils.encodeHex( siteCounterBytes ), CodeUtils.encodeHex( siteContextLengthBytes ),
(siteContextBytes == null)? "(null)": siteContext );
byte[] sitePasswordInfo = Bytes.concat( siteScope.getBytes( MPConstant.mpw_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
if (siteContextBytes != null)
sitePasswordInfo = Bytes.concat( sitePasswordInfo, siteContextLengthBytes, siteContextBytes );
logger.trc( "sitePasswordInfo ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) );
byte[] sitePasswordSeed = MPConstant.mpw_digest.of( getKey(), sitePasswordInfo );
logger.trc( "sitePasswordSeed ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordSeed ) ) );
Preconditions.checkState( sitePasswordSeed.length > 0 );
int templateIndex = sitePasswordSeed[0] & 0xFF; // Mask the integer's sign.
MPTemplate template = siteType.getTemplateAtRollingIndex( templateIndex );
logger.trc( "type %s, template: %s", siteType, template.getTemplateString() );
StringBuilder password = new StringBuilder( template.length() );
for (int i = 0; i < template.length(); ++i) {
int characterIndex = sitePasswordSeed[i + 1] & 0xFF; // Mask the integer's sign.
MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i );
char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex );
logger.trc( "class %c, index %d (0x%02X) -> character: %c", characterClass.getIdentifier(), characterIndex,
sitePasswordSeed[i + 1], passwordCharacter );
password.append( passwordCharacter );
}
return password.toString();
}
}

View File

@@ -1,102 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword;
import com.google.common.base.Preconditions;
import com.google.common.primitives.Bytes;
import com.google.common.primitives.UnsignedInteger;
import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.lhunath.opal.system.logging.Logger;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* bugs:
* - V2: miscounted the byte-length fromInt multi-byte full names.
*
* @author lhunath, 2014-08-30
*/
public class MasterKeyV2 extends MasterKeyV1 {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MasterKeyV2.class );
public MasterKeyV2(final String fullName) {
super( fullName );
}
@Override
public Version getAlgorithmVersion() {
return Version.V2;
}
@Override
public String encode(@Nonnull final String siteName, final MPSiteType siteType, @Nonnull UnsignedInteger siteCounter,
final MPSiteVariant siteVariant, @Nullable final String siteContext) {
Preconditions.checkArgument( siteType.getTypeClass() == MPSiteTypeClass.Generated );
Preconditions.checkArgument( !siteName.isEmpty() );
logger.trc( "siteName: %s", siteName );
logger.trc( "siteCounter: %d", siteCounter.longValue() );
logger.trc( "siteVariant: %d (%s)", siteVariant.ordinal(), siteVariant );
logger.trc( "siteType: %d (%s)", siteType.ordinal(), siteType );
if (siteCounter.longValue() == 0)
siteCounter = UnsignedInteger.valueOf( (System.currentTimeMillis() / (MPConstant.mpw_counter_timeout * 1000)) * MPConstant.mpw_counter_timeout );
String siteScope = siteVariant.getScope();
byte[] siteNameBytes = siteName.getBytes( MPConstant.mpw_charset );
byte[] siteNameLengthBytes = bytesForInt( siteNameBytes.length );
byte[] siteCounterBytes = bytesForInt( siteCounter );
byte[] siteContextBytes = ((siteContext == null) || siteContext.isEmpty())? null: siteContext.getBytes( MPConstant.mpw_charset );
byte[] siteContextLengthBytes = bytesForInt( (siteContextBytes == null)? 0: siteContextBytes.length );
logger.trc( "site scope: %s, context: %s", siteScope, (siteContextBytes == null)? "<empty>": siteContext );
logger.trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)", siteScope, CodeUtils.encodeHex( siteNameLengthBytes ),
siteName, CodeUtils.encodeHex( siteCounterBytes ), CodeUtils.encodeHex( siteContextLengthBytes ),
(siteContextBytes == null)? "(null)": siteContext );
byte[] sitePasswordInfo = Bytes.concat( siteScope.getBytes( MPConstant.mpw_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
if (siteContextBytes != null)
sitePasswordInfo = Bytes.concat( sitePasswordInfo, siteContextLengthBytes, siteContextBytes );
logger.trc( "sitePasswordInfo ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) );
byte[] sitePasswordSeed = MPConstant.mpw_digest.of( getKey(), sitePasswordInfo );
logger.trc( "sitePasswordSeed ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordSeed ) ) );
Preconditions.checkState( sitePasswordSeed.length > 0 );
int templateIndex = sitePasswordSeed[0] & 0xFF; // Mask the integer's sign.
MPTemplate template = siteType.getTemplateAtRollingIndex( templateIndex );
logger.trc( "type %s, template: %s", siteType, template.getTemplateString() );
StringBuilder password = new StringBuilder( template.length() );
for (int i = 0; i < template.length(); ++i) {
int characterIndex = sitePasswordSeed[i + 1] & 0xFF; // Mask the integer's sign.
MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i );
char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex );
logger.trc( "class %c, index %d (0x%02X) -> character: %c", characterClass.getIdentifier(), characterIndex,
sitePasswordSeed[i + 1], passwordCharacter );
password.append( passwordCharacter );
}
return password.toString();
}
}

View File

@@ -1,69 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword;
import com.google.common.primitives.Bytes;
import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.util.Arrays;
import javax.annotation.Nullable;
/**
* bugs:
* - no known issues.
*
* @author lhunath, 2014-08-30
*/
public class MasterKeyV3 extends MasterKeyV2 {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MasterKeyV3.class );
public MasterKeyV3(final String fullName) {
super( fullName );
}
@Override
public Version getAlgorithmVersion() {
return Version.V3;
}
@Nullable
@Override
protected byte[] deriveKey(final char[] masterPassword) {
byte[] fullNameBytes = getFullName().getBytes( MPConstant.mpw_charset );
byte[] fullNameLengthBytes = bytesForInt( fullNameBytes.length );
String mpKeyScope = MPSiteVariant.Password.getScope();
byte[] masterKeySalt = Bytes.concat( mpKeyScope.getBytes( MPConstant.mpw_charset ), fullNameLengthBytes, fullNameBytes );
logger.trc( "key scope: %s", mpKeyScope );
logger.trc( "masterKeySalt ID: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) );
ByteBuffer mpBytesBuf = MPConstant.mpw_charset.encode( CharBuffer.wrap( masterPassword ) );
byte[] mpBytes = new byte[mpBytesBuf.remaining()];
mpBytesBuf.get( mpBytes, 0, mpBytes.length );
Arrays.fill( mpBytesBuf.array(), (byte) 0 );
return scrypt( masterKeySalt, mpBytes );
}
}

View File

@@ -1,19 +0,0 @@
package com.lyndir.masterpassword.model;
/**
* @author lhunath, 14-12-17
*/
public class IncorrectMasterPasswordException extends Exception {
private final MPUser user;
public IncorrectMasterPasswordException(final MPUser user) {
super( "Incorrect master password for user: " + user.getFullName() );
this.user = user;
}
public MPUser getUser() {
return user;
}
}

View File

@@ -0,0 +1,205 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.model;
import com.google.common.primitives.UnsignedInteger;
import com.lyndir.masterpassword.*;
import javax.annotation.Nullable;
import org.joda.time.Instant;
/**
* @author lhunath, 14-12-05
*/
public class MPFileSite extends MPSite {
private final MPFileUser user;
private String siteName;
@Nullable
private String siteContent;
private UnsignedInteger siteCounter;
private MPResultType resultType;
private MPMasterKey.Version algorithmVersion;
@Nullable
private String loginContent;
@Nullable
private MPResultType loginType;
@Nullable
private String url;
private int uses;
private Instant lastUsed;
public MPFileSite(final MPFileUser user, final String siteName) {
this( user, siteName, DEFAULT_COUNTER, MPResultType.DEFAULT, MPMasterKey.Version.CURRENT );
}
public MPFileSite(final MPFileUser user, final String siteName, final UnsignedInteger siteCounter, final MPResultType resultType,
final MPMasterKey.Version algorithmVersion) {
this.user = user;
this.siteName = siteName;
this.siteCounter = siteCounter;
this.resultType = resultType;
this.algorithmVersion = algorithmVersion;
this.lastUsed = new Instant();
}
protected MPFileSite(final MPFileUser user, final String siteName, @Nullable final String siteContent,
final UnsignedInteger siteCounter,
final MPResultType resultType, final MPMasterKey.Version algorithmVersion,
@Nullable final String loginContent, @Nullable final MPResultType loginType,
@Nullable final String url, final int uses, final Instant lastUsed) {
this.user = user;
this.siteName = siteName;
this.siteContent = siteContent;
this.siteCounter = siteCounter;
this.resultType = resultType;
this.algorithmVersion = algorithmVersion;
this.loginContent = loginContent;
this.loginType = loginType;
this.url = url;
this.uses = uses;
this.lastUsed = lastUsed;
}
public String resultFor(final MPMasterKey masterKey)
throws MPInvalidatedException {
return resultFor( masterKey, MPKeyPurpose.Authentication, null );
}
public String resultFor(final MPMasterKey masterKey, final MPKeyPurpose keyPurpose, @Nullable final String keyContext)
throws MPInvalidatedException {
return resultFor( masterKey, keyPurpose, keyContext, getSiteContent() );
}
public String loginFor(final MPMasterKey masterKey)
throws MPInvalidatedException {
if (loginType == null)
loginType = MPResultType.GeneratedName;
return loginFor( masterKey, loginType, loginContent );
}
public MPFileUser getUser() {
return user;
}
@Override
public String getSiteName() {
return siteName;
}
@Override
public void setSiteName(final String siteName) {
this.siteName = siteName;
}
@Nullable
public String getSiteContent() {
return siteContent;
}
public void setSitePassword(final MPMasterKey masterKey, @Nullable final MPResultType resultType, @Nullable final String result)
throws MPInvalidatedException {
this.resultType = resultType;
if (result == null)
this.siteContent = null;
else
this.siteContent = masterKey.siteState(
getSiteName(), getSiteCounter(), MPKeyPurpose.Authentication, null, getResultType(), result, getAlgorithmVersion() );
}
@Override
public UnsignedInteger getSiteCounter() {
return siteCounter;
}
@Override
public void setSiteCounter(final UnsignedInteger siteCounter) {
this.siteCounter = siteCounter;
}
@Override
public MPResultType getResultType() {
return resultType;
}
@Override
public void setResultType(final MPResultType resultType) {
this.resultType = resultType;
}
@Override
public MPMasterKey.Version getAlgorithmVersion() {
return algorithmVersion;
}
@Override
public void setAlgorithmVersion(final MPMasterKey.Version algorithmVersion) {
this.algorithmVersion = algorithmVersion;
}
@Nullable
public MPResultType getLoginType() {
return loginType;
}
@Nullable
public String getLoginContent() {
return loginContent;
}
public void setLoginName(final MPMasterKey masterKey, @Nullable final MPResultType loginType, @Nullable final String result)
throws MPInvalidatedException {
this.loginType = loginType;
if (this.loginType != null)
if (result == null)
this.loginContent = null;
else
this.loginContent = masterKey.siteState(
siteName, DEFAULT_COUNTER, MPKeyPurpose.Identification, null, this.loginType, result, algorithmVersion );
}
@Nullable
public String getUrl() {
return url;
}
public void setUrl(@Nullable final String url) {
this.url = url;
}
public int getUses() {
return uses;
}
public Instant getLastUsed() {
return lastUsed;
}
public void use() {
uses++;
lastUsed = new Instant();
user.use();
}
}

View File

@@ -0,0 +1,172 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.model;
import com.google.common.collect.*;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.masterpassword.*;
import java.util.*;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.joda.time.*;
/**
* @author lhunath, 14-12-07
*/
public class MPFileUser extends MPUser<MPFileSite> implements Comparable<MPFileUser> {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MPFileUser.class );
private final String fullName;
private final Collection<MPFileSite> sites = Sets.newHashSet();
@Nullable
private byte[] keyID;
private MPMasterKey.Version algorithmVersion;
private int avatar;
private MPResultType defaultType;
private ReadableInstant lastUsed;
public MPFileUser(final String fullName) {
this( fullName, null, MPMasterKey.Version.CURRENT );
}
public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPMasterKey.Version algorithmVersion) {
this( fullName, keyID, algorithmVersion, 0, MPResultType.DEFAULT, new Instant() );
}
public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPMasterKey.Version algorithmVersion, final int avatar,
final MPResultType defaultType, final ReadableInstant lastUsed) {
this.fullName = fullName;
this.keyID = (keyID == null)? null: keyID.clone();
this.algorithmVersion = algorithmVersion;
this.avatar = avatar;
this.defaultType = defaultType;
this.lastUsed = lastUsed;
}
@Override
public String getFullName() {
return fullName;
}
@Override
public MPMasterKey.Version getAlgorithmVersion() {
return algorithmVersion;
}
public void setAlgorithmVersion(final MPMasterKey.Version algorithmVersion) {
this.algorithmVersion = algorithmVersion;
}
@Override
public int getAvatar() {
return avatar;
}
public void setAvatar(final int avatar) {
this.avatar = avatar;
}
public MPResultType getDefaultType() {
return defaultType;
}
public void setDefaultType(final MPResultType defaultType) {
this.defaultType = defaultType;
}
public ReadableInstant getLastUsed() {
return lastUsed;
}
public void use() {
lastUsed = new Instant();
}
public Iterable<MPFileSite> getSites() {
return sites;
}
@Override
public void addSite(final MPFileSite site) {
sites.add( site );
}
@Override
public void deleteSite(final MPFileSite site) {
sites.remove( site );
}
@Override
public Collection<MPFileSite> findSites(final String query) {
ImmutableList.Builder<MPFileSite> results = ImmutableList.builder();
for (final MPFileSite site : getSites())
if (site.getSiteName().startsWith( query ))
results.add( site );
return results.build();
}
/**
* Performs an authentication attempt against the keyID for this user.
*
* Note: If this user doesn't have a keyID set yet, authentication will always succeed and the key ID will be set as a result.
*
* @param masterPassword The password to authenticate with.
*
* @return The master key for the user if authentication was successful.
*
* @throws MPIncorrectMasterPasswordException If authentication fails due to the given master password not matching the user's keyID.
*/
@Nonnull
@Override
public MPMasterKey authenticate(final char[] masterPassword)
throws MPIncorrectMasterPasswordException {
try {
key = new MPMasterKey( getFullName(), masterPassword );
if ((keyID == null) || (keyID.length == 0))
keyID = key.getKeyID( algorithmVersion );
else if (!Arrays.equals( key.getKeyID( algorithmVersion ), keyID ))
throw new MPIncorrectMasterPasswordException( this );
return key;
}
catch (final MPInvalidatedException e) {
throw logger.bug( e );
}
}
void save()
throws MPInvalidatedException {
MPFileUserManager.get().save( this, getMasterKey() );
}
@Override
public int compareTo(final MPFileUser o) {
int comparison = getLastUsed().compareTo( o.getLastUsed() );
if (comparison == 0)
comparison = getFullName().compareTo( o.getFullName() );
return comparison;
}
}

View File

@@ -0,0 +1,139 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.model;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
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 com.lyndir.masterpassword.*;
import java.io.*;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Manages user data stored in user-specific {@code .mpsites} files under {@code .mpw.d}.
*
* @author lhunath, 14-12-07
*/
public class MPFileUserManager extends MPUserManager {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MPFileUserManager.class );
private static final MPFileUserManager instance;
static {
String rcDir = System.getenv( MPConstant.env_rcDir );
if (rcDir != null)
instance = create( new File( rcDir ) );
else
instance = create( new File( ifNotNullElseNullable( System.getProperty( "user.home" ), System.getenv( "HOME" ) ), ".mpw.d" ) );
}
private final File userFilesDirectory;
public static MPFileUserManager get() {
MPUserManager.instance = instance;
return instance;
}
public static MPFileUserManager create(final File userFilesDirectory) {
return new MPFileUserManager( userFilesDirectory );
}
protected MPFileUserManager(final File userFilesDirectory) {
super( unmarshallUsers( userFilesDirectory ) );
this.userFilesDirectory = userFilesDirectory;
}
private static Iterable<MPFileUser> 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( listUserFiles( userFilesDirectory ) ).transform( new Function<File, MPFileUser>() {
@Nullable
@Override
public MPFileUser apply(@Nullable final File file) {
try {
return new MPFlatUnmarshaller().unmarshall( Preconditions.checkNotNull( file ) );
}
catch (final IOException e) {
logger.err( e, "Couldn't read user from: %s", file );
return null;
}
}
} ).filter( Predicates.notNull() );
}
private static ImmutableList<File> listUserFiles(final File userFilesDirectory) {
return ImmutableList.copyOf( ifNotNullElse( userFilesDirectory.listFiles( new FilenameFilter() {
@Override
public boolean accept(final File dir, final String name) {
return name.endsWith( ".mpsites" );
}
} ), new File[0] ) );
}
@Override
public void deleteUser(final MPFileUser user) {
super.deleteUser( user );
// Remove deleted users.
File userFile = getUserFile( user );
if (userFile.exists() && !userFile.delete())
logger.err( "Couldn't delete file: %s", userFile );
}
/**
* Write the current user state to disk.
*/
public void save(final MPFileUser user, final MPMasterKey masterKey)
throws MPInvalidatedException {
try {
new CharSink() {
@Override
public Writer openStream()
throws IOException {
return new OutputStreamWriter( new FileOutputStream( getUserFile( user ) ), Charsets.UTF_8 );
}
}.write( new MPFlatMarshaller().marshall( user, masterKey, MPMarshaller.ContentMode.PROTECTED ) );
}
catch (final IOException e) {
logger.err( e, "Unable to save sites for user: %s", user );
}
}
@Nonnull
private File getUserFile(final MPFileUser user) {
return new File( userFilesDirectory, user.getFullName() + ".mpsites" );
}
/**
* @return The location on the file system where the user models are stored.
*/
public File getPath() {
return userFilesDirectory;
}
}

View File

@@ -0,0 +1,80 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
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 org.joda.time.Instant;
/**
* @author lhunath, 2017-09-20
*/
public class MPFlatMarshaller implements MPMarshaller {
private static final int FORMAT = 1;
@Override
public String marshall(final MPFileUser user, final MPMasterKey masterKey, final ContentMode contentMode)
throws MPInvalidatedException {
StringBuilder content = new StringBuilder();
content.append( "# Master Password site export\n" );
content.append( "# " ).append( contentMode.description() ).append( '\n' );
content.append( "# \n" );
content.append( "##\n" );
content.append( "# Format: " ).append( FORMAT ).append( '\n' );
content.append( "# Date: " ).append( MPConstant.dateTimeFormatter.print( new Instant() ) ).append( '\n' );
content.append( "# User Name: " ).append( user.getFullName() ).append( '\n' );
content.append( "# Full Name: " ).append( user.getFullName() ).append( '\n' );
content.append( "# Avatar: " ).append( user.getAvatar() ).append( '\n' );
content.append( "# Key ID: " ).append( user.exportKeyID() ).append( '\n' );
content.append( "# Algorithm: " ).append( MPMasterKey.Version.CURRENT.toInt() ).append( '\n' );
content.append( "# Default Type: " ).append( user.getDefaultType().getType() ).append( '\n' );
content.append( "# Passwords: " ).append( contentMode.name() ).append( '\n' );
content.append( "##\n" );
content.append( "#\n" );
content.append( "# Last Times Password Login\t Site\tSite\n" );
content.append( "# used used type name\t name\tpassword\n" );
for (final MPFileSite site : user.getSites()) {
String loginName = site.getLoginContent();
String password = site.getSiteContent();
if (!contentMode.isRedacted()) {
loginName = site.loginFor( masterKey );
password = site.resultFor( masterKey );
}
content.append( strf( "%s %8d %8s %25s\t%25s\t%s\n", //
MPConstant.dateTimeFormatter.print( site.getLastUsed() ), // lastUsed
site.getUses(), // uses
strf( "%d:%d:%d", //
site.getResultType().getType(), // type
site.getAlgorithmVersion().toInt(), // algorithm
site.getSiteCounter().intValue() ), // counter
ifNotNullElse( loginName, "" ), // loginName
site.getSiteName(), // siteName
ifNotNullElse( password, "" ) // password
) );
}
return content.toString();
}
}

View File

@@ -0,0 +1,139 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.model;
import com.google.common.base.*;
import com.google.common.io.CharStreams;
import com.google.common.primitives.UnsignedInteger;
import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
import com.lyndir.masterpassword.*;
import java.io.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nonnull;
import org.joda.time.DateTime;
/**
* @author lhunath, 14-12-07
*/
public class MPFlatUnmarshaller implements MPUnmarshaller {
private static final Pattern[] unmarshallFormats = {
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 static final Pattern colon = Pattern.compile( ":" );
@Nonnull
@Override
public MPFileUser unmarshall(@Nonnull final File file)
throws IOException {
try (Reader reader = new InputStreamReader( new FileInputStream( file ), Charsets.UTF_8 )) {
return unmarshall( CharStreams.toString( reader ) );
}
}
@Nonnull
@Override
public MPFileUser unmarshall(@Nonnull final String content) {
MPFileUser user = null;
byte[] keyID = null;
String fullName = null;
int mpVersion = 0, importFormat = 0, avatar = 0;
boolean clearContent = false, headerStarted = false;
MPResultType defaultType = MPResultType.DEFAULT;
//noinspection HardcodedLineSeparator
for (final String line : Splitter.on( CharMatcher.anyOf( "\r\n" ) ).omitEmptyStrings().split( content ))
// Header delimitor.
if (line.startsWith( "##" ))
if (!headerStarted)
// Starts the header.
headerStarted = true;
else
// Ends the header.
user = new MPFileUser( fullName, keyID, MPMasterKey.Version.fromInt( mpVersion ), avatar, defaultType, new DateTime( 0 ) );
// Comment.
else if (line.startsWith( "#" )) {
if (headerStarted && (user == 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 = "visible".equalsIgnoreCase( value );
else if ("Default Type".equalsIgnoreCase( name ))
defaultType = MPResultType.forType( ConversionUtils.toIntegerNN( value ) );
}
}
}
// No comment.
else if (user != null) {
Matcher siteMatcher = unmarshallFormats[importFormat].matcher( line );
if (!siteMatcher.matches())
return null;
MPFileSite site;
switch (importFormat) {
case 0:
site = new MPFileSite( user, //
siteMatcher.group( 5 ), siteMatcher.group( 6 ), MPFileSite.DEFAULT_COUNTER,
MPResultType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),
MPMasterKey.Version.fromInt( ConversionUtils.toIntegerNN(
colon.matcher( siteMatcher.group( 4 ) ).replaceAll( "" ) ) ),
null, null, null, ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ),
MPConstant.dateTimeFormatter.parseDateTime( siteMatcher.group( 1 ) ).toInstant() );
break;
case 1:
site = new MPFileSite( user, //
siteMatcher.group( 7 ), siteMatcher.group( 8 ),
UnsignedInteger.valueOf( colon.matcher( siteMatcher.group( 5 ) ).replaceAll( "" ) ),
MPResultType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),
MPMasterKey.Version.fromInt( ConversionUtils.toIntegerNN(
colon.matcher( siteMatcher.group( 4 ) ).replaceAll( "" ) ) ),
siteMatcher.group( 6 ), MPResultType.GeneratedName, null,
ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ),
MPConstant.dateTimeFormatter.parseDateTime( siteMatcher.group( 1 ) ).toInstant() );
break;
default:
throw new UnsupportedOperationException( "Unexpected format: " + importFormat );
}
user.addSite( site );
}
return Preconditions.checkNotNull( user, "No full header found in import file." );
}
}

View File

@@ -0,0 +1,37 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.model;
/**
* @author lhunath, 14-12-17
*/
public class MPIncorrectMasterPasswordException extends Exception {
private final MPFileUser user;
public MPIncorrectMasterPasswordException(final MPFileUser user) {
super( "Incorrect master password for user: " + user.getFullName() );
this.user = user;
}
public MPFileUser getUser() {
return user;
}
}

View File

@@ -0,0 +1,34 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.model;
import com.lyndir.masterpassword.MPMasterKey;
/**
* @author lhunath, 2017-09-20
*/
public class MPJSONMarshaller implements MPMarshaller {
@Override
public String marshall(final MPFileUser user, final MPMasterKey masterKey, final ContentMode contentMode) {
// TODO
return null;
}
}

View File

@@ -0,0 +1,45 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.model;
import java.io.File;
import java.io.IOException;
import javax.annotation.Nonnull;
/**
* @author lhunath, 2017-09-20
*/
public class MPJSONUnmarshaller implements MPUnmarshaller {
@Nonnull
@Override
public MPFileUser unmarshall(@Nonnull final File file)
throws IOException {
// TODO
return null;
}
@Nonnull
@Override
public MPFileUser unmarshall(@Nonnull final String content) {
// TODO
return null;
}
}

View File

@@ -0,0 +1,60 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.model;
/**
* @author lhunath, 2017-09-20
*/
public enum MPMarshalFormat {
/**
* Marshal using the line-based plain-text format.
*/
Flat {
@Override
public MPMarshaller marshaller() {
return new MPFlatMarshaller();
}
@Override
public MPUnmarshaller unmarshaller() {
return new MPFlatUnmarshaller();
}
},
/**
* Marshal using the JSON structured format.
*/
JSON {
@Override
public MPMarshaller marshaller() {
return new MPJSONMarshaller();
}
@Override
public MPUnmarshaller unmarshaller() {
return new MPJSONUnmarshaller();
}
};
public static final MPMarshalFormat DEFAULT = JSON;
public abstract MPMarshaller marshaller();
public abstract MPUnmarshaller unmarshaller();
}

View File

@@ -0,0 +1,52 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.model;
import com.lyndir.masterpassword.MPInvalidatedException;
import com.lyndir.masterpassword.MPMasterKey;
/**
* @author lhunath, 14-12-07
*/
public interface MPMarshaller {
String marshall(MPFileUser user, MPMasterKey masterKey, ContentMode contentMode)
throws MPInvalidatedException;
enum ContentMode {
PROTECTED( "Export of site names and stored passwords (unless device-private) encrypted with the master key." ),
VISIBLE( "Export of site names and passwords in clear-text." );
private final String description;
private boolean redacted;
ContentMode(final String description) {
this.description = description;
}
public String description() {
return description;
}
public boolean isRedacted() {
return redacted;
}
}
}

View File

@@ -1,3 +1,21 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.model; package com.lyndir.masterpassword.model;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf; import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
@@ -6,138 +24,58 @@ import com.google.common.primitives.UnsignedInteger;
import com.lyndir.masterpassword.*; import com.lyndir.masterpassword.*;
import java.util.Objects; import java.util.Objects;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.joda.time.Instant;
/** /**
* @author lhunath, 14-12-05 * @author lhunath, 14-12-16
*/ */
public class MPSite { public abstract class MPSite {
public static final MPSiteType DEFAULT_TYPE = MPSiteType.GeneratedLong; public static final UnsignedInteger DEFAULT_COUNTER = UnsignedInteger.ONE;
public static final UnsignedInteger DEFAULT_COUNTER = UnsignedInteger.valueOf( 1 );
private final MPUser user; public abstract String getSiteName();
private MasterKey.Version algorithmVersion;
private Instant lastUsed;
private String siteName;
private MPSiteType siteType;
private UnsignedInteger siteCounter;
private int uses;
private String loginName;
public MPSite(final MPUser user, final String siteName) { public abstract void setSiteName(String siteName);
this( user, siteName, DEFAULT_TYPE, DEFAULT_COUNTER );
public abstract UnsignedInteger getSiteCounter();
public abstract void setSiteCounter(UnsignedInteger siteCounter);
public abstract MPResultType getResultType();
public abstract void setResultType(MPResultType resultType);
public abstract MPMasterKey.Version getAlgorithmVersion();
public abstract void setAlgorithmVersion(MPMasterKey.Version algorithmVersion);
public String resultFor(final MPMasterKey masterKey, final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
@Nullable final String siteContent)
throws MPInvalidatedException {
return masterKey.siteResult(
getSiteName(), getSiteCounter(), keyPurpose, keyContext, getResultType(), siteContent, getAlgorithmVersion() );
} }
public MPSite(final MPUser user, final String siteName, final MPSiteType siteType, final UnsignedInteger siteCounter) { public String loginFor(final MPMasterKey masterKey, final MPResultType loginType, @Nullable final String loginContent)
this.user = user; throws MPInvalidatedException {
this.algorithmVersion = MasterKey.Version.CURRENT;
this.lastUsed = new Instant();
this.siteName = siteName;
this.siteType = siteType;
this.siteCounter = siteCounter;
}
protected MPSite(final MPUser user, final MasterKey.Version algorithmVersion, final Instant lastUsed, final String siteName, return masterKey.siteResult(
final MPSiteType siteType, final UnsignedInteger siteCounter, final int uses, @Nullable final String loginName, getSiteName(), DEFAULT_COUNTER, MPKeyPurpose.Identification, null, loginType, loginContent, getAlgorithmVersion() );
@Nullable final String importContent) {
this.user = user;
this.algorithmVersion = algorithmVersion;
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, @Nullable final String context) {
return masterKey.encode( siteName, siteType, siteCounter, variant, context );
}
public MPUser getUser() {
return user;
}
@Nullable
protected String exportContent() {
return null;
}
public MasterKey.Version getAlgorithmVersion() {
return algorithmVersion;
}
public void setAlgorithmVersion(final MasterKey.Version mpVersion) {
this.algorithmVersion = mpVersion;
}
public Instant getLastUsed() {
return lastUsed;
}
public void updateLastUsed() {
lastUsed = new Instant();
user.updateLastUsed();
}
public String getSiteName() {
return siteName;
}
public void setSiteName(final String siteName) {
this.siteName = siteName;
}
public MPSiteType getSiteType() {
return siteType;
}
public void setSiteType(final MPSiteType siteType) {
this.siteType = siteType;
}
public UnsignedInteger getSiteCounter() {
return siteCounter;
}
public void setSiteCounter(final UnsignedInteger siteCounter) {
this.siteCounter = siteCounter;
}
public int getUses() {
return uses;
}
public void setUses(final int uses) {
this.uses = uses;
}
public String getLoginName() {
return loginName;
}
public void setLoginName(final String loginName) {
this.loginName = loginName;
} }
@Override @Override
public boolean equals(final Object obj) { public boolean equals(final Object obj) {
return (this == obj) || ((obj instanceof MPSite) && Objects.equals( siteName, ((MPSite) obj).siteName )); return (this == obj) || ((obj instanceof MPSite) && Objects.equals( getSiteName(), ((MPSite) obj).getSiteName() ));
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hashCode( siteName ); return Objects.hashCode( getSiteName() );
} }
@Override @Override
public String toString() { public String toString() {
return strf( "{MPSite: %s}", siteName ); return strf( "{%s: %s}", getClass().getSimpleName(), getSiteName() );
} }
} }

View File

@@ -1,131 +0,0 @@
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 (final 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 (final 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.contentMode = contentMode;
this.masterKey = masterKey;
StringBuilder header = new StringBuilder();
header.append( "# Master Password site export\n" );
header.append( "# " ).append( this.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.CURRENT.toBundleVersion() ).append( '\n' );
header.append( "# Algorithm: " ).append( MasterKey.Version.CURRENT.toInt() ).append( '\n' );
header.append( "# Default Type: " ).append( user.getDefaultType().getType() ).append( '\n' );
header.append( "# Passwords: " ).append( this.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(final 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.getAlgorithmVersion().toInt(), // algorithm
site.getSiteCounter().intValue() ), // 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(MPSite site, MasterKey masterKey);
}
}

View File

@@ -1,3 +1,21 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.model; package com.lyndir.masterpassword.model;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf; import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
@@ -10,13 +28,13 @@ import java.util.Objects;
*/ */
public class MPSiteResult { public class MPSiteResult {
private final MPSite site; private final MPFileSite site;
public MPSiteResult(final MPSite site) { public MPSiteResult(final MPFileSite site) {
this.site = site; this.site = site;
} }
public MPSite getSite() { public MPFileSite getSite() {
return site; return site;
} }

View File

@@ -1,163 +0,0 @@
package com.lyndir.masterpassword.model;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.io.CharStreams;
import com.google.common.primitives.UnsignedInteger;
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 com.lyndir.masterpassword.MasterKey;
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 = {
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;
@SuppressWarnings({ "FieldCanBeLocal", "unused" })
private final int mpVersion;
@SuppressWarnings({ "FieldCanBeLocal", "unused" })
private final boolean clearContent;
private final MPUser user;
@Nonnull
public static MPSiteUnmarshaller unmarshall(@Nonnull final File file)
throws IOException {
try (Reader reader = new InputStreamReader( new FileInputStream( file ), Charsets.UTF_8 )) {
return unmarshall( CharStreams.readLines( reader ) );
}
}
@Nonnull
public static MPSiteUnmarshaller unmarshall(@Nonnull final 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 (final 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 = "visible".equalsIgnoreCase( value );
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, MasterKey.Version.fromInt( mpVersion ), avatar, defaultType, new DateTime( 0 ) );
}
@Nullable
public MPSite unmarshallSite(@Nonnull final String siteLine) {
Matcher siteMatcher = unmarshallFormats[importFormat].matcher( siteLine );
if (!siteMatcher.matches())
return null;
MPSite site;
switch (importFormat) {
case 0:
site = new MPSite( user, //
MasterKey.Version.fromInt( ConversionUtils.toIntegerNN( siteMatcher.group( 4 ).replace( ":", "" ) ) ), //
rfc3339.parseDateTime( siteMatcher.group( 1 ) ).toInstant(), //
siteMatcher.group( 5 ), //
MPSiteType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ), MPSite.DEFAULT_COUNTER, //
ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ), //
null, //
siteMatcher.group( 6 ) );
break;
case 1:
site = new MPSite( user, //
MasterKey.Version.fromInt( ConversionUtils.toIntegerNN( siteMatcher.group( 4 ).replace( ":", "" ) ) ), //
rfc3339.parseDateTime( siteMatcher.group( 1 ) ).toInstant(), //
siteMatcher.group( 7 ), //
MPSiteType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),
UnsignedInteger.valueOf( 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

@@ -16,24 +16,21 @@
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>. // LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//============================================================================== //==============================================================================
package com.lyndir.masterpassword; package com.lyndir.masterpassword.model;
import java.io.*;
import javax.annotation.Nonnull;
/** /**
* <i>07 04, 2012</i> * @author lhunath, 14-12-07
*
* @author lhunath
*/ */
public enum MPSiteTypeClass { public interface MPUnmarshaller {
Generated( 1 << 4 ),
Stored( 1 << 5 );
private final int mask; @Nonnull
MPFileUser unmarshall(@Nonnull File file)
throws IOException;
MPSiteTypeClass(final int mask) { @Nonnull
this.mask = mask; MPFileUser unmarshall(@Nonnull String content);
}
public int getMask() {
return mask;
}
} }

View File

@@ -1,153 +1,86 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.model; package com.lyndir.masterpassword.model;
import static com.lyndir.lhunath.opal.system.util.StringUtils.*; import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import com.google.common.collect.ImmutableList; import com.google.common.base.Preconditions;
import com.google.common.collect.Sets;
import com.lyndir.lhunath.opal.system.CodeUtils; import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.masterpassword.MPSiteType; import com.lyndir.masterpassword.MPInvalidatedException;
import com.lyndir.masterpassword.MasterKey; import com.lyndir.masterpassword.MPMasterKey;
import java.util.*; import java.util.*;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.joda.time.*;
/** /**
* @author lhunath, 14-12-07 * @author lhunath, 2014-06-08
*/ */
public class MPUser implements Comparable<MPUser> { public abstract class MPUser<S extends MPSite> {
private final String fullName;
private final Collection<MPSite> sites = Sets.newHashSet();
@Nullable @Nullable
private byte[] keyID; protected MPMasterKey key;
private final MasterKey.Version algorithmVersion;
private int avatar;
private MPSiteType defaultType;
private ReadableInstant lastUsed;
public MPUser(final String fullName) { public abstract String getFullName();
this( fullName, null );
public boolean isMasterKeyAvailable() {
return key != null;
} }
public MPUser(final String fullName, @Nullable final byte[] keyID) {
this( fullName, keyID, MasterKey.Version.CURRENT, 0, MPSiteType.GeneratedLong, new DateTime() );
}
public MPUser(final String fullName, @Nullable final byte[] keyID, final MasterKey.Version algorithmVersion, final int avatar,
final MPSiteType defaultType, final ReadableInstant lastUsed) {
this.fullName = fullName;
this.keyID = (keyID == null)? null: keyID.clone();
this.algorithmVersion = algorithmVersion;
this.avatar = avatar;
this.defaultType = defaultType;
this.lastUsed = lastUsed;
}
public Collection<MPSiteResult> findSitesByName(final String query) {
ImmutableList.Builder<MPSiteResult> results = ImmutableList.builder();
for (final 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 void deleteSite(final MPSite site) {
sites.remove( site );
}
public String getFullName() {
return fullName;
}
public boolean hasKeyID() {
return keyID != null;
}
public String exportKeyID() {
return CodeUtils.encodeHex( keyID );
}
/**
* Performs an authentication attempt against the keyID for this user.
*
* Note: If this user doesn't have a keyID set yet, authentication will always succeed and the key ID will be set as a result.
*
* @param masterPassword The password to authenticate with.
*
* @return The master key for the user if authentication was successful.
*
* @throws IncorrectMasterPasswordException If authentication fails due to the given master password not matching the user's keyID.
*/
@Nonnull @Nonnull
@SuppressWarnings("MethodCanBeVariableArityMethod") public MPMasterKey getMasterKey() {
public MasterKey authenticate(final char[] masterPassword) return Preconditions.checkNotNull( key, "User is not authenticated: " + getFullName() );
throws IncorrectMasterPasswordException {
MasterKey masterKey = MasterKey.create( algorithmVersion, getFullName(), masterPassword );
if ((keyID == null) || (keyID.length == 0))
keyID = masterKey.getKeyID();
else if (!Arrays.equals( masterKey.getKeyID(), keyID ))
throw new IncorrectMasterPasswordException( this );
return masterKey;
} }
public String exportKeyID()
throws MPInvalidatedException {
return CodeUtils.encodeHex( getMasterKey().getKeyID( getAlgorithmVersion() ) );
}
public abstract MPMasterKey.Version getAlgorithmVersion();
public int getAvatar() { public int getAvatar() {
return avatar; return 0;
} }
public void setAvatar(final int avatar) { public abstract void addSite(S site);
this.avatar = avatar;
}
public MPSiteType getDefaultType() { public abstract void deleteSite(S site);
return defaultType;
}
public void setDefaultType(final MPSiteType defaultType) { public abstract Collection<S> findSites(String query);
this.defaultType = defaultType;
}
public ReadableInstant getLastUsed() { @Nonnull
return lastUsed; public abstract MPMasterKey authenticate(char[] masterPassword)
} throws MPIncorrectMasterPasswordException;
public void updateLastUsed() { @Override
lastUsed = new Instant(); public int hashCode() {
} return Objects.hashCode( getFullName() );
public Iterable<MPSite> getSites() {
return sites;
} }
@Override @Override
public boolean equals(final Object obj) { public boolean equals(final Object obj) {
return (this == obj) || ((obj instanceof MPUser) && Objects.equals( fullName, ((MPUser) obj).fullName )); return (this == obj) || ((obj instanceof MPUser) && Objects.equals( getFullName(), ((MPUser<?>) obj).getFullName() ));
}
@Override
public int hashCode() {
return Objects.hashCode( fullName );
} }
@Override @Override
public String toString() { public String toString() {
return strf( "{MPUser: %s}", fullName ); return strf( "{%s: %s}", getClass().getSimpleName(), getFullName() );
}
@Override
public int compareTo(final MPUser o) {
int comparison = lastUsed.compareTo( o.lastUsed );
if (comparison == 0)
comparison = fullName.compareTo( o.fullName );
return comparison;
} }
} }

View File

@@ -1,124 +0,0 @@
package com.lyndir.masterpassword.model;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
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 com.lyndir.masterpassword.MPConstant;
import java.io.*;
import javax.annotation.Nullable;
/**
* Manages user data stored in user-specific {@code .mpsites} files under {@code .mpw.d}.
* @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;
static {
String rcDir = System.getenv( MPConstant.env_rcDir );
if (rcDir != null)
instance = create( new File( rcDir ) );
else
instance = create( new File( ifNotNullElseNullable( System.getProperty( "user.home" ), System.getenv( "HOME" ) ), ".mpw.d" ) );
}
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( listUserFiles( userFilesDirectory ) ).transform( new Function<File, MPUser>() {
@Nullable
@Override
public MPUser apply(@Nullable final File file) {
try {
return MPSiteUnmarshaller.unmarshall( Preconditions.checkNotNull( file ) ).getUser();
}
catch (final IOException e) {
logger.err( e, "Couldn't read user from: %s", file );
return null;
}
}
} ).filter( Predicates.notNull() );
}
private static ImmutableList<File> listUserFiles(final File userFilesDirectory) {
return ImmutableList.copyOf( ifNotNullElse( userFilesDirectory.listFiles( new FilenameFilter() {
@Override
public boolean accept(final File dir, final String name) {
return name.endsWith( ".mpsites" );
}
} ), new File[0] ) );
}
@Override
public void addUser(final MPUser user) {
super.addUser( user );
save();
}
@Override
public void deleteUser(final MPUser user) {
super.deleteUser( user );
save();
}
/**
* Write the current user state to disk.
*/
public void save() {
// Save existing users.
for (final MPUser user : getUsers())
try {
new CharSink() {
@Override
public Writer openStream()
throws IOException {
File mpsitesFile = new File( userFilesDirectory, user.getFullName() + ".mpsites" );
return new OutputStreamWriter( new FileOutputStream( mpsitesFile ), Charsets.UTF_8 );
}
}.write( MPSiteMarshaller.marshallSafe( user ).getExport() );
}
catch (final IOException e) {
logger.err( e, "Unable to save sites for user: %s", user );
}
// Remove deleted users.
for (final File userFile : listUserFiles( userFilesDirectory ))
if (getUserNamed( userFile.getName().replaceFirst( "\\.mpsites$", "" ) ) == null)
if (!userFile.delete())
logger.err( "Couldn't delete file: %s", userFile );
}
/**
* @return The location on the file system where the user models are stored.
*/
public File getPath() {
return userFilesDirectory;
}
}

View File

@@ -1,6 +1,25 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword.model; package com.lyndir.masterpassword.model;
import com.google.common.collect.*; import com.google.common.collect.*;
import com.lyndir.masterpassword.MPInvalidatedException;
import java.util.*; import java.util.*;
@@ -9,31 +28,31 @@ import java.util.*;
*/ */
public abstract class MPUserManager { public abstract class MPUserManager {
private final Map<String, MPUser> usersByName = Maps.newHashMap(); private final Map<String, MPFileUser> usersByName = Maps.newHashMap();
static MPUserManager instance; static MPUserManager instance;
public static MPUserManager get() { public static MPUserManager get() {
return instance; return instance;
} }
protected MPUserManager(final Iterable<MPUser> users) { protected MPUserManager(final Iterable<MPFileUser> users) {
for (final MPUser user : users) for (final MPFileUser user : users)
usersByName.put( user.getFullName(), user ); usersByName.put( user.getFullName(), user );
} }
public SortedSet<MPUser> getUsers() { public SortedSet<MPFileUser> getUsers() {
return FluentIterable.from( usersByName.values() ).toSortedSet( Ordering.natural() ); return FluentIterable.from( usersByName.values() ).toSortedSet( Ordering.natural() );
} }
public MPUser getUserNamed(final String fullName) { public MPFileUser getUserNamed(final String fullName) {
return usersByName.get( fullName ); return usersByName.get( fullName );
} }
public void addUser(final MPUser user) { public void addUser(final MPFileUser user) {
usersByName.put( user.getFullName(), user ); usersByName.put( user.getFullName(), user );
} }
public void deleteUser(final MPUser user) { public void deleteUser(final MPFileUser user) {
usersByName.remove( user.getFullName() ); usersByName.remove( user.getFullName() );
} }
} }

View File

@@ -1,3 +1,21 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
/** /**
* *
* @author lhunath, 15-02-04 * @author lhunath, 15-02-04

View File

@@ -23,12 +23,10 @@ import com.google.common.collect.Lists;
import com.google.common.primitives.UnsignedInteger; import com.google.common.primitives.UnsignedInteger;
import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.ConversionUtils; import com.lyndir.lhunath.opal.system.util.ConversionUtils;
import com.lyndir.lhunath.opal.system.util.NNFunctionNN;
import java.io.IOException; import java.io.IOException;
import java.util.Deque; import java.util.Deque;
import java.util.List; import java.util.List;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import javax.annotation.Nonnull;
import javax.xml.parsers.*; import javax.xml.parsers.*;
import org.xml.sax.Attributes; import org.xml.sax.Attributes;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
@@ -38,7 +36,7 @@ import org.xml.sax.ext.DefaultHandler2;
/** /**
* @author lhunath, 2015-12-22 * @author lhunath, 2015-12-22
*/ */
@SuppressWarnings("HardCodedStringLiteral") @SuppressWarnings({ "HardCodedStringLiteral", "ProhibitedExceptionDeclared" })
public class MPTestSuite implements Callable<Boolean> { public class MPTestSuite implements Callable<Boolean> {
@SuppressWarnings("UnusedDeclaration") @SuppressWarnings("UnusedDeclaration")
@@ -99,12 +97,12 @@ public class MPTestSuite implements Callable<Boolean> {
currentCase.siteName = text; currentCase.siteName = text;
if ("siteCounter".equals( qName )) if ("siteCounter".equals( qName ))
currentCase.siteCounter = text.isEmpty()? null: UnsignedInteger.valueOf( text ); currentCase.siteCounter = text.isEmpty()? null: UnsignedInteger.valueOf( text );
if ("siteType".equals( qName )) if ("resultType".equals( qName ))
currentCase.siteType = text; currentCase.resultType = text;
if ("siteVariant".equals( qName )) if ("keyPurpose".equals( qName ))
currentCase.siteVariant = text; currentCase.keyPurpose = text;
if ("siteContext".equals( qName )) if ("keyContext".equals( qName ))
currentCase.siteContext = text; currentCase.keyContext = text;
if ("result".equals( qName )) if ("result".equals( qName ))
currentCase.result = text; currentCase.result = text;
} }
@@ -134,7 +132,8 @@ public class MPTestSuite implements Callable<Boolean> {
return tests; return tests;
} }
public boolean forEach(final String testName, final NNFunctionNN<MPTests.Case, Boolean> testFunction) { public boolean forEach(final String testName, final TestCase testFunction)
throws Exception {
List<MPTests.Case> cases = tests.getCases(); List<MPTests.Case> cases = tests.getCases();
for (int c = 0; c < cases.size(); c++) { for (int c = 0; c < cases.size(); c++) {
MPTests.Case testCase = cases.get( c ); MPTests.Case testCase = cases.get( c );
@@ -144,7 +143,7 @@ public class MPTestSuite implements Callable<Boolean> {
progress( Logger.Target.INFO, c, cases.size(), // progress( Logger.Target.INFO, c, cases.size(), //
"[%s] on %s...", testName, testCase.getIdentifier() ); "[%s] on %s...", testName, testCase.getIdentifier() );
if (!testFunction.apply( testCase )) { if (!testFunction.run( testCase )) {
progress( Logger.Target.ERROR, cases.size(), cases.size(), // progress( Logger.Target.ERROR, cases.size(), cases.size(), //
"[%s] on %s: FAILED!", testName, testCase.getIdentifier() ); "[%s] on %s: FAILED!", testName, testCase.getIdentifier() );
@@ -168,13 +167,14 @@ public class MPTestSuite implements Callable<Boolean> {
@Override @Override
public Boolean call() public Boolean call()
throws Exception { throws Exception {
return forEach( "mpw", new NNFunctionNN<MPTests.Case, Boolean>() { return forEach( "mpw", new TestCase() {
@Nonnull
@Override @Override
public Boolean apply(@Nonnull final MPTests.Case testCase) { public boolean run(final MPTests.Case testCase)
MasterKey masterKey = MasterKey.create( testCase.getAlgorithm(), testCase.getFullName(), testCase.getMasterPassword() ); throws Exception {
String sitePassword = masterKey.encode( testCase.getSiteName(), testCase.getSiteType(), testCase.getSiteCounter(), MPMasterKey masterKey = new MPMasterKey( testCase.getFullName(), testCase.getMasterPassword().toCharArray() );
testCase.getSiteVariant(), testCase.getSiteContext() ); String sitePassword = masterKey.siteResult( testCase.getSiteName(), testCase.getSiteCounter(), testCase.getKeyPurpose(),
testCase.getKeyContext(), testCase.getResultType(),
null, testCase.getAlgorithm() );
return testCase.getResult().equals( sitePassword ); return testCase.getResult().equals( sitePassword );
} }
@@ -195,4 +195,11 @@ public class MPTestSuite implements Callable<Boolean> {
void progress(int current, int max, String messageFormat, Object... args); void progress(int current, int max, String messageFormat, Object... args);
} }
public interface TestCase {
boolean run(MPTests.Case testCase)
throws Exception;
}
} }

View File

@@ -74,9 +74,9 @@ public class MPTests {
String keyID; String keyID;
String siteName; String siteName;
UnsignedInteger siteCounter; UnsignedInteger siteCounter;
String siteType; String resultType;
String siteVariant; String keyPurpose;
String siteContext; String keyContext;
String result; String result;
private transient Case parentCase; private transient Case parentCase;
@@ -130,25 +130,25 @@ public class MPTests {
return checkNotNull( parentCase.siteCounter ); return checkNotNull( parentCase.siteCounter );
} }
} ); } );
siteType = ifNotNullElse( siteType, new NNSupplier<String>() { resultType = ifNotNullElse( resultType, new NNSupplier<String>() {
@Nonnull @Nonnull
@Override @Override
public String get() { public String get() {
return checkNotNull( parentCase.siteType ); return checkNotNull( parentCase.resultType );
} }
} ); } );
siteVariant = ifNotNullElse( siteVariant, new NNSupplier<String>() { keyPurpose = ifNotNullElse( keyPurpose, new NNSupplier<String>() {
@Nonnull @Nonnull
@Override @Override
public String get() { public String get() {
return checkNotNull( parentCase.siteVariant ); return checkNotNull( parentCase.keyPurpose );
} }
} ); } );
siteContext = ifNotNullElse( siteContext, new NNSupplier<String>() { keyContext = ifNotNullElse( keyContext, new NNSupplier<String>() {
@Nonnull @Nonnull
@Override @Override
public String get() { public String get() {
return (parentCase == null)? "": checkNotNull( parentCase.siteContext ); return (parentCase == null)? "": checkNotNull( parentCase.keyContext );
} }
} ); } );
result = ifNotNullElse( result, new NNSupplier<String>() { result = ifNotNullElse( result, new NNSupplier<String>() {
@@ -171,8 +171,8 @@ public class MPTests {
} }
@Nonnull @Nonnull
public MasterKey.Version getAlgorithm() { public MPMasterKey.Version getAlgorithm() {
return MasterKey.Version.fromInt( checkNotNull( algorithm ) ); return MPMasterKey.Version.fromInt( checkNotNull( algorithm ) );
} }
@Nonnull @Nonnull
@@ -181,8 +181,8 @@ public class MPTests {
} }
@Nonnull @Nonnull
public char[] getMasterPassword() { public String getMasterPassword() {
return checkNotNull( masterPassword ).toCharArray(); return checkNotNull( masterPassword );
} }
@Nonnull @Nonnull
@@ -200,18 +200,18 @@ public class MPTests {
} }
@Nonnull @Nonnull
public MPSiteType getSiteType() { public MPResultType getResultType() {
return MPSiteType.forName( checkNotNull( siteType ) ); return MPResultType.forName( checkNotNull( resultType ) );
} }
@Nonnull @Nonnull
public MPSiteVariant getSiteVariant() { public MPKeyPurpose getKeyPurpose() {
return MPSiteVariant.forName( checkNotNull( siteVariant ) ); return MPKeyPurpose.forName( checkNotNull( keyPurpose ) );
} }
@Nonnull @Nonnull
public String getSiteContext() { public String getKeyContext() {
return checkNotNull( siteContext ); return checkNotNull( keyContext );
} }
@Nonnull @Nonnull

View File

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

View File

@@ -0,0 +1 @@
../../../../../mpw_tests.xml

View File

@@ -0,0 +1,134 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword;
import static org.testng.Assert.*;
import com.google.common.base.Charsets;
import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.util.Random;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
public class MPMasterKeyTest {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MPMasterKeyTest.class );
private MPTestSuite testSuite;
@BeforeMethod
public void setUp()
throws Exception {
testSuite = new MPTestSuite();
}
@Test
public void testMasterKey()
throws Exception {
testSuite.forEach( "testMasterKey", new MPTestSuite.TestCase() {
@Override
public boolean run(final MPTests.Case testCase)
throws Exception {
char[] masterPassword = testCase.getMasterPassword().toCharArray();
MPMasterKey masterKey = new MPMasterKey( testCase.getFullName(), masterPassword );
// Test key
assertEquals(
CodeUtils.encodeHex( masterKey.getKeyID( testCase.getAlgorithm() ) ),
testCase.getKeyID(),
"[testMasterKey] keyID mismatch: " + testCase );
// Test invalidation
masterKey.invalidate();
try {
masterKey.getKeyID( testCase.getAlgorithm() );
fail( "[testMasterKey] invalidate ineffective: " + testCase );
}
catch (final MPInvalidatedException ignored) {
}
assertNotEquals(
masterPassword,
testCase.getMasterPassword().toCharArray(),
"[testMasterKey] masterPassword not wiped: " + testCase );
return true;
}
} );
}
@Test
public void testSiteResult()
throws Exception {
testSuite.forEach( "testSiteResult", new MPTestSuite.TestCase() {
@Override
public boolean run(final MPTests.Case testCase)
throws Exception {
char[] masterPassword = testCase.getMasterPassword().toCharArray();
MPMasterKey masterKey = new MPMasterKey( testCase.getFullName(), masterPassword );
// Test site result
assertEquals(
masterKey.siteResult( testCase.getSiteName(), testCase.getSiteCounter(), testCase.getKeyPurpose(),
testCase.getKeyContext(), testCase.getResultType(),
null, testCase.getAlgorithm() ),
testCase.getResult(),
"[testSiteResult] result mismatch: " + testCase );
return true;
}
} );
}
@Test
public void testSiteState()
throws Exception {
MPTests.Case testCase = testSuite.getTests().getDefaultCase();
char[] masterPassword = testCase.getMasterPassword().toCharArray();
MPMasterKey masterKey = new MPMasterKey( testCase.getFullName(), masterPassword );
Random random = new Random();
StringBuilder password = new StringBuilder();
for (int p = 0; p < 8; ++p)
password.append( (char) (random.nextInt( Character.MAX_CODE_POINT - Character.MIN_CODE_POINT ) + Character.MIN_CODE_POINT) );
logger.dbg( "password: %s", CodeUtils.encodeHex( password.toString().getBytes( Charsets.UTF_8 ) ) );
for (final MPMasterKey.Version version : MPMasterKey.Version.values()) {
MPResultType resultType = MPResultType.StoredPersonal;
// Test site state
String state = masterKey.siteState( testCase.getSiteName(), testCase.getSiteCounter(), testCase.getKeyPurpose(),
testCase.getKeyContext(), resultType, password.toString(), version );
String result = masterKey.siteResult( testCase.getSiteName(), testCase.getSiteCounter(), testCase.getKeyPurpose(),
testCase.getKeyContext(), resultType, state, version );
logger.dbg( "result: %s", CodeUtils.encodeHex( result.getBytes( Charsets.UTF_8 ) ) );
assertEquals(
result,
password.toString(),
"[testSiteState] state mismatch: " + testCase );
}
}
}

View File

@@ -1,112 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword;
import static org.testng.Assert.*;
import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.NNFunctionNN;
import javax.annotation.Nonnull;
import org.jetbrains.annotations.NonNls;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
public class MasterKeyTest {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MasterKeyTest.class );
@NonNls
private MPTestSuite testSuite;
@BeforeMethod
public void setUp()
throws Exception {
testSuite = new MPTestSuite();
}
@Test
public void testEncode()
throws Exception {
testSuite.forEach( "testEncode", new NNFunctionNN<MPTests.Case, Boolean>() {
@Nonnull
@Override
public Boolean apply(@Nonnull final MPTests.Case testCase) {
MasterKey masterKey = MasterKey.create( testCase.getAlgorithm(), testCase.getFullName(), testCase.getMasterPassword() );
assertEquals(
masterKey.encode( testCase.getSiteName(), testCase.getSiteType(), testCase.getSiteCounter(),
testCase.getSiteVariant(), testCase.getSiteContext() ),
testCase.getResult(), "[testEncode] Failed test case: " + testCase );
return true;
}
} );
}
@Test
public void testGetUserName()
throws Exception {
MPTests.Case defaultCase = testSuite.getTests().getDefaultCase();
assertEquals( MasterKey.create( defaultCase.getFullName(), defaultCase.getMasterPassword() ).getFullName(),
defaultCase.getFullName(), "[testGetUserName] Failed test case: " + defaultCase );
}
@Test
public void testGetKeyID()
throws Exception {
testSuite.forEach( "testGetKeyID", new NNFunctionNN<MPTests.Case, Boolean>() {
@Nonnull
@Override
public Boolean apply(@Nonnull final MPTests.Case testCase) {
MasterKey masterKey = MasterKey.create( testCase.getFullName(), testCase.getMasterPassword() );
assertEquals( CodeUtils.encodeHex( masterKey.getKeyID() ),
testCase.getKeyID(), "[testGetKeyID] Failed test case: " + testCase );
return true;
}
} );
}
@Test
public void testInvalidate()
throws Exception {
try {
MPTests.Case defaultCase = testSuite.getTests().getDefaultCase();
MasterKey masterKey = MasterKey.create( defaultCase.getFullName(), defaultCase.getMasterPassword() );
masterKey.invalidate();
masterKey.encode( defaultCase.getSiteName(), defaultCase.getSiteType(), defaultCase.getSiteCounter(),
defaultCase.getSiteVariant(), defaultCase.getSiteContext() );
fail( "[testInvalidate] Master key should have been invalidated, but was still usable." );
}
catch (final IllegalStateException ignored) {
}
}
}

View File

@@ -6,7 +6,7 @@
</encoder> </encoder>
</appender> </appender>
<logger name="com.lyndir" level="${mp.log.level:-INFO}" /> <logger name="com.lyndir" level="${mp.log.level:-TRACE}" />
<root level="INFO"> <root level="INFO">
<appender-ref ref="stdout" /> <appender-ref ref="stdout" />

279
core/mpw_tests.xml Normal file
View File

@@ -0,0 +1,279 @@
<tests>
<!-- Default values for all parameters. -->
<case id="default">
<algorithm>-1</algorithm>
<fullName>Robert Lee Mitchell</fullName>
<masterPassword>banana colored duckling</masterPassword>
<keyID>98EEF4D1DF46D849574A82A03C3177056B15DFFCA29BB3899DE4628453675302</keyID>
<siteName>masterpasswordapp.com</siteName>
<siteCounter>1</siteCounter>
<resultType>Long</resultType>
<keyPurpose>Authentication</keyPurpose>
<result><!-- abstract --></result>
</case>
<!-- Algorithm 3 -->
<case id="v3" parent="default">
<algorithm>3</algorithm>
<result>Jejr5[RepuSosp</result>
</case>
<case id="v3_mb_fullName" parent="v3">
<fullName></fullName>
<keyID>1717AA1F9BF5BA56CD0965CDA3D78E6D2E6A1EA8C067A8EA621F3DDAD4A87EB8</keyID>
<result>NopaDajh8=Fene</result>
</case>
<case id="v3_mb_masterPassword" parent="v3">
<masterPassword></masterPassword>
<keyID>351432B8528A5ABECAB768CA95015097DE76FE14C41E10AF36C67DCFB8917E08</keyID>
<result>QesuHirv5-Xepl</result>
</case>
<case id="v3_mb_siteName" parent="v3">
<siteName></siteName>
<result>LiheCuwhSerz6)</result>
</case>
<case id="v3_loginName" parent="v3">
<keyPurpose>Identification</keyPurpose>
<resultType>Name</resultType>
<result>wohzaqage</result>
</case>
<case id="v3_securityAnswer" parent="v3">
<keyPurpose>Recovery</keyPurpose>
<resultType>Phrase</resultType>
<result>xin diyjiqoja hubu</result>
</case>
<case id="v3_securityAnswer_context" parent="v3_securityAnswer">
<keyContext>question</keyContext>
<result>xogx tem cegyiva jab</result>
</case>
<case id="v3_type_maximum" parent="v3">
<resultType>Maximum</resultType>
<result>W6@692^B1#&amp;@gVdSdLZ@</result>
</case>
<case id="v3_type_medium" parent="v3">
<resultType>Medium</resultType>
<result>Jej2$Quv</result>
</case>
<case id="v3_type_basic" parent="v3">
<resultType>Basic</resultType>
<result>WAo2xIg6</result>
</case>
<case id="v3_type_short" parent="v3">
<resultType>Short</resultType>
<result>Jej2</result>
</case>
<case id="v3_type_pin" parent="v3">
<resultType>PIN</resultType>
<result>7662</result>
</case>
<case id="v3_type_name" parent="v3">
<resultType>Name</resultType>
<result>jejraquvo</result>
</case>
<case id="v3_type_phrase" parent="v3">
<resultType>Phrase</resultType>
<result>jejr quv cabsibu tam</result>
</case>
<case id="v3_counter_ceiling" parent="v3">
<siteCounter>4294967295</siteCounter>
<result>XambHoqo6[Peni</result>
</case>
<!-- Algorithm 2 -->
<case id="v2" parent="default">
<algorithm>2</algorithm>
<result>Jejr5[RepuSosp</result>
</case>
<case id="v2_mb_fullName" parent="v2">
<fullName></fullName>
<keyID>4D5851D0B093D65DE0CF13D94877270468C0B65A6E42CA50D393AC9B99C457B5</keyID>
<result>WaqoGuho2[Xaxw</result>
</case>
<case id="v2_mb_masterPassword" parent="v2">
<masterPassword></masterPassword>
<keyID>351432B8528A5ABECAB768CA95015097DE76FE14C41E10AF36C67DCFB8917E08</keyID>
<result>QesuHirv5-Xepl</result>
</case>
<case id="v2_mb_siteName" parent="v2">
<siteName></siteName>
<result>LiheCuwhSerz6)</result>
</case>
<case id="v2_loginName" parent="v2">
<keyPurpose>Identification</keyPurpose>
<resultType>Name</resultType>
<result>wohzaqage</result>
</case>
<case id="v2_securityAnswer" parent="v2">
<keyPurpose>Recovery</keyPurpose>
<resultType>Phrase</resultType>
<result>xin diyjiqoja hubu</result>
</case>
<case id="v2_securityAnswer_context" parent="v2_securityAnswer">
<keyContext>question</keyContext>
<result>xogx tem cegyiva jab</result>
</case>
<case id="v2_type_maximum" parent="v2">
<resultType>Maximum</resultType>
<result>W6@692^B1#&amp;@gVdSdLZ@</result>
</case>
<case id="v2_type_medium" parent="v2">
<resultType>Medium</resultType>
<result>Jej2$Quv</result>
</case>
<case id="v2_type_basic" parent="v2">
<resultType>Basic</resultType>
<result>WAo2xIg6</result>
</case>
<case id="v2_type_short" parent="v2">
<resultType>Short</resultType>
<result>Jej2</result>
</case>
<case id="v2_type_pin" parent="v2">
<resultType>PIN</resultType>
<result>7662</result>
</case>
<case id="v2_type_name" parent="v2">
<resultType>Name</resultType>
<result>jejraquvo</result>
</case>
<case id="v2_type_phrase" parent="v2">
<resultType>Phrase</resultType>
<result>jejr quv cabsibu tam</result>
</case>
<case id="v2_counter_ceiling" parent="v2">
<siteCounter>4294967295</siteCounter>
<result>XambHoqo6[Peni</result>
</case>
<!-- Algorithm 1 -->
<case id="v1" parent="default">
<algorithm>1</algorithm>
<result>Jejr5[RepuSosp</result>
</case>
<case id="v1_mb_fullName" parent="v1">
<fullName></fullName>
<keyID>4D5851D0B093D65DE0CF13D94877270468C0B65A6E42CA50D393AC9B99C457B5</keyID>
<result>WaqoGuho2[Xaxw</result>
</case>
<case id="v1_mb_masterPassword" parent="v1">
<masterPassword></masterPassword>
<keyID>351432B8528A5ABECAB768CA95015097DE76FE14C41E10AF36C67DCFB8917E08</keyID>
<result>QesuHirv5-Xepl</result>
</case>
<case id="v1_mb_siteName" parent="v1">
<siteName></siteName>
<result>WawiYarp2@Kodh</result>
</case>
<case id="v1_loginName" parent="v1">
<keyPurpose>Identification</keyPurpose>
<resultType>Name</resultType>
<result>wohzaqage</result>
</case>
<case id="v1_securityAnswer" parent="v1">
<keyPurpose>Recovery</keyPurpose>
<resultType>Phrase</resultType>
<result>xin diyjiqoja hubu</result>
</case>
<case id="v1_securityAnswer_context" parent="v1_securityAnswer">
<keyContext>question</keyContext>
<result>xogx tem cegyiva jab</result>
</case>
<case id="v1_type_maximum" parent="v1">
<resultType>Maximum</resultType>
<result>W6@692^B1#&amp;@gVdSdLZ@</result>
</case>
<case id="v1_type_medium" parent="v1">
<resultType>Medium</resultType>
<result>Jej2$Quv</result>
</case>
<case id="v1_type_basic" parent="v1">
<resultType>Basic</resultType>
<result>WAo2xIg6</result>
</case>
<case id="v1_type_short" parent="v1">
<resultType>Short</resultType>
<result>Jej2</result>
</case>
<case id="v1_type_pin" parent="v1">
<resultType>PIN</resultType>
<result>7662</result>
</case>
<case id="v1_type_name" parent="v1">
<resultType>Name</resultType>
<result>jejraquvo</result>
</case>
<case id="v1_type_phrase" parent="v1">
<resultType>Phrase</resultType>
<result>jejr quv cabsibu tam</result>
</case>
<case id="v1_counter_ceiling" parent="v1">
<siteCounter>4294967295</siteCounter>
<result>XambHoqo6[Peni</result>
</case>
<!-- Algorithm 0 -->
<case id="v0" parent="default">
<algorithm>0</algorithm>
<result>Feji5@ReduWosh</result>
</case>
<case id="v0_mb_fullName" parent="v0">
<fullName></fullName>
<keyID>4D5851D0B093D65DE0CF13D94877270468C0B65A6E42CA50D393AC9B99C457B5</keyID>
<result>HajrYudo7@Mamh</result>
</case>
<case id="v0_mb_masterPassword" parent="v0">
<masterPassword></masterPassword>
<keyID>351432B8528A5ABECAB768CA95015097DE76FE14C41E10AF36C67DCFB8917E08</keyID>
<result>MewmDini0]Meho</result>
</case>
<case id="v0_mb_siteName" parent="v0">
<siteName></siteName>
<result>HahiVana2@Nole</result>
</case>
<case id="v0_loginName" parent="v0">
<keyPurpose>Identification</keyPurpose>
<resultType>Name</resultType>
<result>lozwajave</result>
</case>
<case id="v0_securityAnswer" parent="v0">
<keyPurpose>Recovery</keyPurpose>
<resultType>Phrase</resultType>
<result>miy lirfijoja dubu</result>
</case>
<case id="v0_securityAnswer_context" parent="v0_securityAnswer">
<keyContext>question</keyContext>
<result>movm bex gevrica jaf</result>
</case>
<case id="v0_type_maximum" parent="v0">
<resultType>Maximum</resultType>
<result>w1!3bA3icmRAc)SS@lwl</result>
</case>
<case id="v0_type_medium" parent="v0">
<resultType>Medium</resultType>
<result>Fej7]Jug</result>
</case>
<case id="v0_type_basic" parent="v0">
<resultType>Basic</resultType>
<result>wvH7irC1</result>
</case>
<case id="v0_type_short" parent="v0">
<resultType>Short</resultType>
<result>Fej7</result>
</case>
<case id="v0_type_pin" parent="v0">
<resultType>PIN</resultType>
<result>2117</result>
</case>
<case id="v0_type_name" parent="v0">
<resultType>Name</resultType>
<result>fejrajugo</result>
</case>
<case id="v0_type_phrase" parent="v0">
<resultType>Phrase</resultType>
<result>fejr jug gabsibu bax</result>
</case>
<case id="v0_counter_ceiling" parent="v0">
<siteCounter>4294967295</siteCounter>
<result>QateDojh1@Hecn</result>
</case>
</tests>

View File

@@ -6,20 +6,18 @@
<option name="PM_INSTALL_OPTIONS" value="" /> <option name="PM_INSTALL_OPTIONS" value="" />
<option name="ACTIVITY_EXTRA_FLAGS" value="" /> <option name="ACTIVITY_EXTRA_FLAGS" value="" />
<option name="MODE" value="default_activity" /> <option name="MODE" value="default_activity" />
<option name="TARGET_SELECTION_MODE" value="SHOW_DIALOG" />
<option name="PREFERRED_AVD" value="" /> <option name="PREFERRED_AVD" value="" />
<option name="CLEAR_LOGCAT" value="false" /> <option name="CLEAR_LOGCAT" value="false" />
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" /> <option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
<option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" /> <option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
<option name="FORCE_STOP_RUNNING_APP" value="true" /> <option name="FORCE_STOP_RUNNING_APP" value="true" />
<option name="DEBUGGER_TYPE" value="Java" /> <option name="TARGET_SELECTION_MODE" value="SHOW_DIALOG" />
<option name="USE_LAST_SELECTED_DEVICE" value="false" /> <option name="USE_LAST_SELECTED_DEVICE" value="false" />
<option name="PREFERRED_AVD" value="" /> <option name="PREFERRED_AVD" value="" />
<option name="DEBUGGER_TYPE" value="Java" />
<Java /> <Java />
<Profilers> <Profilers>
<option name="ENABLE_ADVANCED_PROFILING" value="false" /> <option name="ENABLE_ADVANCED_PROFILING" value="false" />
<option name="GAPID_ENABLED" value="false" />
<option name="GAPID_DISABLE_PCS" value="false" />
<option name="SUPPORT_LIB_ENABLED" value="true" /> <option name="SUPPORT_LIB_ENABLED" value="true" />
<option name="INSTRUMENTATION_ENABLED" value="true" /> <option name="INSTRUMENTATION_ENABLED" value="true" />
</Profilers> </Profilers>

View File

@@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="GUI" type="Application" factoryName="Application"> <configuration default="false" name="GUI" type="Application" factoryName="Application" show_console_on_std_err="true">
<option name="MAIN_CLASS_NAME" value="com.lyndir.masterpassword.gui.GUI" /> <option name="MAIN_CLASS_NAME" value="com.lyndir.masterpassword.gui.GUI" />
<option name="VM_PARAMETERS" value="" /> <option name="VM_PARAMETERS" value="" />
<option name="PROGRAM_PARAMETERS" value="" /> <option name="PROGRAM_PARAMETERS" value="" />
@@ -9,7 +9,7 @@
<option name="ENABLE_SWING_INSPECTOR" value="false" /> <option name="ENABLE_SWING_INSPECTOR" value="false" />
<option name="ENV_VARIABLES" /> <option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" /> <option name="PASS_PARENT_ENVS" value="true" />
<module name="gui" /> <module name="masterpassword-gui" />
<envs /> <envs />
<method /> <method />
</configuration> </configuration>

View File

@@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="Tests" type="TestNG" factoryName="TestNG"> <configuration default="false" name="Tests" type="TestNG" factoryName="TestNG" show_console_on_std_err="true">
<module name="tests" /> <module name="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" /> <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" /> <option name="ALTERNATIVE_JRE_PATH" />
<option name="SUITE_NAME" value="" /> <option name="SUITE_NAME" value="" />
@@ -17,7 +17,7 @@
<option name="ENV_VARIABLES" /> <option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" /> <option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE"> <option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" /> <value defaultName="wholeProject" />
</option> </option>
<option name="USE_DEFAULT_REPORTERS" value="false" /> <option name="USE_DEFAULT_REPORTERS" value="false" />
<option name="PROPERTIES_FILE" value="" /> <option name="PROPERTIES_FILE" value="" />

View File

@@ -1,3 +1,3 @@
<component name="DependencyValidationManager"> <component name="DependencyValidationManager">
<scope name="masterpassword" pattern="com.lyndir.masterpassword.*" /> <scope name="masterpassword" pattern="com.lyndir.masterpassword..*" />
</component> </component>

View File

@@ -22,7 +22,7 @@ buildscript {
} }
dependencies { dependencies {
classpath group: 'com.android.tools.build', name: 'gradle', version: '2.2.3' classpath group: 'com.android.tools.build', name: 'gradle', version: '2.3.0'
} }
} }

View File

@@ -22,7 +22,6 @@
<module>masterpassword-tests</module> <module>masterpassword-tests</module>
<module>masterpassword-algorithm</module> <module>masterpassword-algorithm</module>
<module>masterpassword-model</module> <module>masterpassword-model</module>
<module>masterpassword-cli</module>
<module>masterpassword-gui</module> <module>masterpassword-gui</module>
</modules> </modules>

View File

@@ -1,5 +1,11 @@
rootProject.name = 'masterpassword' rootProject.name = 'masterpassword'
def local = new Properties();
try {
local.load(file('local.properties').newDataInputStream())
} catch (FileNotFoundException ignored) {
}
include 'masterpassword-algorithm' include 'masterpassword-algorithm'
project(':masterpassword-algorithm').projectDir = new File( '../core/java/algorithm' ) project(':masterpassword-algorithm').projectDir = new File( '../core/java/algorithm' )
@@ -9,11 +15,12 @@ project(':masterpassword-model').projectDir = new File( '../core/java/model' )
include 'masterpassword-tests' include 'masterpassword-tests'
project(':masterpassword-tests').projectDir = new File( '../core/java/tests' ) project(':masterpassword-tests').projectDir = new File( '../core/java/tests' )
include 'masterpassword-cli'
project(':masterpassword-cli').projectDir = new File( '../platform-independent/cli-java' )
include 'masterpassword-gui' include 'masterpassword-gui'
project(':masterpassword-gui').projectDir = new File( '../platform-independent/gui-java' ) project(':masterpassword-gui').projectDir = new File( '../platform-independent/gui-java' )
include 'masterpassword-android' if (local.containsKey('sdk.dir')) {
project(':masterpassword-android').projectDir = new File( '../platform-android' ) include 'masterpassword-android'
project(':masterpassword-android').projectDir = new File( '../platform-android' )
} else {
logger.warn( "Skipping masterpassword-android since sdk.dir is not defined in local.properties." )
}

View File

@@ -33,7 +33,6 @@ import android.view.WindowManager;
import android.widget.*; import android.widget.*;
import butterknife.BindView; import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.primitives.UnsignedInteger; import com.google.common.primitives.UnsignedInteger;
import com.google.common.util.concurrent.*; import com.google.common.util.concurrent.*;
@@ -54,10 +53,10 @@ public class EmergencyActivity extends Activity {
private final Preferences preferences = Preferences.get( this ); private final Preferences preferences = Preferences.get( this );
private final ListeningExecutorService executor = MoreExecutors.listeningDecorator( Executors.newSingleThreadExecutor() ); private final ListeningExecutorService executor = MoreExecutors.listeningDecorator( Executors.newSingleThreadExecutor() );
private final ImmutableList<MPSiteType> allSiteTypes = ImmutableList.copyOf( MPSiteType.forClass( MPSiteTypeClass.Generated ) ); private final ImmutableList<MPResultType> allResultTypes = ImmutableList.copyOf( MPResultType.forClass( MPResultTypeClass.Template ) );
private final ImmutableList<MasterKey.Version> allVersions = ImmutableList.copyOf( MasterKey.Version.values() ); private final ImmutableList<MPMasterKey.Version> allVersions = ImmutableList.copyOf( MPMasterKey.Version.values() );
private ListenableFuture<MasterKey> masterKeyFuture; private MPMasterKey masterKey;
@BindView(R.id.progressView) @BindView(R.id.progressView)
ProgressBar progressView; ProgressBar progressView;
@@ -71,8 +70,8 @@ public class EmergencyActivity extends Activity {
@BindView(R.id.siteNameField) @BindView(R.id.siteNameField)
EditText siteNameField; EditText siteNameField;
@BindView(R.id.siteTypeButton) @BindView(R.id.resultTypeButton)
Button siteTypeButton; Button resultTypeButton;
@BindView(R.id.counterField) @BindView(R.id.counterField)
Button siteCounterButton; Button siteCounterButton;
@@ -97,7 +96,6 @@ public class EmergencyActivity extends Activity {
private int id_userName; private int id_userName;
private int id_masterPassword; private int id_masterPassword;
private int id_version;
private String sitePassword; private String sitePassword;
public static void start(final Context context) { public static void start(final Context context) {
@@ -131,15 +129,15 @@ public class EmergencyActivity extends Activity {
updateSitePassword(); updateSitePassword();
} }
} ); } );
siteTypeButton.setOnClickListener( new View.OnClickListener() { resultTypeButton.setOnClickListener( new View.OnClickListener() {
@Override @Override
public void onClick(final View v) { public void onClick(final View v) {
@SuppressWarnings("SuspiciousMethodCalls") @SuppressWarnings("SuspiciousMethodCalls")
MPSiteType siteType = MPResultType resultType =
allSiteTypes.get( (allSiteTypes.indexOf( siteTypeButton.getTag() ) + 1) % allSiteTypes.size() ); allResultTypes.get( (allResultTypes.indexOf( resultTypeButton.getTag() ) + 1) % allResultTypes.size() );
preferences.setDefaultSiteType( siteType ); preferences.setDefaultResultType( resultType );
siteTypeButton.setTag( siteType ); resultTypeButton.setTag( resultType );
siteTypeButton.setText( siteType.getShortName() ); resultTypeButton.setText( resultType.getShortName() );
updateSitePassword(); updateSitePassword();
} }
} ); } );
@@ -156,7 +154,7 @@ public class EmergencyActivity extends Activity {
@Override @Override
public void onClick(final View v) { public void onClick(final View v) {
@SuppressWarnings("SuspiciousMethodCalls") @SuppressWarnings("SuspiciousMethodCalls")
MasterKey.Version siteVersion = MPMasterKey.Version siteVersion =
allVersions.get( (allVersions.indexOf( siteVersionButton.getTag() ) + 1) % allVersions.size() ); allVersions.get( (allVersions.indexOf( siteVersionButton.getTag() ) + 1) % allVersions.size() );
preferences.setDefaultVersion( siteVersion ); preferences.setDefaultVersion( siteVersion );
siteVersionButton.setTag( siteVersion ); siteVersionButton.setTag( siteVersion );
@@ -213,17 +211,17 @@ public class EmergencyActivity extends Activity {
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
MasterKey.setAllowNativeByDefault( preferences.isAllowNativeKDF() ); // FIXME: MasterKey.setAllowNativeByDefault( preferences.isAllowNativeKDF() );
fullNameField.setText( preferences.getFullName() ); fullNameField.setText( preferences.getFullName() );
rememberFullNameField.setChecked( preferences.isRememberFullName() ); rememberFullNameField.setChecked( preferences.isRememberFullName() );
forgetPasswordField.setChecked( preferences.isForgetPassword() ); forgetPasswordField.setChecked( preferences.isForgetPassword() );
maskPasswordField.setChecked( preferences.isMaskPassword() ); maskPasswordField.setChecked( preferences.isMaskPassword() );
sitePasswordField.setTransformationMethod( preferences.isMaskPassword()? new PasswordTransformationMethod(): null ); sitePasswordField.setTransformationMethod( preferences.isMaskPassword()? new PasswordTransformationMethod(): null );
MPSiteType defaultSiteType = preferences.getDefaultSiteType(); MPResultType defaultResultType = preferences.getDefaultResultType();
siteTypeButton.setTag( defaultSiteType ); resultTypeButton.setTag( defaultResultType );
siteTypeButton.setText( defaultSiteType.getShortName() ); resultTypeButton.setText( defaultResultType.getShortName() );
MasterKey.Version defaultVersion = preferences.getDefaultVersion(); MPMasterKey.Version defaultVersion = preferences.getDefaultVersion();
siteVersionButton.setTag( defaultVersion ); siteVersionButton.setTag( defaultVersion );
siteVersionButton.setText( defaultVersion.name() ); siteVersionButton.setText( defaultVersion.name() );
siteCounterButton.setText( MessageFormat.format( "{0}", 1 ) ); siteCounterButton.setText( MessageFormat.format( "{0}", 1 ) );
@@ -241,10 +239,8 @@ public class EmergencyActivity extends Activity {
if (preferences.isForgetPassword()) { if (preferences.isForgetPassword()) {
synchronized (this) { synchronized (this) {
id_userName = id_masterPassword = 0; id_userName = id_masterPassword = 0;
if (masterKeyFuture != null) { if (masterKey != null)
masterKeyFuture.cancel( true ); masterKey = null;
masterKeyFuture = null;
}
masterPasswordField.setText( "" ); masterPasswordField.setText( "" );
} }
@@ -260,23 +256,17 @@ public class EmergencyActivity extends Activity {
private synchronized void updateMasterKey() { private synchronized void updateMasterKey() {
final String fullName = fullNameField.getText().toString(); final String fullName = fullNameField.getText().toString();
final char[] masterPassword = masterPasswordField.getText().toString().toCharArray(); final char[] masterPassword = masterPasswordField.getText().toString().toCharArray();
final MasterKey.Version version = (MasterKey.Version) siteVersionButton.getTag();
if ((id_userName == fullName.hashCode()) if ((id_userName == fullName.hashCode())
&& (id_masterPassword == Arrays.hashCode( masterPassword )) && (id_masterPassword == Arrays.hashCode( masterPassword )))
&& (id_version == version.ordinal())) if (masterKey != null)
if ((masterKeyFuture != null) && !masterKeyFuture.isCancelled())
return; return;
id_userName = fullName.hashCode(); id_userName = fullName.hashCode();
id_masterPassword = Arrays.hashCode( masterPassword ); id_masterPassword = Arrays.hashCode( masterPassword );
id_version = version.ordinal();
if (preferences.isRememberFullName()) if (preferences.isRememberFullName())
preferences.setFullName( fullName ); preferences.setFullName( fullName );
if (masterKeyFuture != null)
masterKeyFuture.cancel( true );
if (fullName.isEmpty() || (masterPassword.length == 0)) { if (fullName.isEmpty() || (masterPassword.length == 0)) {
sitePasswordField.setText( "" ); sitePasswordField.setText( "" );
progressView.setVisibility( View.INVISIBLE ); progressView.setVisibility( View.INVISIBLE );
@@ -285,43 +275,21 @@ public class EmergencyActivity extends Activity {
sitePasswordField.setText( "" ); sitePasswordField.setText( "" );
progressView.setVisibility( View.VISIBLE ); progressView.setVisibility( View.VISIBLE );
(masterKeyFuture = executor.submit( new Callable<MasterKey>() { masterKey = new MPMasterKey( fullName, masterPassword );
@Override
public MasterKey call()
throws Exception {
try {
return MasterKey.create( version, fullName, masterPassword );
}
catch (final Exception e) {
sitePasswordField.setText( "" );
progressView.setVisibility( View.INVISIBLE );
logger.err( e, "While generating master key." );
throw e;
}
}
} )).addListener( new Runnable() {
@Override
public void run() {
runOnUiThread( new Runnable() {
@Override
public void run() {
updateSitePassword(); updateSitePassword();
} }
} );
}
}, executor );
}
private void updateSitePassword() { private void updateSitePassword() {
final String siteName = siteNameField.getText().toString(); final String siteName = siteNameField.getText().toString();
final MPSiteType type = (MPSiteType) siteTypeButton.getTag(); final MPResultType type = (MPResultType) resultTypeButton.getTag();
final UnsignedInteger counter = UnsignedInteger.valueOf( siteCounterButton.getText().toString() ); final UnsignedInteger counter = UnsignedInteger.valueOf( siteCounterButton.getText().toString() );
final MPMasterKey.Version version = (MPMasterKey.Version) siteVersionButton.getTag();
if ((masterKeyFuture == null) || siteName.isEmpty() || (type == null)) { if ((masterKey == null) || siteName.isEmpty() || (type == null)) {
sitePasswordField.setText( "" ); sitePasswordField.setText( "" );
progressView.setVisibility( View.INVISIBLE ); progressView.setVisibility( View.INVISIBLE );
if (masterKeyFuture == null) if (masterKey == null)
updateMasterKey(); updateMasterKey();
return; return;
} }
@@ -332,7 +300,7 @@ public class EmergencyActivity extends Activity {
@Override @Override
public void run() { public void run() {
try { try {
sitePassword = masterKeyFuture.get().encode( siteName, type, counter, MPSiteVariant.Password, null ); sitePassword = masterKey.siteResult( siteName, counter, MPKeyPurpose.Authentication, null, type, null, version );
runOnUiThread( new Runnable() { runOnUiThread( new Runnable() {
@Override @Override
@@ -342,16 +310,10 @@ public class EmergencyActivity extends Activity {
} }
} ); } );
} }
catch (final InterruptedException ignored) { catch (final MPInvalidatedException ignored) {
sitePasswordField.setText( "" ); sitePasswordField.setText( "" );
progressView.setVisibility( View.INVISIBLE ); progressView.setVisibility( View.INVISIBLE );
} }
catch (final ExecutionException e) {
sitePasswordField.setText( "" );
progressView.setVisibility( View.INVISIBLE );
logger.err( e, "While generating site password." );
throw Throwables.propagate( e );
}
catch (final RuntimeException e) { catch (final RuntimeException e) {
sitePasswordField.setText( "" ); sitePasswordField.setText( "" );
progressView.setVisibility( View.INVISIBLE ); progressView.setVisibility( View.INVISIBLE );
@@ -363,10 +325,9 @@ public class EmergencyActivity extends Activity {
} }
public void integrityTests(final View view) { public void integrityTests(final View view) {
if (masterKeyFuture != null) { if (masterKey != null)
masterKeyFuture.cancel( true ); masterKey = null;
masterKeyFuture = null;
}
TestActivity.startNoSkip( this ); TestActivity.startNoSkip( this );
} }

View File

@@ -38,7 +38,7 @@ public final class Preferences {
private static final String PREF_FORGET_PASSWORD = "forgetPassword"; private static final String PREF_FORGET_PASSWORD = "forgetPassword";
private static final String PREF_MASK_PASSWORD = "maskPassword"; private static final String PREF_MASK_PASSWORD = "maskPassword";
private static final String PREF_FULL_NAME = "fullName"; private static final String PREF_FULL_NAME = "fullName";
private static final String PREF_SITE_TYPE = "siteType"; private static final String PREF_RESULT_TYPE = "resultType";
private static final String PREF_ALGORITHM_VERSION = "algorithmVersion"; private static final String PREF_ALGORITHM_VERSION = "algorithmVersion";
private static Preferences instance; private static Preferences instance;
@@ -74,7 +74,7 @@ public final class Preferences {
} }
public boolean isAllowNativeKDF() { public boolean isAllowNativeKDF() {
return prefs().getBoolean( PREF_NATIVE_KDF, MasterKey.isAllowNativeByDefault() ); return prefs().getBoolean( PREF_NATIVE_KDF, true );
} }
public boolean setTestsPassed(final Set<String> value) { public boolean setTestsPassed(final Set<String> value) {
@@ -138,20 +138,20 @@ public final class Preferences {
return prefs().getString( PREF_FULL_NAME, "" ); return prefs().getString( PREF_FULL_NAME, "" );
} }
public boolean setDefaultSiteType(@Nonnull final MPSiteType value) { public boolean setDefaultResultType(final MPResultType value) {
if (getDefaultSiteType() == value) if (getDefaultResultType() == value)
return false; return false;
prefs().edit().putInt( PREF_SITE_TYPE, value.ordinal() ).apply(); prefs().edit().putInt( PREF_RESULT_TYPE, value.ordinal() ).apply();
return true; return true;
} }
@Nonnull @Nonnull
public MPSiteType getDefaultSiteType() { public MPResultType getDefaultResultType() {
return MPSiteType.values()[prefs().getInt( PREF_SITE_TYPE, MPSiteType.GeneratedLong.ordinal() )]; return MPResultType.values()[prefs().getInt( PREF_RESULT_TYPE, MPResultType.DEFAULT.ordinal() )];
} }
public boolean setDefaultVersion(@Nonnull final MasterKey.Version value) { public boolean setDefaultVersion(final MPMasterKey.Version value) {
if (getDefaultVersion() == value) if (getDefaultVersion() == value)
return false; return false;
@@ -160,7 +160,7 @@ public final class Preferences {
} }
@Nonnull @Nonnull
public MasterKey.Version getDefaultVersion() { public MPMasterKey.Version getDefaultVersion() {
return MasterKey.Version.values()[prefs().getInt( PREF_ALGORITHM_VERSION, MasterKey.Version.CURRENT.ordinal() )]; return MPMasterKey.Version.values()[prefs().getInt( PREF_ALGORITHM_VERSION, MPMasterKey.Version.CURRENT.ordinal() )];
} }
} }

View File

@@ -80,7 +80,7 @@ public class TestActivity extends Activity implements MPTestSuite.Listener {
@Override @Override
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) { public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
preferences.setNativeKDFEnabled( isChecked ); preferences.setNativeKDFEnabled( isChecked );
MasterKey.setAllowNativeByDefault( isChecked ); // TODO: MasterKey.setAllowNativeByDefault( isChecked );
} }
} ); } );
@@ -122,7 +122,7 @@ public class TestActivity extends Activity implements MPTestSuite.Listener {
if (testFuture != null) if (testFuture != null)
testFuture.cancel( true ); testFuture.cancel( true );
MasterKey.setAllowNativeByDefault( preferences.isAllowNativeKDF() ); // TODO: MasterKey.setAllowNativeByDefault( preferences.isAllowNativeKDF() );
setStatus( R.string.tests_testing, R.string.tests_btn_testing, null ); setStatus( R.string.tests_testing, R.string.tests_btn_testing, null );
Futures.addCallback( testFuture = backgroundExecutor.submit( testSuite ), new FutureCallback<Boolean>() { Futures.addCallback( testFuture = backgroundExecutor.submit( testSuite ), new FutureCallback<Boolean>() {

View File

@@ -105,7 +105,7 @@
android:id="@id/sitePasswordField" android:id="@id/sitePasswordField"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:nextFocusForward="@+id/siteTypeButton" android:nextFocusForward="@+id/resultTypeButton"
android:gravity="center" android:gravity="center"
android:background="@android:color/transparent" android:background="@android:color/transparent"
android:textColor="#FFFFFF" android:textColor="#FFFFFF"
@@ -157,7 +157,7 @@
android:gravity="center"> android:gravity="center">
<Button <Button
android:id="@id/siteTypeButton" android:id="@id/resultTypeButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
@@ -175,12 +175,12 @@
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:labelFor="@id/siteTypeButton" android:labelFor="@id/resultTypeButton"
android:gravity="center" android:gravity="center"
android:background="@android:color/transparent" android:background="@android:color/transparent"
android:textSize="12sp" android:textSize="12sp"
android:textColor="@android:color/tertiary_text_dark" android:textColor="@android:color/tertiary_text_dark"
android:text="@string/siteType_hint" /> android:text="@string/resultType_hint" />
</LinearLayout> </LinearLayout>

View File

@@ -8,7 +8,7 @@
<string name="masterPassword_hint">Your master password</string> <string name="masterPassword_hint">Your master password</string>
<string name="siteName_hint">eg. google.com</string> <string name="siteName_hint">eg. google.com</string>
<string name="sitePassword_hint">Tap to copy</string> <string name="sitePassword_hint">Tap to copy</string>
<string name="siteType_hint">Type</string> <string name="resultType_hint">Type</string>
<string name="siteCounter_hint">Counter</string> <string name="siteCounter_hint">Counter</string>
<string name="siteVersion_hint">Algorithm</string> <string name="siteVersion_hint">Algorithm</string>
<string name="empty" /> <string name="empty" />

View File

@@ -249,11 +249,12 @@
DAA1765219D8B82B0044227B /* copy_pw.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1763C19D8B82B0044227B /* copy_pw.png */; }; DAA1765219D8B82B0044227B /* copy_pw.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1763C19D8B82B0044227B /* copy_pw.png */; };
DAA1765319D8B82B0044227B /* choose_type@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1763D19D8B82B0044227B /* choose_type@2x.png */; }; DAA1765319D8B82B0044227B /* choose_type@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1763D19D8B82B0044227B /* choose_type@2x.png */; };
DAA1765419D8B82B0044227B /* choose_type.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1763E19D8B82B0044227B /* choose_type.png */; }; DAA1765419D8B82B0044227B /* choose_type.png in Resources */ = {isa = PBXBuildFile; fileRef = DAA1763E19D8B82B0044227B /* choose_type.png */; };
DAA449D21EEC4B5800E7BDD5 /* mpw-marshall.c in Sources */ = {isa = PBXBuildFile; fileRef = DAA449D01EEC4B5800E7BDD5 /* mpw-marshall.c */; }; DAA449D21EEC4B5800E7BDD5 /* mpw-marshal.c in Sources */ = {isa = PBXBuildFile; fileRef = DAA449D01EEC4B5800E7BDD5 /* mpw-marshal.c */; };
DAADBFE01A68763B00F7A756 /* mpw-algorithm.c in Sources */ = {isa = PBXBuildFile; fileRef = 93D3969393A3A46BD27D7078 /* mpw-algorithm.c */; }; DAADBFE01A68763B00F7A756 /* mpw-algorithm.c in Sources */ = {isa = PBXBuildFile; fileRef = 93D3969393A3A46BD27D7078 /* mpw-algorithm.c */; };
DAB07C9D1F7725C500CC6D43 /* aes.c in Sources */ = {isa = PBXBuildFile; fileRef = DAB07C9B1F7725C500CC6D43 /* aes.c */; };
DAB7AE5D1F3D752900C856B1 /* libjson-c.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAB7AE5C1F3D752900C856B1 /* libjson-c.a */; }; DAB7AE5D1F3D752900C856B1 /* libjson-c.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAB7AE5C1F3D752900C856B1 /* libjson-c.a */; };
DAB7AE771F3D755B00C856B1 /* libjson-c.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAB7AE761F3D755B00C856B1 /* libjson-c.a */; }; DAB7AE771F3D755B00C856B1 /* libjson-c.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAB7AE761F3D755B00C856B1 /* libjson-c.a */; };
DAB7AE991F3DDEE000C856B1 /* mpw-marshall-util.c in Sources */ = {isa = PBXBuildFile; fileRef = DAB7AE981F3DDEE000C856B1 /* mpw-marshall-util.c */; }; DAB7AE991F3DDEE000C856B1 /* mpw-marshal-util.c in Sources */ = {isa = PBXBuildFile; fileRef = DAB7AE981F3DDEE000C856B1 /* mpw-marshal-util.c */; };
DABB981615100B4000B05417 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DABB981515100B4000B05417 /* SystemConfiguration.framework */; }; DABB981615100B4000B05417 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DABB981515100B4000B05417 /* SystemConfiguration.framework */; };
DABD39371711E29700CF925C /* avatar-0.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD366C1711E29400CF925C /* avatar-0.png */; }; DABD39371711E29700CF925C /* avatar-0.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD366C1711E29400CF925C /* avatar-0.png */; };
DABD39381711E29700CF925C /* avatar-0@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD366D1711E29400CF925C /* avatar-0@2x.png */; }; DABD39381711E29700CF925C /* avatar-0@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD366D1711E29400CF925C /* avatar-0@2x.png */; };
@@ -349,7 +350,6 @@
DAC77CAE148291A600BCF976 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; }; DAC77CAE148291A600BCF976 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
DAC8DF47192831E100BA7D71 /* icon_key.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD379A1711E29600CF925C /* icon_key.png */; }; DAC8DF47192831E100BA7D71 /* icon_key.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD379A1711E29600CF925C /* icon_key.png */; };
DAC8DF48192831E100BA7D71 /* icon_key@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD379B1711E29600CF925C /* icon_key@2x.png */; }; DAC8DF48192831E100BA7D71 /* icon_key@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD379B1711E29600CF925C /* icon_key@2x.png */; };
DACA29731705E1A8002C6C22 /* ciphers.plist in Resources */ = {isa = PBXBuildFile; fileRef = DACA29711705E1A8002C6C22 /* ciphers.plist */; };
DACA29741705E1A8002C6C22 /* dictionary.lst in Resources */ = {isa = PBXBuildFile; fileRef = DACA29721705E1A8002C6C22 /* dictionary.lst */; }; DACA29741705E1A8002C6C22 /* dictionary.lst in Resources */ = {isa = PBXBuildFile; fileRef = DACA29721705E1A8002C6C22 /* dictionary.lst */; };
DACA298D1705E2BD002C6C22 /* JRSwizzle.h in Headers */ = {isa = PBXBuildFile; fileRef = DACA29771705E2BD002C6C22 /* JRSwizzle.h */; }; DACA298D1705E2BD002C6C22 /* JRSwizzle.h in Headers */ = {isa = PBXBuildFile; fileRef = DACA29771705E2BD002C6C22 /* JRSwizzle.h */; };
DACA299A1705E2BD002C6C22 /* JRSwizzle.m in Sources */ = {isa = PBXBuildFile; fileRef = DACA298C1705E2BD002C6C22 /* JRSwizzle.m */; }; DACA299A1705E2BD002C6C22 /* JRSwizzle.m in Sources */ = {isa = PBXBuildFile; fileRef = DACA298C1705E2BD002C6C22 /* JRSwizzle.m */; };
@@ -871,9 +871,11 @@
DAA1763C19D8B82B0044227B /* copy_pw.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = copy_pw.png; sourceTree = "<group>"; }; DAA1763C19D8B82B0044227B /* copy_pw.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = copy_pw.png; sourceTree = "<group>"; };
DAA1763D19D8B82B0044227B /* choose_type@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "choose_type@2x.png"; sourceTree = "<group>"; }; DAA1763D19D8B82B0044227B /* choose_type@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "choose_type@2x.png"; sourceTree = "<group>"; };
DAA1763E19D8B82B0044227B /* choose_type.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = choose_type.png; sourceTree = "<group>"; }; DAA1763E19D8B82B0044227B /* choose_type.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = choose_type.png; sourceTree = "<group>"; };
DAA449D01EEC4B5800E7BDD5 /* mpw-marshall.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-marshall.c"; sourceTree = "<group>"; }; DAA449D01EEC4B5800E7BDD5 /* mpw-marshal.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-marshal.c"; sourceTree = "<group>"; };
DAA449D11EEC4B5800E7BDD5 /* mpw-marshall.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-marshall.h"; sourceTree = "<group>"; }; DAA449D11EEC4B5800E7BDD5 /* mpw-marshal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-marshal.h"; sourceTree = "<group>"; };
DAAC35DD156BD77D00C5FD93 /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = System/Library/Frameworks/CoreTelephony.framework; sourceTree = SDKROOT; }; DAAC35DD156BD77D00C5FD93 /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = System/Library/Frameworks/CoreTelephony.framework; sourceTree = SDKROOT; };
DAB07C9B1F7725C500CC6D43 /* aes.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = aes.c; sourceTree = "<group>"; };
DAB07C9C1F7725C500CC6D43 /* aes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = aes.h; sourceTree = "<group>"; };
DAB7AE5C1F3D752900C856B1 /* libjson-c.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libjson-c.a"; path = "External/libjson-c/libjson-c-ios/lib/libjson-c.a"; sourceTree = "<group>"; }; DAB7AE5C1F3D752900C856B1 /* libjson-c.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libjson-c.a"; path = "External/libjson-c/libjson-c-ios/lib/libjson-c.a"; sourceTree = "<group>"; };
DAB7AE611F3D755B00C856B1 /* arraylist.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = arraylist.h; sourceTree = "<group>"; }; DAB7AE611F3D755B00C856B1 /* arraylist.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = arraylist.h; sourceTree = "<group>"; };
DAB7AE621F3D755B00C856B1 /* bits.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bits.h; sourceTree = "<group>"; }; DAB7AE621F3D755B00C856B1 /* bits.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bits.h; sourceTree = "<group>"; };
@@ -896,8 +898,8 @@
DAB7AE731F3D755B00C856B1 /* strdup_compat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = strdup_compat.h; sourceTree = "<group>"; }; DAB7AE731F3D755B00C856B1 /* strdup_compat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = strdup_compat.h; sourceTree = "<group>"; };
DAB7AE741F3D755B00C856B1 /* vasprintf_compat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vasprintf_compat.h; sourceTree = "<group>"; }; DAB7AE741F3D755B00C856B1 /* vasprintf_compat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vasprintf_compat.h; sourceTree = "<group>"; };
DAB7AE761F3D755B00C856B1 /* libjson-c.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libjson-c.a"; sourceTree = "<group>"; }; DAB7AE761F3D755B00C856B1 /* libjson-c.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libjson-c.a"; sourceTree = "<group>"; };
DAB7AE971F3DDEE000C856B1 /* mpw-marshall-util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-marshall-util.h"; sourceTree = "<group>"; }; DAB7AE971F3DDEE000C856B1 /* mpw-marshal-util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-marshal-util.h"; sourceTree = "<group>"; };
DAB7AE981F3DDEE000C856B1 /* mpw-marshall-util.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-marshall-util.c"; sourceTree = "<group>"; }; DAB7AE981F3DDEE000C856B1 /* mpw-marshal-util.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-marshal-util.c"; sourceTree = "<group>"; };
DABB981515100B4000B05417 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; DABB981515100B4000B05417 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
DABD360F1711E29400CF925C /* ui_background.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ui_background.png; sourceTree = "<group>"; }; DABD360F1711E29400CF925C /* ui_background.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ui_background.png; sourceTree = "<group>"; };
DABD36101711E29400CF925C /* ui_background@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ui_background@2x.png"; sourceTree = "<group>"; }; DABD36101711E29400CF925C /* ui_background@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ui_background@2x.png"; sourceTree = "<group>"; };
@@ -1563,7 +1565,6 @@
DAC632871486D95D0075AEA5 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; DAC632871486D95D0075AEA5 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
DAC77CAD148291A600BCF976 /* libPearl.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPearl.a; sourceTree = BUILT_PRODUCTS_DIR; }; DAC77CAD148291A600BCF976 /* libPearl.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPearl.a; sourceTree = BUILT_PRODUCTS_DIR; };
DAC77CB1148291A600BCF976 /* Pearl-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "Pearl-Prefix.pch"; path = "../../Source/Pearl/Pearl-Prefix.pch"; sourceTree = "<group>"; }; DAC77CB1148291A600BCF976 /* Pearl-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "Pearl-Prefix.pch"; path = "../../Source/Pearl/Pearl-Prefix.pch"; sourceTree = "<group>"; };
DACA29711705E1A8002C6C22 /* ciphers.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = ciphers.plist; sourceTree = "<group>"; };
DACA29721705E1A8002C6C22 /* dictionary.lst */ = {isa = PBXFileReference; fileEncoding = 1; lastKnownFileType = text; path = dictionary.lst; sourceTree = "<group>"; }; DACA29721705E1A8002C6C22 /* dictionary.lst */ = {isa = PBXFileReference; fileEncoding = 1; lastKnownFileType = text; path = dictionary.lst; sourceTree = "<group>"; };
DACA29771705E2BD002C6C22 /* JRSwizzle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JRSwizzle.h; sourceTree = "<group>"; }; DACA29771705E2BD002C6C22 /* JRSwizzle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JRSwizzle.h; sourceTree = "<group>"; };
DACA298C1705E2BD002C6C22 /* JRSwizzle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JRSwizzle.m; sourceTree = "<group>"; }; DACA298C1705E2BD002C6C22 /* JRSwizzle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JRSwizzle.m; sourceTree = "<group>"; };
@@ -1751,6 +1752,8 @@
93D39A2239FFFE6BEC83E191 /* C */ = { 93D39A2239FFFE6BEC83E191 /* C */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DAB07C9B1F7725C500CC6D43 /* aes.c */,
DAB07C9C1F7725C500CC6D43 /* aes.h */,
DA5B0B3E1F36469400B663F0 /* base64.c */, DA5B0B3E1F36469400B663F0 /* base64.c */,
DA5B0B3F1F36469400B663F0 /* base64.h */, DA5B0B3F1F36469400B663F0 /* base64.h */,
93D390A99850139D0FF0211E /* mpw-algorithm_v0.c */, 93D390A99850139D0FF0211E /* mpw-algorithm_v0.c */,
@@ -1759,10 +1762,10 @@
93D39D4E713564B7654341B0 /* mpw-algorithm_v3.c */, 93D39D4E713564B7654341B0 /* mpw-algorithm_v3.c */,
93D3969393A3A46BD27D7078 /* mpw-algorithm.c */, 93D3969393A3A46BD27D7078 /* mpw-algorithm.c */,
93D3990D850D76A94C6B7A4D /* mpw-algorithm.h */, 93D3990D850D76A94C6B7A4D /* mpw-algorithm.h */,
DAB7AE981F3DDEE000C856B1 /* mpw-marshall-util.c */, DAB7AE981F3DDEE000C856B1 /* mpw-marshal-util.c */,
DAB7AE971F3DDEE000C856B1 /* mpw-marshall-util.h */, DAB7AE971F3DDEE000C856B1 /* mpw-marshal-util.h */,
DAA449D01EEC4B5800E7BDD5 /* mpw-marshall.c */, DAA449D01EEC4B5800E7BDD5 /* mpw-marshal.c */,
DAA449D11EEC4B5800E7BDD5 /* mpw-marshall.h */, DAA449D11EEC4B5800E7BDD5 /* mpw-marshal.h */,
93D392C5A6572DB0EB5B82C8 /* mpw-types.c */, 93D392C5A6572DB0EB5B82C8 /* mpw-types.c */,
93D39AFD17CBE324D745DAB0 /* mpw-types.h */, 93D39AFD17CBE324D745DAB0 /* mpw-types.h */,
93D396C311C3725870343EE0 /* mpw-util.c */, 93D396C311C3725870343EE0 /* mpw-util.c */,
@@ -3039,7 +3042,6 @@
DACA29701705E1A8002C6C22 /* Data */ = { DACA29701705E1A8002C6C22 /* Data */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DACA29711705E1A8002C6C22 /* ciphers.plist */,
DACA29721705E1A8002C6C22 /* dictionary.lst */, DACA29721705E1A8002C6C22 /* dictionary.lst */,
); );
path = Data; path = Data;
@@ -3699,7 +3701,6 @@
files = ( files = (
DAFE4A5A1503982E003ABA7C /* Pearl.strings in Resources */, DAFE4A5A1503982E003ABA7C /* Pearl.strings in Resources */,
DA0CC53B1EB57B5C009A8ED9 /* Fabric.plist in Resources */, DA0CC53B1EB57B5C009A8ED9 /* Fabric.plist in Resources */,
DACA29731705E1A8002C6C22 /* ciphers.plist in Resources */,
DA32D04F19D2F59B004F3F0E /* meter_fuel@2x.png in Resources */, DA32D04F19D2F59B004F3F0E /* meter_fuel@2x.png in Resources */,
DACA29741705E1A8002C6C22 /* dictionary.lst in Resources */, DACA29741705E1A8002C6C22 /* dictionary.lst in Resources */,
DA45224C190628B2008F650A /* icon_gear@2x.png in Resources */, DA45224C190628B2008F650A /* icon_gear@2x.png in Resources */,
@@ -3970,7 +3971,7 @@
DABD3C151711E2DC00CF925C /* MPiOSAppDelegate.m in Sources */, DABD3C151711E2DC00CF925C /* MPiOSAppDelegate.m in Sources */,
DA0CC5941EB6B030009A8ED9 /* MPSiteEntity+CoreDataClass.m in Sources */, DA0CC5941EB6B030009A8ED9 /* MPSiteEntity+CoreDataClass.m in Sources */,
DABD3C1C1711E2DC00CF925C /* MPGuideViewController.m in Sources */, DABD3C1C1711E2DC00CF925C /* MPGuideViewController.m in Sources */,
DAA449D21EEC4B5800E7BDD5 /* mpw-marshall.c in Sources */, DAA449D21EEC4B5800E7BDD5 /* mpw-marshal.c in Sources */,
DABD3C1E1711E2DC00CF925C /* MPPreferencesViewController.m in Sources */, DABD3C1E1711E2DC00CF925C /* MPPreferencesViewController.m in Sources */,
DABD3C1F1711E2DC00CF925C /* MPTypeViewController.m in Sources */, DABD3C1F1711E2DC00CF925C /* MPTypeViewController.m in Sources */,
DABD3C211711E2DC00CF925C /* MPiOSConfig.m in Sources */, DABD3C211711E2DC00CF925C /* MPiOSConfig.m in Sources */,
@@ -3988,7 +3989,7 @@
DA0CC58E1EB6B030009A8ED9 /* MPSiteQuestionEntity+CoreDataClass.m in Sources */, DA0CC58E1EB6B030009A8ED9 /* MPSiteQuestionEntity+CoreDataClass.m in Sources */,
93D39A5FF670957C0AF8298D /* MPSiteCell.m in Sources */, 93D39A5FF670957C0AF8298D /* MPSiteCell.m in Sources */,
93D398ECD7D1A0DEDDADF516 /* MPEmergencyViewController.m in Sources */, 93D398ECD7D1A0DEDDADF516 /* MPEmergencyViewController.m in Sources */,
DAB7AE991F3DDEE000C856B1 /* mpw-marshall-util.c in Sources */, DAB7AE991F3DDEE000C856B1 /* mpw-marshal-util.c in Sources */,
DA95B50F1C4776F00067F5EF /* NSMutableSet+Pearl.m in Sources */, DA95B50F1C4776F00067F5EF /* NSMutableSet+Pearl.m in Sources */,
93D394B5036C882B33C71872 /* MPSitesSegue.m in Sources */, 93D394B5036C882B33C71872 /* MPSitesSegue.m in Sources */,
DA0CC5911EB6B030009A8ED9 /* MPStoredSiteEntity+CoreDataProperties.m in Sources */, DA0CC5911EB6B030009A8ED9 /* MPStoredSiteEntity+CoreDataProperties.m in Sources */,
@@ -3998,6 +3999,7 @@
93D396D8B67DA6522CDBA142 /* MPCoachmarkViewController.m in Sources */, 93D396D8B67DA6522CDBA142 /* MPCoachmarkViewController.m in Sources */,
DAADBFE01A68763B00F7A756 /* mpw-algorithm.c in Sources */, DAADBFE01A68763B00F7A756 /* mpw-algorithm.c in Sources */,
DA0CC54E1EB6AD0E009A8ED9 /* MasterPassword.xcdatamodeld in Sources */, DA0CC54E1EB6AD0E009A8ED9 /* MasterPassword.xcdatamodeld in Sources */,
DAB07C9D1F7725C500CC6D43 /* aes.c in Sources */,
93D39EAA4D064193074D3021 /* MPFixable.m in Sources */, 93D39EAA4D064193074D3021 /* MPFixable.m in Sources */,
93D394982CBD25D46692DD7C /* MPWebViewController.m in Sources */, 93D394982CBD25D46692DD7C /* MPWebViewController.m in Sources */,
93D39D8F78978196D6ABDEDE /* MPNavigationController.m in Sources */, 93D39D8F78978196D6ABDEDE /* MPNavigationController.m in Sources */,

View File

@@ -31,7 +31,7 @@
DA16B342170661E0000A0EAB /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC632871486D95D0075AEA5 /* Security.framework */; }; DA16B342170661E0000A0EAB /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC632871486D95D0075AEA5 /* Security.framework */; };
DA16B344170661EE000A0EAB /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA16B343170661EE000A0EAB /* Cocoa.framework */; }; DA16B344170661EE000A0EAB /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA16B343170661EE000A0EAB /* Cocoa.framework */; };
DA16B345170661F2000A0EAB /* libPearl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC77CAD148291A600BCF976 /* libPearl.a */; }; DA16B345170661F2000A0EAB /* libPearl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC77CAD148291A600BCF976 /* libPearl.a */; };
DA1C7AAA1F1A8F24009A3551 /* mpw-marshall.c in Sources */ = {isa = PBXBuildFile; fileRef = DAA449D31EEC4B6B00E7BDD5 /* mpw-marshall.c */; }; DA1C7AAA1F1A8F24009A3551 /* mpw-marshal.c in Sources */ = {isa = PBXBuildFile; fileRef = DAA449D31EEC4B6B00E7BDD5 /* mpw-marshal.c */; };
DA1C7AAB1F1A8F24009A3551 /* mpw-types.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773C21A4746AF004F356A /* mpw-types.c */; }; DA1C7AAB1F1A8F24009A3551 /* mpw-types.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773C21A4746AF004F356A /* mpw-types.c */; };
DA1C7AAC1F1A8F24009A3551 /* mpw-util.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773C51A4746AF004F356A /* mpw-util.c */; }; DA1C7AAC1F1A8F24009A3551 /* mpw-util.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773C51A4746AF004F356A /* mpw-util.c */; };
DA1C7AAD1F1A8F24009A3551 /* mpw-algorithm.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773BB1A4746AF004F356A /* mpw-algorithm.c */; }; DA1C7AAD1F1A8F24009A3551 /* mpw-algorithm.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773BB1A4746AF004F356A /* mpw-algorithm.c */; };
@@ -110,8 +110,8 @@
DA6774431A474A3B004F356A /* mpw-algorithm.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773BB1A4746AF004F356A /* mpw-algorithm.c */; }; DA6774431A474A3B004F356A /* mpw-algorithm.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773BB1A4746AF004F356A /* mpw-algorithm.c */; };
DA6774451A474A3B004F356A /* mpw-types.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773C21A4746AF004F356A /* mpw-types.c */; }; DA6774451A474A3B004F356A /* mpw-types.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773C21A4746AF004F356A /* mpw-types.c */; };
DA6774461A474A3B004F356A /* mpw-util.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773C51A4746AF004F356A /* mpw-util.c */; }; DA6774461A474A3B004F356A /* mpw-util.c in Sources */ = {isa = PBXBuildFile; fileRef = DA6773C51A4746AF004F356A /* mpw-util.c */; };
DA7471A31F2B71AE005F3468 /* mpw-marshall-util.c in Sources */ = {isa = PBXBuildFile; fileRef = DA7471A01F2B71A9005F3468 /* mpw-marshall-util.c */; }; DA7471A31F2B71AE005F3468 /* mpw-marshal-util.c in Sources */ = {isa = PBXBuildFile; fileRef = DA7471A01F2B71A9005F3468 /* mpw-marshal-util.c */; };
DA7471A61F2B71B9005F3468 /* mpw-marshall-util.c in Sources */ = {isa = PBXBuildFile; fileRef = DA7471A01F2B71A9005F3468 /* mpw-marshall-util.c */; }; DA7471A61F2B71B9005F3468 /* mpw-marshal-util.c in Sources */ = {isa = PBXBuildFile; fileRef = DA7471A01F2B71A9005F3468 /* mpw-marshal-util.c */; };
DA89D4EC1A51EABD00AC64D7 /* Pearl-Cocoa.h in Headers */ = {isa = PBXBuildFile; fileRef = DA89D4EA1A51EABD00AC64D7 /* Pearl-Cocoa.h */; }; DA89D4EC1A51EABD00AC64D7 /* Pearl-Cocoa.h in Headers */ = {isa = PBXBuildFile; fileRef = DA89D4EA1A51EABD00AC64D7 /* Pearl-Cocoa.h */; };
DA89D4ED1A51EABD00AC64D7 /* Pearl-Cocoa.m in Sources */ = {isa = PBXBuildFile; fileRef = DA89D4EB1A51EABD00AC64D7 /* Pearl-Cocoa.m */; }; DA89D4ED1A51EABD00AC64D7 /* Pearl-Cocoa.m in Sources */ = {isa = PBXBuildFile; fileRef = DA89D4EB1A51EABD00AC64D7 /* Pearl-Cocoa.m */; };
DA8ED895192906920099B726 /* PearlTween.m in Sources */ = {isa = PBXBuildFile; fileRef = DA8ED891192906920099B726 /* PearlTween.m */; }; DA8ED895192906920099B726 /* PearlTween.m in Sources */ = {isa = PBXBuildFile; fileRef = DA8ED891192906920099B726 /* PearlTween.m */; };
@@ -121,7 +121,7 @@
DA9261521BE1A86700369DE5 /* Fabric.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA9261501BE1A86700369DE5 /* Fabric.framework */; }; DA9261521BE1A86700369DE5 /* Fabric.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA9261501BE1A86700369DE5 /* Fabric.framework */; };
DA9261541BE1A88900369DE5 /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = DA9261531BE1A88900369DE5 /* libc++.tbd */; }; DA9261541BE1A88900369DE5 /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = DA9261531BE1A88900369DE5 /* libc++.tbd */; };
DA9261561BE1A89600369DE5 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = DA9261551BE1A89600369DE5 /* libz.tbd */; }; DA9261561BE1A89600369DE5 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = DA9261551BE1A89600369DE5 /* libz.tbd */; };
DAA449D51EEC4B6B00E7BDD5 /* mpw-marshall.c in Sources */ = {isa = PBXBuildFile; fileRef = DAA449D31EEC4B6B00E7BDD5 /* mpw-marshall.c */; }; DAA449D51EEC4B6B00E7BDD5 /* mpw-marshal.c in Sources */ = {isa = PBXBuildFile; fileRef = DAA449D31EEC4B6B00E7BDD5 /* mpw-marshal.c */; };
DAAA81B0195A8D1300FA30D9 /* gradient.png in Resources */ = {isa = PBXBuildFile; fileRef = DAAA81AF195A8D1300FA30D9 /* gradient.png */; }; DAAA81B0195A8D1300FA30D9 /* gradient.png in Resources */ = {isa = PBXBuildFile; fileRef = DAAA81AF195A8D1300FA30D9 /* gradient.png */; };
DAADCC4719FAFFAD00987B1D /* NSNotificationCenter+PearlEasyCleanup.h in Headers */ = {isa = PBXBuildFile; fileRef = DAADCC3E19FAFFAD00987B1D /* NSNotificationCenter+PearlEasyCleanup.h */; }; DAADCC4719FAFFAD00987B1D /* NSNotificationCenter+PearlEasyCleanup.h in Headers */ = {isa = PBXBuildFile; fileRef = DAADCC3E19FAFFAD00987B1D /* NSNotificationCenter+PearlEasyCleanup.h */; };
DAADCC4819FAFFAD00987B1D /* NSPersistentStore+PearlMigration.h in Headers */ = {isa = PBXBuildFile; fileRef = DAADCC3F19FAFFAD00987B1D /* NSPersistentStore+PearlMigration.h */; }; DAADCC4819FAFFAD00987B1D /* NSPersistentStore+PearlMigration.h in Headers */ = {isa = PBXBuildFile; fileRef = DAADCC3F19FAFFAD00987B1D /* NSPersistentStore+PearlMigration.h */; };
@@ -130,6 +130,10 @@
DAADCC6919FB007F00987B1D /* NSManagedObjectModel+KCOrderedAccessorFix.m in Sources */ = {isa = PBXBuildFile; fileRef = DAADCC6719FB007F00987B1D /* NSManagedObjectModel+KCOrderedAccessorFix.m */; }; DAADCC6919FB007F00987B1D /* NSManagedObjectModel+KCOrderedAccessorFix.m in Sources */ = {isa = PBXBuildFile; fileRef = DAADCC6719FB007F00987B1D /* NSManagedObjectModel+KCOrderedAccessorFix.m */; };
DAADCC6A19FB00B500987B1D /* libKCOrderedAccessorFix.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAADCC5019FB006500987B1D /* libKCOrderedAccessorFix.a */; }; DAADCC6A19FB00B500987B1D /* libKCOrderedAccessorFix.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAADCC5019FB006500987B1D /* libKCOrderedAccessorFix.a */; };
DAAF16661F5CA3240013B8AE /* mpw-cli-util.c in Sources */ = {isa = PBXBuildFile; fileRef = DAAF16631F5897EA0013B8AE /* mpw-cli-util.c */; }; DAAF16661F5CA3240013B8AE /* mpw-cli-util.c in Sources */ = {isa = PBXBuildFile; fileRef = DAAF16631F5897EA0013B8AE /* mpw-cli-util.c */; };
DAB07CA01F7725D400CC6D43 /* aes.c in Sources */ = {isa = PBXBuildFile; fileRef = DAB07C9E1F7725D400CC6D43 /* aes.c */; };
DAB07CA31F77261400CC6D43 /* aes.c in Sources */ = {isa = PBXBuildFile; fileRef = DAB07C9E1F7725D400CC6D43 /* aes.c */; };
DAB07CA51F77261C00CC6D43 /* aes.c in Sources */ = {isa = PBXBuildFile; fileRef = DAB07C9E1F7725D400CC6D43 /* aes.c */; };
DAB07CA61F77262400CC6D43 /* aes.c in Sources */ = {isa = PBXBuildFile; fileRef = DAB07C9E1F7725D400CC6D43 /* aes.c */; };
DAB7AE5A1F3D74E700C856B1 /* libjson-c.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAB7AE591F3D74E700C856B1 /* libjson-c.a */; }; DAB7AE5A1F3D74E700C856B1 /* libjson-c.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAB7AE591F3D74E700C856B1 /* libjson-c.a */; };
DAB7AE5B1F3D750B00C856B1 /* libjson-c.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAB7AE591F3D74E700C856B1 /* libjson-c.a */; }; DAB7AE5B1F3D750B00C856B1 /* libjson-c.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAB7AE591F3D74E700C856B1 /* libjson-c.a */; };
DAB7AE941F3D757B00C856B1 /* libjson-c.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAB7AE901F3D757B00C856B1 /* libjson-c.a */; }; DAB7AE941F3D757B00C856B1 /* libjson-c.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAB7AE901F3D757B00C856B1 /* libjson-c.a */; };
@@ -177,7 +181,6 @@
DACA27381705DF81002C6C22 /* menu-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = DACA24581705DF7D002C6C22 /* menu-icon.png */; }; DACA27381705DF81002C6C22 /* menu-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = DACA24581705DF7D002C6C22 /* menu-icon.png */; };
DACA29671705DF81002C6C22 /* SourceCodePro-ExtraLight.otf in Resources */ = {isa = PBXBuildFile; fileRef = DACA268E1705DF81002C6C22 /* SourceCodePro-ExtraLight.otf */; }; DACA29671705DF81002C6C22 /* SourceCodePro-ExtraLight.otf in Resources */ = {isa = PBXBuildFile; fileRef = DACA268E1705DF81002C6C22 /* SourceCodePro-ExtraLight.otf */; };
DACA29681705DF81002C6C22 /* SourceCodePro-Black.otf in Resources */ = {isa = PBXBuildFile; fileRef = DACA268F1705DF81002C6C22 /* SourceCodePro-Black.otf */; }; DACA29681705DF81002C6C22 /* SourceCodePro-Black.otf in Resources */ = {isa = PBXBuildFile; fileRef = DACA268F1705DF81002C6C22 /* SourceCodePro-Black.otf */; };
DACA29731705E1A8002C6C22 /* ciphers.plist in Resources */ = {isa = PBXBuildFile; fileRef = DACA29711705E1A8002C6C22 /* ciphers.plist */; };
DACA29741705E1A8002C6C22 /* dictionary.lst in Resources */ = {isa = PBXBuildFile; fileRef = DACA29721705E1A8002C6C22 /* dictionary.lst */; }; DACA29741705E1A8002C6C22 /* dictionary.lst in Resources */ = {isa = PBXBuildFile; fileRef = DACA29721705E1A8002C6C22 /* dictionary.lst */; };
DACA298D1705E2BD002C6C22 /* JRSwizzle.h in Headers */ = {isa = PBXBuildFile; fileRef = DACA29771705E2BD002C6C22 /* JRSwizzle.h */; }; DACA298D1705E2BD002C6C22 /* JRSwizzle.h in Headers */ = {isa = PBXBuildFile; fileRef = DACA29771705E2BD002C6C22 /* JRSwizzle.h */; };
DACA299A1705E2BD002C6C22 /* JRSwizzle.m in Sources */ = {isa = PBXBuildFile; fileRef = DACA298C1705E2BD002C6C22 /* JRSwizzle.m */; }; DACA299A1705E2BD002C6C22 /* JRSwizzle.m in Sources */ = {isa = PBXBuildFile; fileRef = DACA298C1705E2BD002C6C22 /* JRSwizzle.m */; };
@@ -950,8 +953,8 @@
DA6773C51A4746AF004F356A /* mpw-util.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-util.c"; sourceTree = "<group>"; }; DA6773C51A4746AF004F356A /* mpw-util.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-util.c"; sourceTree = "<group>"; };
DA6773C61A4746AF004F356A /* mpw-util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-util.h"; sourceTree = "<group>"; }; DA6773C61A4746AF004F356A /* mpw-util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-util.h"; sourceTree = "<group>"; };
DA67743B1A474A03004F356A /* mpw-test */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "mpw-test"; sourceTree = BUILT_PRODUCTS_DIR; }; DA67743B1A474A03004F356A /* mpw-test */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "mpw-test"; sourceTree = BUILT_PRODUCTS_DIR; };
DA7471A01F2B71A9005F3468 /* mpw-marshall-util.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-marshall-util.c"; sourceTree = "<group>"; }; DA7471A01F2B71A9005F3468 /* mpw-marshal-util.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-marshal-util.c"; sourceTree = "<group>"; };
DA7471A11F2B71A9005F3468 /* mpw-marshall-util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-marshall-util.h"; sourceTree = "<group>"; }; DA7471A11F2B71A9005F3468 /* mpw-marshal-util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-marshal-util.h"; sourceTree = "<group>"; };
DA771FE51E6E15A1004D7EDE /* MasterPassword-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MasterPassword-Prefix.pch"; sourceTree = "<group>"; }; DA771FE51E6E15A1004D7EDE /* MasterPassword-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MasterPassword-Prefix.pch"; sourceTree = "<group>"; };
DA831A271A6E1146000AC234 /* mpw-algorithm_v0.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = "mpw-algorithm_v0.c"; sourceTree = "<group>"; }; DA831A271A6E1146000AC234 /* mpw-algorithm_v0.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = "mpw-algorithm_v0.c"; sourceTree = "<group>"; };
DA831A281A6E1146000AC234 /* mpw-algorithm_v1.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = "mpw-algorithm_v1.c"; sourceTree = "<group>"; }; DA831A281A6E1146000AC234 /* mpw-algorithm_v1.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = "mpw-algorithm_v1.c"; sourceTree = "<group>"; };
@@ -966,8 +969,8 @@
DA9261501BE1A86700369DE5 /* Fabric.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Fabric.framework; sourceTree = "<group>"; }; DA9261501BE1A86700369DE5 /* Fabric.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Fabric.framework; sourceTree = "<group>"; };
DA9261531BE1A88900369DE5 /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; DA9261531BE1A88900369DE5 /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; };
DA9261551BE1A89600369DE5 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; DA9261551BE1A89600369DE5 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
DAA449D31EEC4B6B00E7BDD5 /* mpw-marshall.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-marshall.c"; sourceTree = "<group>"; }; DAA449D31EEC4B6B00E7BDD5 /* mpw-marshal.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-marshal.c"; sourceTree = "<group>"; };
DAA449D41EEC4B6B00E7BDD5 /* mpw-marshall.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-marshall.h"; sourceTree = "<group>"; }; DAA449D41EEC4B6B00E7BDD5 /* mpw-marshal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-marshal.h"; sourceTree = "<group>"; };
DAAA81AF195A8D1300FA30D9 /* gradient.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = gradient.png; sourceTree = "<group>"; }; DAAA81AF195A8D1300FA30D9 /* gradient.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = gradient.png; sourceTree = "<group>"; };
DAADCC3E19FAFFAD00987B1D /* NSNotificationCenter+PearlEasyCleanup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSNotificationCenter+PearlEasyCleanup.h"; sourceTree = "<group>"; }; DAADCC3E19FAFFAD00987B1D /* NSNotificationCenter+PearlEasyCleanup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSNotificationCenter+PearlEasyCleanup.h"; sourceTree = "<group>"; };
DAADCC3F19FAFFAD00987B1D /* NSPersistentStore+PearlMigration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSPersistentStore+PearlMigration.h"; sourceTree = "<group>"; }; DAADCC3F19FAFFAD00987B1D /* NSPersistentStore+PearlMigration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSPersistentStore+PearlMigration.h"; sourceTree = "<group>"; };
@@ -977,6 +980,8 @@
DAADCC6719FB007F00987B1D /* NSManagedObjectModel+KCOrderedAccessorFix.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSManagedObjectModel+KCOrderedAccessorFix.m"; sourceTree = "<group>"; }; DAADCC6719FB007F00987B1D /* NSManagedObjectModel+KCOrderedAccessorFix.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSManagedObjectModel+KCOrderedAccessorFix.m"; sourceTree = "<group>"; };
DAAF16631F5897EA0013B8AE /* mpw-cli-util.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-cli-util.c"; sourceTree = "<group>"; }; DAAF16631F5897EA0013B8AE /* mpw-cli-util.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-cli-util.c"; sourceTree = "<group>"; };
DAAF16641F5897EA0013B8AE /* mpw-cli-util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-cli-util.h"; sourceTree = "<group>"; }; DAAF16641F5897EA0013B8AE /* mpw-cli-util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-cli-util.h"; sourceTree = "<group>"; };
DAB07C9E1F7725D400CC6D43 /* aes.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = aes.c; sourceTree = "<group>"; };
DAB07C9F1F7725D400CC6D43 /* aes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = aes.h; sourceTree = "<group>"; };
DAB7AE591F3D74E700C856B1 /* libjson-c.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libjson-c.a"; path = "External/libjson-c/libjson-c-osx/lib/libjson-c.a"; sourceTree = "<group>"; }; DAB7AE591F3D74E700C856B1 /* libjson-c.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libjson-c.a"; path = "External/libjson-c/libjson-c-osx/lib/libjson-c.a"; sourceTree = "<group>"; };
DAB7AE7B1F3D757B00C856B1 /* arraylist.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = arraylist.h; sourceTree = "<group>"; }; DAB7AE7B1F3D757B00C856B1 /* arraylist.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = arraylist.h; sourceTree = "<group>"; };
DAB7AE7C1F3D757B00C856B1 /* bits.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bits.h; sourceTree = "<group>"; }; DAB7AE7C1F3D757B00C856B1 /* bits.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bits.h; sourceTree = "<group>"; };
@@ -1046,7 +1051,6 @@
DACA24581705DF7D002C6C22 /* menu-icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menu-icon.png"; sourceTree = "<group>"; }; DACA24581705DF7D002C6C22 /* menu-icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menu-icon.png"; sourceTree = "<group>"; };
DACA268E1705DF81002C6C22 /* SourceCodePro-ExtraLight.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SourceCodePro-ExtraLight.otf"; sourceTree = "<group>"; }; DACA268E1705DF81002C6C22 /* SourceCodePro-ExtraLight.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SourceCodePro-ExtraLight.otf"; sourceTree = "<group>"; };
DACA268F1705DF81002C6C22 /* SourceCodePro-Black.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SourceCodePro-Black.otf"; sourceTree = "<group>"; }; DACA268F1705DF81002C6C22 /* SourceCodePro-Black.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SourceCodePro-Black.otf"; sourceTree = "<group>"; };
DACA29711705E1A8002C6C22 /* ciphers.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = ciphers.plist; sourceTree = "<group>"; };
DACA29721705E1A8002C6C22 /* dictionary.lst */ = {isa = PBXFileReference; fileEncoding = 1; lastKnownFileType = text; path = dictionary.lst; sourceTree = "<group>"; }; DACA29721705E1A8002C6C22 /* dictionary.lst */ = {isa = PBXFileReference; fileEncoding = 1; lastKnownFileType = text; path = dictionary.lst; sourceTree = "<group>"; };
DACA29771705E2BD002C6C22 /* JRSwizzle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JRSwizzle.h; sourceTree = "<group>"; }; DACA29771705E2BD002C6C22 /* JRSwizzle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JRSwizzle.h; sourceTree = "<group>"; };
DACA298C1705E2BD002C6C22 /* JRSwizzle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JRSwizzle.m; sourceTree = "<group>"; }; DACA298C1705E2BD002C6C22 /* JRSwizzle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JRSwizzle.m; sourceTree = "<group>"; };
@@ -1878,6 +1882,8 @@
DA6773291A4746AF004F356A /* C */ = { DA6773291A4746AF004F356A /* C */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DAB07C9E1F7725D400CC6D43 /* aes.c */,
DAB07C9F1F7725D400CC6D43 /* aes.h */,
DA5B0B381F36467300B663F0 /* base64.c */, DA5B0B381F36467300B663F0 /* base64.c */,
DA5B0B391F36467300B663F0 /* base64.h */, DA5B0B391F36467300B663F0 /* base64.h */,
DA1C7AB71F1A8F6E009A3551 /* cli */, DA1C7AB71F1A8F6E009A3551 /* cli */,
@@ -1887,10 +1893,10 @@
DA831A2A1A6E1146000AC234 /* mpw-algorithm_v3.c */, DA831A2A1A6E1146000AC234 /* mpw-algorithm_v3.c */,
DA6773BB1A4746AF004F356A /* mpw-algorithm.c */, DA6773BB1A4746AF004F356A /* mpw-algorithm.c */,
DA6773BC1A4746AF004F356A /* mpw-algorithm.h */, DA6773BC1A4746AF004F356A /* mpw-algorithm.h */,
DA7471A01F2B71A9005F3468 /* mpw-marshall-util.c */, DA7471A01F2B71A9005F3468 /* mpw-marshal-util.c */,
DA7471A11F2B71A9005F3468 /* mpw-marshall-util.h */, DA7471A11F2B71A9005F3468 /* mpw-marshal-util.h */,
DAA449D31EEC4B6B00E7BDD5 /* mpw-marshall.c */, DAA449D31EEC4B6B00E7BDD5 /* mpw-marshal.c */,
DAA449D41EEC4B6B00E7BDD5 /* mpw-marshall.h */, DAA449D41EEC4B6B00E7BDD5 /* mpw-marshal.h */,
DA6773C21A4746AF004F356A /* mpw-types.c */, DA6773C21A4746AF004F356A /* mpw-types.c */,
DA6773C31A4746AF004F356A /* mpw-types.h */, DA6773C31A4746AF004F356A /* mpw-types.h */,
DA6773C51A4746AF004F356A /* mpw-util.c */, DA6773C51A4746AF004F356A /* mpw-util.c */,
@@ -2095,7 +2101,6 @@
DACA29701705E1A8002C6C22 /* Data */ = { DACA29701705E1A8002C6C22 /* Data */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DACA29711705E1A8002C6C22 /* ciphers.plist */,
DACA29721705E1A8002C6C22 /* dictionary.lst */, DACA29721705E1A8002C6C22 /* dictionary.lst */,
); );
path = Data; path = Data;
@@ -2606,7 +2611,6 @@
DACA27381705DF81002C6C22 /* menu-icon.png in Resources */, DACA27381705DF81002C6C22 /* menu-icon.png in Resources */,
DACA29671705DF81002C6C22 /* SourceCodePro-ExtraLight.otf in Resources */, DACA29671705DF81002C6C22 /* SourceCodePro-ExtraLight.otf in Resources */,
DACA29681705DF81002C6C22 /* SourceCodePro-Black.otf in Resources */, DACA29681705DF81002C6C22 /* SourceCodePro-Black.otf in Resources */,
DACA29731705E1A8002C6C22 /* ciphers.plist in Resources */,
DACA29741705E1A8002C6C22 /* dictionary.lst in Resources */, DACA29741705E1A8002C6C22 /* dictionary.lst in Resources */,
DA5E5D081724A667003798D8 /* MasterPassword.entitlements in Resources */, DA5E5D081724A667003798D8 /* MasterPassword.entitlements in Resources */,
DA5E5D0A1724A667003798D8 /* InfoPlist.strings in Resources */, DA5E5D0A1724A667003798D8 /* InfoPlist.strings in Resources */,
@@ -2670,13 +2674,14 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
DA1C7AAA1F1A8F24009A3551 /* mpw-marshall.c in Sources */, DA1C7AAA1F1A8F24009A3551 /* mpw-marshal.c in Sources */,
DA1C7AAB1F1A8F24009A3551 /* mpw-types.c in Sources */, DA1C7AAB1F1A8F24009A3551 /* mpw-types.c in Sources */,
DA5B0B3D1F36467900B663F0 /* base64.c in Sources */, DA5B0B3D1F36467900B663F0 /* base64.c in Sources */,
DAB07CA31F77261400CC6D43 /* aes.c in Sources */,
DAAF16661F5CA3240013B8AE /* mpw-cli-util.c in Sources */, DAAF16661F5CA3240013B8AE /* mpw-cli-util.c in Sources */,
DA1C7AAC1F1A8F24009A3551 /* mpw-util.c in Sources */, DA1C7AAC1F1A8F24009A3551 /* mpw-util.c in Sources */,
DA1C7AC31F1A8FBA009A3551 /* mpw-cli.c in Sources */, DA1C7AC31F1A8FBA009A3551 /* mpw-cli.c in Sources */,
DA7471A31F2B71AE005F3468 /* mpw-marshall-util.c in Sources */, DA7471A31F2B71AE005F3468 /* mpw-marshal-util.c in Sources */,
DA1C7AAD1F1A8F24009A3551 /* mpw-algorithm.c in Sources */, DA1C7AAD1F1A8F24009A3551 /* mpw-algorithm.c in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@@ -2688,6 +2693,7 @@
DA5B0B3C1F36467900B663F0 /* base64.c in Sources */, DA5B0B3C1F36467900B663F0 /* base64.c in Sources */,
DA1C7ACA1F1A8FD8009A3551 /* mpw-types.c in Sources */, DA1C7ACA1F1A8FD8009A3551 /* mpw-types.c in Sources */,
DA1C7ACB1F1A8FD8009A3551 /* mpw-util.c in Sources */, DA1C7ACB1F1A8FD8009A3551 /* mpw-util.c in Sources */,
DAB07CA61F77262400CC6D43 /* aes.c in Sources */,
DA1C7AD71F1A8FE6009A3551 /* mpw-bench.c in Sources */, DA1C7AD71F1A8FE6009A3551 /* mpw-bench.c in Sources */,
DA1C7ACD1F1A8FD8009A3551 /* mpw-algorithm.c in Sources */, DA1C7ACD1F1A8FD8009A3551 /* mpw-algorithm.c in Sources */,
); );
@@ -2701,13 +2707,14 @@
DA5E5CF61724A667003798D8 /* MPAlgorithm.m in Sources */, DA5E5CF61724A667003798D8 /* MPAlgorithm.m in Sources */,
DA2686201EBFD7A40001E37E /* MPSiteEntity+CoreDataProperties.m in Sources */, DA2686201EBFD7A40001E37E /* MPSiteEntity+CoreDataProperties.m in Sources */,
DA5E5CF71724A667003798D8 /* MPAlgorithmV0.m in Sources */, DA5E5CF71724A667003798D8 /* MPAlgorithmV0.m in Sources */,
DAA449D51EEC4B6B00E7BDD5 /* mpw-marshall.c in Sources */, DAA449D51EEC4B6B00E7BDD5 /* mpw-marshal.c in Sources */,
DA26861E1EBFD7A40001E37E /* MPGeneratedSiteEntity+CoreDataProperties.m in Sources */, DA26861E1EBFD7A40001E37E /* MPGeneratedSiteEntity+CoreDataProperties.m in Sources */,
DA5E5CF81724A667003798D8 /* MPAlgorithmV1.m in Sources */, DA5E5CF81724A667003798D8 /* MPAlgorithmV1.m in Sources */,
DA2686231EBFD7A40001E37E /* MPStoredSiteEntity+CoreDataClass.m in Sources */, DA2686231EBFD7A40001E37E /* MPStoredSiteEntity+CoreDataClass.m in Sources */,
DA2686221EBFD7A40001E37E /* MPSiteQuestionEntity+CoreDataProperties.m in Sources */, DA2686221EBFD7A40001E37E /* MPSiteQuestionEntity+CoreDataProperties.m in Sources */,
DA4571201F572F3200D54152 /* PearlCryptUtils.m in Sources */, DA4571201F572F3200D54152 /* PearlCryptUtils.m in Sources */,
DACBFCDF1C59B22E007EF90F /* NSMutableSet+Pearl.m in Sources */, DACBFCDF1C59B22E007EF90F /* NSMutableSet+Pearl.m in Sources */,
DAB07CA01F7725D400CC6D43 /* aes.c in Sources */,
DA2686241EBFD7A40001E37E /* MPStoredSiteEntity+CoreDataProperties.m in Sources */, DA2686241EBFD7A40001E37E /* MPStoredSiteEntity+CoreDataProperties.m in Sources */,
DA6774311A4746AF004F356A /* mpw-util.c in Sources */, DA6774311A4746AF004F356A /* mpw-util.c in Sources */,
DA5E5CF91724A667003798D8 /* MPAppDelegate_Key.m in Sources */, DA5E5CF91724A667003798D8 /* MPAppDelegate_Key.m in Sources */,
@@ -2738,7 +2745,7 @@
DA4DAE941A7D8117003E5423 /* MPAlgorithmV3.m in Sources */, DA4DAE941A7D8117003E5423 /* MPAlgorithmV3.m in Sources */,
DA4DAE951A7D8117003E5423 /* MPTypes.m in Sources */, DA4DAE951A7D8117003E5423 /* MPTypes.m in Sources */,
93D393A1646430FAAC73E7FE /* MPMacApplication.m in Sources */, 93D393A1646430FAAC73E7FE /* MPMacApplication.m in Sources */,
DA7471A61F2B71B9005F3468 /* mpw-marshall-util.c in Sources */, DA7471A61F2B71B9005F3468 /* mpw-marshal-util.c in Sources */,
93D398D1F5D8CD5A22AF6929 /* MPGradientView.m in Sources */, 93D398D1F5D8CD5A22AF6929 /* MPGradientView.m in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@@ -2749,6 +2756,7 @@
files = ( files = (
DA1C7AD81F1A8FF4009A3551 /* mpw-tests-util.c in Sources */, DA1C7AD81F1A8FF4009A3551 /* mpw-tests-util.c in Sources */,
DA6774451A474A3B004F356A /* mpw-types.c in Sources */, DA6774451A474A3B004F356A /* mpw-types.c in Sources */,
DAB07CA51F77261C00CC6D43 /* aes.c in Sources */,
DA6774461A474A3B004F356A /* mpw-util.c in Sources */, DA6774461A474A3B004F356A /* mpw-util.c in Sources */,
DA1C7AD91F1A8FF4009A3551 /* mpw-tests.c in Sources */, DA1C7AD91F1A8FF4009A3551 /* mpw-tests.c in Sources */,
DA5B0B3B1F36467800B663F0 /* base64.c in Sources */, DA5B0B3B1F36467800B663F0 /* base64.c in Sources */,
@@ -3048,7 +3056,7 @@
CLANG_WARN_DOCUMENTATION_COMMENTS = NO; CLANG_WARN_DOCUMENTATION_COMMENTS = NO;
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"", "\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"",
"/usr/include//libxml2", /usr/include/libxml2,
"$(inherited)", "$(inherited)",
); );
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
@@ -3482,7 +3490,7 @@
CLANG_WARN_DOCUMENTATION_COMMENTS = NO; CLANG_WARN_DOCUMENTATION_COMMENTS = NO;
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"", "\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"",
"/usr/include//libxml2", /usr/include/libxml2,
"$(inherited)", "$(inherited)",
); );
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
@@ -3502,7 +3510,7 @@
CLANG_WARN_DOCUMENTATION_COMMENTS = NO; CLANG_WARN_DOCUMENTATION_COMMENTS = NO;
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"", "\"$(BUILD_ROOT)/../IntermediateBuildFilesPath/UninstalledProducts/$(PLATFORM_NAME)/include\"",
"/usr/include//libxml2", /usr/include/libxml2,
"$(inherited)", "$(inherited)",
); );
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (

View File

@@ -1,90 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>MPGeneratedSiteEntity</key>
<dict>
<key>Login Name</key>
<array>
<string>cvccvcvcv</string>
</array>
<key>Phrase</key>
<array>
<string>cvcc cvc cvccvcv cvc</string>
<string>cvc cvccvcvcv cvcv</string>
<string>cv cvccv cvc cvcvccv</string>
</array>
<key>Maximum Security Password</key>
<array>
<string>anoxxxxxxxxxxxxxxxxx</string>
<string>axxxxxxxxxxxxxxxxxno</string>
</array>
<key>Long Password</key>
<array>
<string>CvcvnoCvcvCvcv</string>
<string>CvcvCvcvnoCvcv</string>
<string>CvcvCvcvCvcvno</string>
<string>CvccnoCvcvCvcv</string>
<string>CvccCvcvnoCvcv</string>
<string>CvccCvcvCvcvno</string>
<string>CvcvnoCvccCvcv</string>
<string>CvcvCvccnoCvcv</string>
<string>CvcvCvccCvcvno</string>
<string>CvcvnoCvcvCvcc</string>
<string>CvcvCvcvnoCvcc</string>
<string>CvcvCvcvCvccno</string>
<string>CvccnoCvccCvcv</string>
<string>CvccCvccnoCvcv</string>
<string>CvccCvccCvcvno</string>
<string>CvcvnoCvccCvcc</string>
<string>CvcvCvccnoCvcc</string>
<string>CvcvCvccCvccno</string>
<string>CvccnoCvcvCvcc</string>
<string>CvccCvcvnoCvcc</string>
<string>CvccCvcvCvccno</string>
</array>
<key>Medium Password</key>
<array>
<string>CvcnoCvc</string>
<string>CvcCvcno</string>
</array>
<key>Basic Password</key>
<array>
<string>aaanaaan</string>
<string>aannaaan</string>
<string>aaannaaa</string>
</array>
<key>Short Password</key>
<array>
<string>Cvcn</string>
</array>
<key>PIN</key>
<array>
<string>nnnn</string>
</array>
</dict>
<key>MPCharacterClasses</key>
<dict>
<key>V</key>
<string>AEIOU</string>
<key>C</key>
<string>BCDFGHJKLMNPQRSTVWXYZ</string>
<key>v</key>
<string>aeiou</string>
<key>c</key>
<string>bcdfghjklmnpqrstvwxyz</string>
<key>A</key>
<string>AEIOUBCDFGHJKLMNPQRSTVWXYZ</string>
<key>a</key>
<string>AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz</string>
<key>n</key>
<string>0123456789</string>
<key>o</key>
<string>@&amp;%?,=[]_:-+*$#!'^~;()/.</string>
<key>x</key>
<string>AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz0123456789!@#$%^&amp;*()</string>
<key> </key>
<string> </string>
</dict>
</dict>
</plist>

View File

@@ -23,7 +23,7 @@ mkdir -p "$prefix/lib" \
export LDFLAGS="-arch i386 -isysroot $SDKROOT -mios-simulator-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} -flto $LDFLAGS" export LDFLAGS="-arch i386 -isysroot $SDKROOT -mios-simulator-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} -flto $LDFLAGS"
export CPPFLAGS="$CFLAGS $CPPFLAGS" export CPPFLAGS="$CFLAGS $CPPFLAGS"
[[ -e Makefile ]] && make -s distclean [[ -e Makefile ]] && make -s distclean
./configure --host=i686-apple --disable-shared --prefix="$prefix_i386" ./configure --host=i686-apple --disable-shared --enable-minimal --prefix="$prefix_i386"
make -j3 install make -j3 install
) )
( (
@@ -34,7 +34,7 @@ mkdir -p "$prefix/lib" \
export LDFLAGS="-arch x86_64 -isysroot $SDKROOT -mios-simulator-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} -flto $LDFLAGS" export LDFLAGS="-arch x86_64 -isysroot $SDKROOT -mios-simulator-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} -flto $LDFLAGS"
export CPPFLAGS="$CFLAGS $CPPFLAGS" export CPPFLAGS="$CFLAGS $CPPFLAGS"
[[ -e Makefile ]] && make -s distclean [[ -e Makefile ]] && make -s distclean
./configure --host=x86_64-apple --disable-shared --prefix="$prefix_x86_64" ./configure --host=x86_64-apple --disable-shared --enable-minimal --prefix="$prefix_x86_64"
make -j3 install make -j3 install
) )
( (
@@ -45,7 +45,7 @@ mkdir -p "$prefix/lib" \
export LDFLAGS="-mthumb -arch armv7 -isysroot $SDKROOT -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} -flto $LDFLAGS" export LDFLAGS="-mthumb -arch armv7 -isysroot $SDKROOT -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} -flto $LDFLAGS"
export CPPFLAGS="$CFLAGS $CPPFLAGS" export CPPFLAGS="$CFLAGS $CPPFLAGS"
[[ -e Makefile ]] && make -s distclean [[ -e Makefile ]] && make -s distclean
./configure --host=x86_64-apple --target=arm-apple --disable-shared --prefix="$prefix_armv7" ./configure --host=x86_64-apple --target=arm-apple --disable-shared --enable-minimal --prefix="$prefix_armv7"
make -j3 install make -j3 install
) )
( (
@@ -56,7 +56,7 @@ mkdir -p "$prefix/lib" \
export LDFLAGS="-mthumb -arch armv7s -isysroot $SDKROOT -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} -flto $LDFLAGS" export LDFLAGS="-mthumb -arch armv7s -isysroot $SDKROOT -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} -flto $LDFLAGS"
export CPPFLAGS="$CFLAGS $CPPFLAGS" export CPPFLAGS="$CFLAGS $CPPFLAGS"
[[ -e Makefile ]] && make -s distclean [[ -e Makefile ]] && make -s distclean
./configure --host=x86_64-apple --target=arm-apple --disable-shared --prefix="$prefix_armv7s" ./configure --host=x86_64-apple --target=arm-apple --disable-shared --enable-minimal --prefix="$prefix_armv7s"
make -j3 install make -j3 install
) )
( (
@@ -67,7 +67,7 @@ mkdir -p "$prefix/lib" \
export LDFLAGS="-mthumb -arch arm64 -isysroot $SDKROOT -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} -flto $LDFLAGS" export LDFLAGS="-mthumb -arch arm64 -isysroot $SDKROOT -mios-version-min=${IPHONEOS_DEPLOYMENT_TARGET:-8.0} -flto $LDFLAGS"
export CPPFLAGS="$CFLAGS $CPPFLAGS" export CPPFLAGS="$CFLAGS $CPPFLAGS"
[[ -e Makefile ]] && make -s distclean [[ -e Makefile ]] && make -s distclean
./configure --host=x86_64-apple --target=arm-apple --disable-shared --prefix="$prefix_arm64" ./configure --host=x86_64-apple --target=arm-apple --disable-shared --enable-minimal --prefix="$prefix_arm64"
make -j3 install make -j3 install
) )

View File

@@ -19,7 +19,7 @@ mkdir -p "$prefix"
export LDFLAGS="-arch x86_64 -isysroot $SDKROOT -mmacosx-version-min=${MACOSX_DEPLOYMENT_TARGET:-10.8} -flto $LDFLAGS" export LDFLAGS="-arch x86_64 -isysroot $SDKROOT -mmacosx-version-min=${MACOSX_DEPLOYMENT_TARGET:-10.8} -flto $LDFLAGS"
export CPPFLAGS="$CFLAGS $CPPFLAGS" export CPPFLAGS="$CFLAGS $CPPFLAGS"
[[ -e Makefile ]] && make -s distclean [[ -e Makefile ]] && make -s distclean
./configure --disable-shared --prefix="$prefix" ./configure --disable-shared --enable-minimal --prefix="$prefix"
make -j3 check make -j3 check
make -j3 install make -j3 install
) )

View File

@@ -17,7 +17,7 @@
//============================================================================== //==============================================================================
#import "MPAppDelegate_Store.h" #import "MPAppDelegate_Store.h"
#import "mpw-marshall.h" #import "mpw-marshal.h"
#import "mpw-util.h" #import "mpw-util.h"
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
@@ -566,10 +566,10 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
saveInContext:(NSManagedObjectContext *)context { saveInContext:(NSManagedObjectContext *)context {
// Read metadata for the import file. // Read metadata for the import file.
MPMarshallInfo *info = mpw_marshall_read_info( importData.UTF8String ); MPMarshalInfo *info = mpw_marshal_read_info( importData.UTF8String );
if (info->format == MPMarshallFormatNone) if (info->format == MPMarshalFormatNone)
return MPError( ([NSError errorWithDomain:MPErrorDomain code:MPErrorMarshallCode userInfo:@{ return MPError( ([NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
@"type" : @(MPMarshallErrorFormat), @"type" : @(MPMarshalErrorFormat),
NSLocalizedDescriptionKey: @"This is not a Master Password import file.", NSLocalizedDescriptionKey: @"This is not a Master Password import file.",
}]), @"While importing sites." ); }]), @"While importing sites." );
@@ -589,13 +589,13 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
caseInsensitiveCompare:@(info->keyID)] != NSOrderedSame); caseInsensitiveCompare:@(info->keyID)] != NSOrderedSame);
// Parse import data. // Parse import data.
MPMarshallError importError = { .type = MPMarshallSuccess }; MPMarshalError importError = { .type = MPMarshalSuccess };
MPMarshalledUser *importUser = mpw_marshall_read( importData.UTF8String, info->format, importMasterPassword.UTF8String, &importError ); MPMarshalledUser *importUser = mpw_marshal_read( importData.UTF8String, info->format, importMasterPassword.UTF8String, &importError );
mpw_marshal_info_free( &info ); mpw_marshal_info_free( &info );
@try { @try {
if (!importUser || importError.type != MPMarshallSuccess) if (!importUser || importError.type != MPMarshalSuccess)
return MPError( ([NSError errorWithDomain:MPErrorDomain code:MPErrorMarshallCode userInfo:@{ return MPError( ([NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
@"type" : @(importError.type), @"type" : @(importError.type),
NSLocalizedDescriptionKey: @(importError.description), NSLocalizedDescriptionKey: @(importError.description),
}]), @"While importing sites." ); }]), @"While importing sites." );
@@ -707,7 +707,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
NSString *masterPassword = askImportPassword( user.name ); NSString *masterPassword = askImportPassword( user.name );
inf( @"Exporting sites, %@, for user: %@", revealPasswords? @"revealing passwords": @"omitting passwords", user.userID ); inf( @"Exporting sites, %@, for user: %@", revealPasswords? @"revealing passwords": @"omitting passwords", user.userID );
MPMarshalledUser *exportUser = mpw_marshall_user( user.name.UTF8String, masterPassword.UTF8String, user.algorithm.version ); MPMarshalledUser *exportUser = mpw_marshal_user( user.name.UTF8String, masterPassword.UTF8String, user.algorithm.version );
exportUser->redacted = !revealPasswords; exportUser->redacted = !revealPasswords;
exportUser->avatar = (unsigned int)user.avatar; exportUser->avatar = (unsigned int)user.avatar;
exportUser->defaultType = user.defaultType; exportUser->defaultType = user.defaultType;
@@ -721,7 +721,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
? [site.algorithm exportPasswordForSite:site usingKey:self.key] ? [site.algorithm exportPasswordForSite:site usingKey:self.key]
: [site.algorithm resolvePasswordForSite:site usingKey:self.key]; : [site.algorithm resolvePasswordForSite:site usingKey:self.key];
MPMarshalledSite *exportSite = mpw_marshall_site( exportUser, MPMarshalledSite *exportSite = mpw_marshal_site( exportUser,
site.name.UTF8String, site.type, counter, site.algorithm.version ); site.name.UTF8String, site.type, counter, site.algorithm.version );
exportSite->content = content.UTF8String; exportSite->content = content.UTF8String;
exportSite->loginContent = site.loginName.UTF8String; exportSite->loginContent = site.loginName.UTF8String;
@@ -735,15 +735,15 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
} }
char *export = NULL; char *export = NULL;
MPMarshallError exportError = (MPMarshallError){ .type= MPMarshallSuccess }; MPMarshalError exportError = (MPMarshalError){ .type= MPMarshalSuccess };
mpw_marshall_write( &export, MPMarshallFormatFlat, exportUser, &exportError ); mpw_marshal_write( &export, MPMarshalFormatFlat, exportUser, &exportError );
NSString *mpsites = nil; NSString *mpsites = nil;
if (export && exportError.type == MPMarshallSuccess) if (export && exportError.type == MPMarshalSuccess)
mpsites = [NSString stringWithCString:export encoding:NSUTF8StringEncoding]; mpsites = [NSString stringWithCString:export encoding:NSUTF8StringEncoding];
mpw_free_string( &export ); mpw_free_string( &export );
resultBlock( mpsites, exportError.type == MPMarshallSuccess? nil: resultBlock( mpsites, exportError.type == MPMarshalSuccess? nil:
[NSError errorWithDomain:MPErrorDomain code:MPErrorMarshallCode userInfo:@{ [NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
@"type" : @(exportError.type), @"type" : @(exportError.type),
NSLocalizedDescriptionKey: @(exportError.description), NSLocalizedDescriptionKey: @(exportError.description),
}] ); }] );

View File

@@ -22,7 +22,7 @@
__BEGIN_DECLS __BEGIN_DECLS
extern NSString *const MPErrorDomain; extern NSString *const MPErrorDomain;
extern NSInteger const MPErrorHangCode; extern NSInteger const MPErrorHangCode;
extern NSInteger const MPErrorMarshallCode; extern NSInteger const MPErrorMarshalCode;
extern NSString *const MPSignedInNotification; extern NSString *const MPSignedInNotification;
extern NSString *const MPSignedOutNotification; extern NSString *const MPSignedOutNotification;

View File

@@ -20,7 +20,7 @@
NSString *const MPErrorDomain = @"MPErrorDomain"; NSString *const MPErrorDomain = @"MPErrorDomain";
NSInteger const MPErrorHangCode = 1; NSInteger const MPErrorHangCode = 1;
NSInteger const MPErrorMarshallCode = 1; NSInteger const MPErrorMarshalCode = 1;
NSString *const MPSignedInNotification = @"MPSignedInNotification"; NSString *const MPSignedInNotification = @"MPSignedInNotification";
NSString *const MPSignedOutNotification = @"MPSignedOutNotification"; NSString *const MPSignedOutNotification = @"MPSignedOutNotification";

View File

@@ -20,7 +20,7 @@
#import "MPAppDelegate_Key.h" #import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h" #import "MPAppDelegate_Store.h"
#import "MPStoreViewController.h" #import "MPStoreViewController.h"
#import "mpw-marshall.h" #import "mpw-marshal.h"
@interface MPiOSAppDelegate()<UIDocumentInteractionControllerDelegate> @interface MPiOSAppDelegate()<UIDocumentInteractionControllerDelegate>
@@ -236,8 +236,8 @@
PearlNotMainQueue( ^{ PearlNotMainQueue( ^{
NSString *importData = [UIPasteboard generalPasteboard].string; NSString *importData = [UIPasteboard generalPasteboard].string;
MPMarshallInfo *importInfo = mpw_marshall_read_info( importData.UTF8String ); MPMarshalInfo *importInfo = mpw_marshal_read_info( importData.UTF8String );
if (importInfo->format != MPMarshallFormatNone) if (importInfo->format != MPMarshalFormatNone)
[PearlAlert showAlertWithTitle:@"Import Sites?" message: [PearlAlert showAlertWithTitle:@"Import Sites?" message:
@"We've detected Master Password import sites on your pasteboard, would you like to import them?" @"We've detected Master Password import sites on your pasteboard, would you like to import them?"
viewStyle:UIAlertViewStyleDefault initAlert:nil viewStyle:UIAlertViewStyleDefault initAlert:nil

View File

@@ -1,13 +1,135 @@
project(mpw C) ### CMAKE
cmake_minimum_required(VERSION 3.0.2) project( mpw C )
cmake_minimum_required( VERSION 3.0.2 )
set(CMAKE_BUILD_TYPE Release)
set(CMAKE_C_FLAGS "-O3 -DMPW_SODIUM=1 -DMPW_JSON=1")
include_directories(core cli) ### CONFIGURATION
file(GLOB SOURCES "core/*.c" "cli/mpw-cli*.c") # Features.
add_executable(mpw ${SOURCES}) option( USE_SODIUM "Implement crypto functions with sodium (depends on libsodium)." ON )
option( USE_JSON "Support JSON-based user configuration format (depends on libjson-c)." ON )
option( USE_COLOR "Colorized identicon (depends on libncurses)." ON )
option( USE_XML "XML parsing (depends on libxml2)." ON )
find_library(libsodium REQUIRED) option( BUILD_MPW "C CLI version of Master Password (needs: mpw_sodium, optional: mpw_color, mpw_json)." ON )
find_library(libjson-c REQUIRED) option( BUILD_MPW_BENCH "C CLI Master Password benchmark utility (needs: mpw_sodium)." OFF )
target_link_libraries(mpw sodium json-c) option( BUILD_MPW_TESTS "C Master Password algorithm test suite (needs: mpw_sodium, mpw_xml)." OFF )
# Default build flags.
set( CMAKE_BUILD_TYPE Release )
set( CMAKE_C_FLAGS "-O3" )
# Version.
file( READ "VERSION" mpw_version )
add_definitions( -DMP_VERSION=${VERSION} )
### DEPENDENCIES
function( use_mpw_sodium t r )
if( USE_SODIUM )
target_link_libraries( ${t} sodium )
target_compile_definitions( ${t} PUBLIC -DMPW_SODIUM=1 )
message(STATUS "${t}: USE_SODIUM is enabled.")
elseif( r STREQUAL "required" )
message(FATAL_ERROR "${t}: USE_SODIUM was required but is not enabled. Please enable the option or remove this target.")
else()
message(STATUS "${t}: USE_SODIUM is supported but not enabled.")
endif()
endfunction()
function( use_mpw_color t )
if( USE_COLOR )
target_link_libraries( ${t} curses)
target_compile_definitions( ${t} PUBLIC -DMPW_COLOR=1 )
message(STATUS "${t}: USE_COLOR is enabled.")
elseif( r STREQUAL "required" )
message(FATAL_ERROR "${t}: USE_COLOR was required but is not enabled. Please enable the option or remove this target.")
else()
message(STATUS "${t}: USE_COLOR is supported but not enabled.")
endif()
endfunction()
function( use_mpw_json t )
if( USE_JSON )
target_link_libraries( ${t} json-c)
target_compile_definitions( ${t} PUBLIC -DMPW_JSON=1 )
message(STATUS "${t}: USE_JSON is enabled.")
elseif( r STREQUAL "required" )
message(FATAL_ERROR "${t}: USE_JSON was required but is not enabled. Please enable the option or remove this target.")
else()
message(STATUS "${t}: USE_JSON is supported but not enabled.")
endif()
endfunction()
function( use_mpw_xml t r )
find_package( LIBXML2 )
if( USE_XML )
if ( LIBXML2_FOUND )
target_include_directories( ${t} PUBLIC ${LIBXML2_INCLUDE_DIR} )
target_link_libraries( ${t} ${LIBXML2_LIBRARIES} )
target_compile_definitions( ${t} PUBLIC -DMPW_XML=1 ${LIBXML2_DEFINITIONS} )
message(STATUS "${t}: USE_XML is enabled.")
elseif( r STREQUAL "required" )
message(FATAL_ERROR "${t}: USE_XML was enabled but is missing libxml2. Please install this library before continuing.")
else()
message(WARNING "${t}: USE_XML was enabled but is missing libxml2. Will continue with USE_XML disabled!")
endif()
elseif( r STREQUAL "required" )
message(FATAL_ERROR "${t}: USE_XML was required but is not enabled. Please enable the option or remove this target.")
else()
message(STATUS "${t}: USE_XML is supported but not enabled.")
endif()
endfunction()
### TARGET: MPW
if( BUILD_MPW )
# target
add_executable( mpw "core/base64.c" "core/aes.c" "core/mpw-algorithm.c" "core/mpw-types.c" "core/mpw-util.c" "core/mpw-marshal-util.c" "core/mpw-marshal.c"
"cli/mpw-cli-util.c" "cli/mpw-cli.c" )
target_include_directories( mpw PUBLIC core cli )
# dependencies
use_mpw_sodium( mpw required )
use_mpw_color( mpw optional )
use_mpw_json( mpw optional )
endif()
### TARGET: MPW-BENCH
if( BUILD_MPW_BENCH )
# target
add_executable( mpw_bench "core/base64.c" "core/aes.c" "core/mpw-algorithm.c" "core/mpw-types.c" "core/mpw-util.c"
"cli/mpw-bench.c" )
target_include_directories( mpw_bench PUBLIC core cli )
# dependencies
use_mpw_sodium( mpw_bench required )
endif()
### TARGET: MPW-TESTS
if( BUILD_MPW_TESTS )
# target
add_executable( mpw_tests "core/base64.c" "core/aes.c" "core/mpw-algorithm.c" "core/mpw-types.c" "core/mpw-util.c"
"cli/mpw-tests-util.c" "cli/mpw-tests.c" )
target_include_directories( mpw_tests PUBLIC core cli )
# dependencies
use_mpw_sodium( mpw_tests required )
use_mpw_xml( mpw_tests required )
endif()

View File

@@ -15,7 +15,22 @@ Note that the build depends on your system having certain dependencies already i
By default, you'll need to have at least `libsodium`, `libjson-c` and `libncurses` installed. By default, you'll need to have at least `libsodium`, `libjson-c` and `libncurses` installed.
### Details ## Building with cmake
There is also a cmake configuration you can use to build instead of using the `./build` script. While `./build` depends on Bash and is geared toward POSIX systems, cmake is platform-independent. You should use your platform's cmake tools to continue. On POSIX systems, you can do this:
cmake . && make
To get a list of options supported by the cmake configuration, use:
cmake -LH
Options can be toggled like so:
cmake -DUSE_COLOR=OFF -DBUILD_MPW_TESTS=ON . && make
## Details
The build script comes with a default configuration which can be adjusted. Full details on the build script are available by opening the build script file. The build script comes with a default configuration which can be adjusted. Full details on the build script are available by opening the build script file.

View File

@@ -73,7 +73,7 @@ mpw() {
) )
# build # build
cc "${cflags[@]}" "$@" "core/base64.c" "core/mpw-algorithm.c" "core/mpw-types.c" "core/mpw-util.c" "core/mpw-marshall-util.c" "core/mpw-marshall.c" "cli/mpw-cli-util.c" \ cc "${cflags[@]}" "$@" "core/base64.c" "core/aes.c" "core/mpw-algorithm.c" "core/mpw-types.c" "core/mpw-util.c" "core/mpw-marshal-util.c" "core/mpw-marshal.c" "cli/mpw-cli-util.c" \
"${ldflags[@]}" "cli/mpw-cli.c" -o "mpw" "${ldflags[@]}" "cli/mpw-cli.c" -o "mpw"
echo "done! You can now run ./mpw-cli-tests, ./install or use ./$_" echo "done! You can now run ./mpw-cli-tests, ./install or use ./$_"
} }
@@ -98,7 +98,7 @@ mpw-bench() {
) )
# build # build
cc "${cflags[@]}" "$@" "core/base64.c" "core/mpw-algorithm.c" "core/mpw-types.c" "core/mpw-util.c" \ cc "${cflags[@]}" "$@" "core/base64.c" "core/aes.c" "core/mpw-algorithm.c" "core/mpw-types.c" "core/mpw-util.c" \
"${ldflags[@]}" "cli/mpw-bench.c" -o "mpw-bench" "${ldflags[@]}" "cli/mpw-bench.c" -o "mpw-bench"
echo "done! You can now use ./$_" echo "done! You can now use ./$_"
} }
@@ -107,8 +107,8 @@ mpw-bench() {
### TARGET: MPW-TESTS ### TARGET: MPW-TESTS
mpw-tests() { mpw-tests() {
# dependencies # dependencies
use_mpw_xml required
use_mpw_sodium required use_mpw_sodium required
use_mpw_xml required
# target # target
cflags=( cflags=(
@@ -124,7 +124,7 @@ mpw-tests() {
) )
# build # build
cc "${cflags[@]}" "$@" "core/base64.c" "core/mpw-algorithm.c" "core/mpw-types.c" "core/mpw-util.c" "cli/mpw-tests-util.c" \ cc "${cflags[@]}" "$@" "core/base64.c" "core/aes.c" "core/mpw-algorithm.c" "core/mpw-types.c" "core/mpw-util.c" "cli/mpw-tests-util.c" \
"${ldflags[@]}" "cli/mpw-tests.c" -o "mpw-tests" "${ldflags[@]}" "cli/mpw-tests.c" -o "mpw-tests"
echo "done! You can now use ./$_" echo "done! You can now use ./$_"
} }
@@ -138,7 +138,7 @@ cc() {
if hash llvm-gcc 2>/dev/null; then if hash llvm-gcc 2>/dev/null; then
llvm-gcc "$@" llvm-gcc "$@"
elif hash gcc 2>/dev/null; then elif hash gcc 2>/dev/null; then
gcc -std=gnu99 "$@" gcc -std=c11 "$@"
elif hash clang 2>/dev/null; then elif hash clang 2>/dev/null; then
clang "$@" clang "$@"
else else
@@ -153,19 +153,29 @@ use() {
local option=$1 requisite=$2 lib=$3 local option=$1 requisite=$2 lib=$3
local enabled=${!option} local enabled=${!option}
if (( enabled )) && haslib "$lib"; then if (( enabled )); then
echo >&2 "Enabled $option (lib$lib)." if haslib "$lib"; then
echo >&2 "INFO: Enabled $option (lib$lib)."
ldflags+=( -l"$lib" ) ldflags+=( -l"$lib" )
return 0 return 0
elif [[ $requisite != required ]]; then
echo >&2 "WARNING: $option was enabled but is missing $lib library. Will continue with $option disabled!" elif [[ $requisite == required ]]; then
return 1
elif (( enabled )); then
echo >&2 "ERROR: $option was enabled but is missing $lib library. Please install this library before continuing." echo >&2 "ERROR: $option was enabled but is missing $lib library. Please install this library before continuing."
exit 1 exit 1
else else
echo >&2 "WARNING: $option was enabled but is missing $lib library. Will continue with $option disabled!"
return 1
fi
elif [[ $requisite == required ]]; then
echo >&2 "ERROR: $option was required but is not enabled. Please enable the option or remove this target before continuing." echo >&2 "ERROR: $option was required but is not enabled. Please enable the option or remove this target before continuing."
exit 1 exit 1
else
echo >&2 "INFO: $option is supported but not enabled."
return 1
fi fi
} }
use_mpw_sodium() { use_mpw_sodium() {

View File

@@ -31,17 +31,17 @@
* *
*/ */
#include <sys/types.h>
#include <ctype.h> #include <ctype.h>
#include <errno.h> #include <errno.h>
#include <pwd.h>
#include <stdio.h> #include <stdio.h>
#include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
#include "blf.h" #include "blf.h"
#include "blowfish.h" #include "blowfish.h"
#include "mpw-util.h"
/* This implementation is adaptable to current computing power. /* This implementation is adaptable to current computing power.
* You can have up to 2^31 rounds which should be enough for some * You can have up to 2^31 rounds which should be enough for some
@@ -187,10 +187,10 @@ bcrypt_hashpass(const char *key, const uint8_t *salt, char *encrypted,
snprintf( encrypted, 8, "$2%c$%2.2u$", minor, logr ); snprintf( encrypted, 8, "$2%c$%2.2u$", minor, logr );
encode_base64( encrypted + 7, csalt, BCRYPT_MAXSALT ); encode_base64( encrypted + 7, csalt, BCRYPT_MAXSALT );
encode_base64( encrypted + 7 + 22, ciphertext, 4 * BCRYPT_WORDS - 1 ); encode_base64( encrypted + 7 + 22, ciphertext, 4 * BCRYPT_WORDS - 1 );
bzero( &state, sizeof( state ) ); mpw_zero( &state, sizeof state );
bzero( ciphertext, sizeof( ciphertext ) ); mpw_zero( ciphertext, sizeof ciphertext );
bzero( csalt, sizeof( csalt ) ); mpw_zero( csalt, sizeof csalt );
bzero( cdata, sizeof( cdata ) ); mpw_zero( cdata, sizeof cdata );
return 0; return 0;
inval: inval:
@@ -198,75 +198,6 @@ bcrypt_hashpass(const char *key, const uint8_t *salt, char *encrypted,
return -1; return -1;
} }
/*
* user friendly functions
*/
static int
bcrypt_newhash(const char *pass, int log_rounds, char *hash, size_t hashlen) {
uint8_t salt[BCRYPT_SALTSPACE];
if (bcrypt_initsalt( log_rounds, salt, sizeof( salt ) ) != 0)
return -1;
if (bcrypt_hashpass( pass, salt, hash, hashlen ) != 0)
return -1;
bzero( salt, sizeof( salt ) );
return 0;
}
static int __unused
bcrypt_checkpass(const char *pass, const char *goodhash) {
char hash[BCRYPT_HASHSPACE];
if (bcrypt_hashpass( pass, (const uint8_t *)goodhash, hash, sizeof( hash ) ) != 0)
return -1;
if (strlen( hash ) != strlen( goodhash ) ||
timingsafe_bcmp( hash, goodhash, strlen( goodhash ) ) != 0) {
errno = EACCES;
return -1;
}
bzero( hash, sizeof( hash ) );
return 0;
}
/*
* Measure this system's performance by measuring the time for 8 rounds.
* We are aiming for something that takes around 0.1s, but not too much over.
*/
static int __unused
_bcrypt_autorounds(void) {
struct timespec before, after;
int r = 8;
char buf[_PASSWORD_LEN];
time_t duration;
clock_gettime( CLOCK_THREAD_CPUTIME_ID, &before );
bcrypt_newhash( "testpassword", r, buf, sizeof( buf ) );
clock_gettime( CLOCK_THREAD_CPUTIME_ID, &after );
duration = after.tv_sec - before.tv_sec;
duration *= 1000000;
duration += (after.tv_nsec - before.tv_nsec) / 1000;
/* too quick? slow it down. */
while (r < 16 && duration <= 60000) {
r += 1;
duration *= 2;
}
/* too slow? speed it up. */
while (r > 6 && duration > 120000) {
r -= 1;
duration /= 2;
}
return r;
}
/* /*
* internal utilities * internal utilities
*/ */

View File

@@ -31,6 +31,8 @@
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
#include <stdint.h>
#ifndef _BLF_H_ #ifndef _BLF_H_
#define _BLF_H_ #define _BLF_H_
@@ -47,8 +49,8 @@
/* Blowfish context */ /* Blowfish context */
typedef struct BlowfishContext { typedef struct BlowfishContext {
u_int32_t S[4][256]; /* S-Boxes */ uint32_t S[4][256]; /* S-Boxes */
u_int32_t P[BLF_N + 2]; /* Subkeys */ uint32_t P[BLF_N + 2]; /* Subkeys */
} blf_ctx; } blf_ctx;
/* Raw access to customized Blowfish /* Raw access to customized Blowfish
@@ -57,25 +59,25 @@ typedef struct BlowfishContext {
* Blowfish_expand0state( state, key, keylen ) * Blowfish_expand0state( state, key, keylen )
*/ */
void Blowfish_encipher(blf_ctx *, u_int32_t *, u_int32_t *); void Blowfish_encipher(blf_ctx *, uint32_t *, uint32_t *);
void Blowfish_decipher(blf_ctx *, u_int32_t *, u_int32_t *); void Blowfish_decipher(blf_ctx *, uint32_t *, uint32_t *);
void Blowfish_initstate(blf_ctx *); void Blowfish_initstate(blf_ctx *);
void Blowfish_expand0state(blf_ctx *, const u_int8_t *, u_int16_t); void Blowfish_expand0state(blf_ctx *, const uint8_t *, u_int16_t);
void Blowfish_expandstate(blf_ctx *, const u_int8_t *, u_int16_t, const u_int8_t *, u_int16_t); void Blowfish_expandstate(blf_ctx *, const uint8_t *, u_int16_t, const uint8_t *, u_int16_t);
/* Standard Blowfish */ /* Standard Blowfish */
void blf_key(blf_ctx *, const u_int8_t *, u_int16_t); void blf_key(blf_ctx *, const uint8_t *, u_int16_t);
void blf_enc(blf_ctx *, u_int32_t *, u_int16_t); void blf_enc(blf_ctx *, uint32_t *, u_int16_t);
void blf_dec(blf_ctx *, u_int32_t *, u_int16_t); void blf_dec(blf_ctx *, uint32_t *, u_int16_t);
void blf_ecb_encrypt(blf_ctx *, u_int8_t *, u_int32_t); void blf_ecb_encrypt(blf_ctx *, uint8_t *, uint32_t);
void blf_ecb_decrypt(blf_ctx *, u_int8_t *, u_int32_t); void blf_ecb_decrypt(blf_ctx *, uint8_t *, uint32_t);
void blf_cbc_encrypt(blf_ctx *, u_int8_t *, u_int8_t *, u_int32_t); void blf_cbc_encrypt(blf_ctx *, uint8_t *, uint8_t *, uint32_t);
void blf_cbc_decrypt(blf_ctx *, u_int8_t *, u_int8_t *, u_int32_t); void blf_cbc_decrypt(blf_ctx *, uint8_t *, uint8_t *, uint32_t);
/* Converts u_int8_t to u_int32_t */ /* Converts uint8_t to uint32_t */
u_int32_t Blowfish_stream2word(const u_int8_t *, u_int16_t, u_int16_t *); uint32_t Blowfish_stream2word(const uint8_t *, u_int16_t, u_int16_t *);
#endif #endif

View File

@@ -63,12 +63,12 @@
#define BLFRND(s,p,i,j,n) (i ^= F(s,j) ^ (p)[n]) #define BLFRND(s,p,i,j,n) (i ^= F(s,j) ^ (p)[n])
void void
Blowfish_encipher(blf_ctx *c, u_int32_t *xl, u_int32_t *xr) Blowfish_encipher(blf_ctx *c, uint32_t *xl, uint32_t *xr)
{ {
u_int32_t Xl; uint32_t Xl;
u_int32_t Xr; uint32_t Xr;
u_int32_t *s = c->S[0]; uint32_t *s = c->S[0];
u_int32_t *p = c->P; uint32_t *p = c->P;
Xl = *xl; Xl = *xl;
Xr = *xr; Xr = *xr;
@@ -88,12 +88,12 @@ Blowfish_encipher(blf_ctx *c, u_int32_t *xl, u_int32_t *xr)
} }
void void
Blowfish_decipher(blf_ctx *c, u_int32_t *xl, u_int32_t *xr) Blowfish_decipher(blf_ctx *c, uint32_t *xl, uint32_t *xr)
{ {
u_int32_t Xl; uint32_t Xl;
u_int32_t Xr; uint32_t Xr;
u_int32_t *s = c->S[0]; uint32_t *s = c->S[0];
u_int32_t *p = c->P; uint32_t *p = c->P;
Xl = *xl; Xl = *xl;
Xr = *xr; Xr = *xr;
@@ -391,13 +391,13 @@ Blowfish_initstate(blf_ctx *c)
*c = initstate; *c = initstate;
} }
u_int32_t uint32_t
Blowfish_stream2word(const u_int8_t *data, u_int16_t databytes, Blowfish_stream2word(const uint8_t *data, uint16_t databytes,
u_int16_t *current) uint16_t *current)
{ {
u_int8_t i; uint8_t i;
u_int16_t j; uint16_t j;
u_int32_t temp; uint32_t temp;
temp = 0x00000000; temp = 0x00000000;
j = *current; j = *current;
@@ -413,14 +413,14 @@ Blowfish_stream2word(const u_int8_t *data, u_int16_t databytes,
} }
void void
Blowfish_expand0state(blf_ctx *c, const u_int8_t *key, u_int16_t keybytes) Blowfish_expand0state(blf_ctx *c, const uint8_t *key, uint16_t keybytes)
{ {
u_int16_t i; uint16_t i;
u_int16_t j; uint16_t j;
u_int16_t k; uint16_t k;
u_int32_t temp; uint32_t temp;
u_int32_t datal; uint32_t datal;
u_int32_t datar; uint32_t datar;
j = 0; j = 0;
for (i = 0; i < BLF_N + 2; i++) { for (i = 0; i < BLF_N + 2; i++) {
@@ -451,15 +451,15 @@ Blowfish_expand0state(blf_ctx *c, const u_int8_t *key, u_int16_t keybytes)
void void
Blowfish_expandstate(blf_ctx *c, const u_int8_t *data, u_int16_t databytes, Blowfish_expandstate(blf_ctx *c, const uint8_t *data, uint16_t databytes,
const u_int8_t *key, u_int16_t keybytes) const uint8_t *key, uint16_t keybytes)
{ {
u_int16_t i; uint16_t i;
u_int16_t j; uint16_t j;
u_int16_t k; uint16_t k;
u_int32_t temp; uint32_t temp;
u_int32_t datal; uint32_t datal;
u_int32_t datar; uint32_t datar;
j = 0; j = 0;
for (i = 0; i < BLF_N + 2; i++) { for (i = 0; i < BLF_N + 2; i++) {
@@ -494,7 +494,7 @@ Blowfish_expandstate(blf_ctx *c, const u_int8_t *data, u_int16_t databytes,
} }
void void
__unused blf_key(blf_ctx *c, const u_int8_t *k, u_int16_t len) __unused blf_key(blf_ctx *c, const uint8_t *k, uint16_t len)
{ {
/* Initialize S-boxes and subkeys with Pi */ /* Initialize S-boxes and subkeys with Pi */
Blowfish_initstate(c); Blowfish_initstate(c);
@@ -504,10 +504,10 @@ __unused blf_key(blf_ctx *c, const u_int8_t *k, u_int16_t len)
} }
void void
blf_enc(blf_ctx *c, u_int32_t *data, u_int16_t blocks) blf_enc(blf_ctx *c, uint32_t *data, uint16_t blocks)
{ {
u_int32_t *d; uint32_t *d;
u_int16_t i; uint16_t i;
d = data; d = data;
for (i = 0; i < blocks; i++) { for (i = 0; i < blocks; i++) {
@@ -517,10 +517,10 @@ blf_enc(blf_ctx *c, u_int32_t *data, u_int16_t blocks)
} }
void void
__unused blf_dec(blf_ctx *c, u_int32_t *data, u_int16_t blocks) __unused blf_dec(blf_ctx *c, uint32_t *data, uint16_t blocks)
{ {
u_int32_t *d; uint32_t *d;
u_int16_t i; uint16_t i;
d = data; d = data;
for (i = 0; i < blocks; i++) { for (i = 0; i < blocks; i++) {
@@ -530,111 +530,111 @@ __unused blf_dec(blf_ctx *c, u_int32_t *data, u_int16_t blocks)
} }
void void
__unused blf_ecb_encrypt(blf_ctx *c, u_int8_t *data, u_int32_t len) __unused blf_ecb_encrypt(blf_ctx *c, uint8_t *data, uint32_t len)
{ {
u_int32_t l, r; uint32_t l, r;
u_int32_t i; uint32_t i;
for (i = 0; i < len; i += 8) { for (i = 0; i < len; i += 8) {
l = (u_int32_t)data[0] << 24 | (u_int32_t)data[1] << 16 | (u_int32_t)data[2] << 8 | (u_int32_t)data[3]; l = (uint32_t)data[0] << 24 | (uint32_t)data[1] << 16 | (uint32_t)data[2] << 8 | (uint32_t)data[3];
r = (u_int32_t)data[4] << 24 | (u_int32_t)data[5] << 16 | (u_int32_t)data[6] << 8 | (u_int32_t)data[7]; r = (uint32_t)data[4] << 24 | (uint32_t)data[5] << 16 | (uint32_t)data[6] << 8 | (uint32_t)data[7];
Blowfish_encipher(c, &l, &r); Blowfish_encipher(c, &l, &r);
data[0] = (u_int8_t)(l >> 24 & 0xff); data[0] = (uint8_t)(l >> 24 & 0xff);
data[1] = (u_int8_t)(l >> 16 & 0xff); data[1] = (uint8_t)(l >> 16 & 0xff);
data[2] = (u_int8_t)(l >> 8 & 0xff); data[2] = (uint8_t)(l >> 8 & 0xff);
data[3] = (u_int8_t)(l & 0xff); data[3] = (uint8_t)(l & 0xff);
data[4] = (u_int8_t)(r >> 24 & 0xff); data[4] = (uint8_t)(r >> 24 & 0xff);
data[5] = (u_int8_t)(r >> 16 & 0xff); data[5] = (uint8_t)(r >> 16 & 0xff);
data[6] = (u_int8_t)(r >> 8 & 0xff); data[6] = (uint8_t)(r >> 8 & 0xff);
data[7] = (u_int8_t)(r & 0xff); data[7] = (uint8_t)(r & 0xff);
data += 8; data += 8;
} }
} }
void void
__unused blf_ecb_decrypt(blf_ctx *c, u_int8_t *data, u_int32_t len) __unused blf_ecb_decrypt(blf_ctx *c, uint8_t *data, uint32_t len)
{ {
u_int32_t l, r; uint32_t l, r;
u_int32_t i; uint32_t i;
for (i = 0; i < len; i += 8) { for (i = 0; i < len; i += 8) {
l = (u_int32_t)data[0] << 24 | (u_int32_t)data[1] << 16 | (u_int32_t)data[2] << 8 | (u_int32_t)data[3]; l = (uint32_t)data[0] << 24 | (uint32_t)data[1] << 16 | (uint32_t)data[2] << 8 | (uint32_t)data[3];
r = (u_int32_t)data[4] << 24 | (u_int32_t)data[5] << 16 | (u_int32_t)data[6] << 8 | (u_int32_t)data[7]; r = (uint32_t)data[4] << 24 | (uint32_t)data[5] << 16 | (uint32_t)data[6] << 8 | (uint32_t)data[7];
Blowfish_decipher(c, &l, &r); Blowfish_decipher(c, &l, &r);
data[0] = (u_int8_t)(l >> 24 & 0xff); data[0] = (uint8_t)(l >> 24 & 0xff);
data[1] = (u_int8_t)(l >> 16 & 0xff); data[1] = (uint8_t)(l >> 16 & 0xff);
data[2] = (u_int8_t)(l >> 8 & 0xff); data[2] = (uint8_t)(l >> 8 & 0xff);
data[3] = (u_int8_t)(l & 0xff); data[3] = (uint8_t)(l & 0xff);
data[4] = (u_int8_t)(r >> 24 & 0xff); data[4] = (uint8_t)(r >> 24 & 0xff);
data[5] = (u_int8_t)(r >> 16 & 0xff); data[5] = (uint8_t)(r >> 16 & 0xff);
data[6] = (u_int8_t)(r >> 8 & 0xff); data[6] = (uint8_t)(r >> 8 & 0xff);
data[7] = (u_int8_t)(r & 0xff); data[7] = (uint8_t)(r & 0xff);
data += 8; data += 8;
} }
} }
void void
__unused blf_cbc_encrypt(blf_ctx *c, u_int8_t *iv, u_int8_t *data, u_int32_t len) __unused blf_cbc_encrypt(blf_ctx *c, uint8_t *iv, uint8_t *data, uint32_t len)
{ {
u_int32_t l, r; uint32_t l, r;
u_int32_t i, j; uint32_t i, j;
for (i = 0; i < len; i += 8) { for (i = 0; i < len; i += 8) {
for (j = 0; j < 8; j++) for (j = 0; j < 8; j++)
data[j] ^= iv[j]; data[j] ^= iv[j];
l = (u_int32_t)data[0] << 24 | (u_int32_t)data[1] << 16 | (u_int32_t)data[2] << 8 | (u_int32_t)data[3]; l = (uint32_t)data[0] << 24 | (uint32_t)data[1] << 16 | (uint32_t)data[2] << 8 | (uint32_t)data[3];
r = (u_int32_t)data[4] << 24 | (u_int32_t)data[5] << 16 | (u_int32_t)data[6] << 8 | (u_int32_t)data[7]; r = (uint32_t)data[4] << 24 | (uint32_t)data[5] << 16 | (uint32_t)data[6] << 8 | (uint32_t)data[7];
Blowfish_encipher(c, &l, &r); Blowfish_encipher(c, &l, &r);
data[0] = (u_int8_t)(l >> 24 & 0xff); data[0] = (uint8_t)(l >> 24 & 0xff);
data[1] = (u_int8_t)(l >> 16 & 0xff); data[1] = (uint8_t)(l >> 16 & 0xff);
data[2] = (u_int8_t)(l >> 8 & 0xff); data[2] = (uint8_t)(l >> 8 & 0xff);
data[3] = (u_int8_t)(l & 0xff); data[3] = (uint8_t)(l & 0xff);
data[4] = (u_int8_t)(r >> 24 & 0xff); data[4] = (uint8_t)(r >> 24 & 0xff);
data[5] = (u_int8_t)(r >> 16 & 0xff); data[5] = (uint8_t)(r >> 16 & 0xff);
data[6] = (u_int8_t)(r >> 8 & 0xff); data[6] = (uint8_t)(r >> 8 & 0xff);
data[7] = (u_int8_t)(r & 0xff); data[7] = (uint8_t)(r & 0xff);
iv = data; iv = data;
data += 8; data += 8;
} }
} }
void void
__unused blf_cbc_decrypt(blf_ctx *c, u_int8_t *iva, u_int8_t *data, u_int32_t len) __unused blf_cbc_decrypt(blf_ctx *c, uint8_t *iva, uint8_t *data, uint32_t len)
{ {
u_int32_t l, r; uint32_t l, r;
u_int8_t *iv; uint8_t *iv;
u_int32_t i, j; uint32_t i, j;
iv = data + len - 16; iv = data + len - 16;
data = data + len - 8; data = data + len - 8;
for (i = len - 8; i >= 8; i -= 8) { for (i = len - 8; i >= 8; i -= 8) {
l = (u_int32_t)data[0] << 24 | (u_int32_t)data[1] << 16 | (u_int32_t)data[2] << 8 | (u_int32_t)data[3]; l = (uint32_t)data[0] << 24 | (uint32_t)data[1] << 16 | (uint32_t)data[2] << 8 | (uint32_t)data[3];
r = (u_int32_t)data[4] << 24 | (u_int32_t)data[5] << 16 | (u_int32_t)data[6] << 8 | (u_int32_t)data[7]; r = (uint32_t)data[4] << 24 | (uint32_t)data[5] << 16 | (uint32_t)data[6] << 8 | (uint32_t)data[7];
Blowfish_decipher(c, &l, &r); Blowfish_decipher(c, &l, &r);
data[0] = (u_int8_t)(l >> 24 & 0xff); data[0] = (uint8_t)(l >> 24 & 0xff);
data[1] = (u_int8_t)(l >> 16 & 0xff); data[1] = (uint8_t)(l >> 16 & 0xff);
data[2] = (u_int8_t)(l >> 8 & 0xff); data[2] = (uint8_t)(l >> 8 & 0xff);
data[3] = (u_int8_t)(l & 0xff); data[3] = (uint8_t)(l & 0xff);
data[4] = (u_int8_t)(r >> 24 & 0xff); data[4] = (uint8_t)(r >> 24 & 0xff);
data[5] = (u_int8_t)(r >> 16 & 0xff); data[5] = (uint8_t)(r >> 16 & 0xff);
data[6] = (u_int8_t)(r >> 8 & 0xff); data[6] = (uint8_t)(r >> 8 & 0xff);
data[7] = (u_int8_t)(r & 0xff); data[7] = (uint8_t)(r & 0xff);
for (j = 0; j < 8; j++) for (j = 0; j < 8; j++)
data[j] ^= iv[j]; data[j] ^= iv[j];
iv -= 8; iv -= 8;
data -= 8; data -= 8;
} }
l = (u_int32_t)data[0] << 24 | (u_int32_t)data[1] << 16 | (u_int32_t)data[2] << 8 | (u_int32_t)data[3]; l = (uint32_t)data[0] << 24 | (uint32_t)data[1] << 16 | (uint32_t)data[2] << 8 | (uint32_t)data[3];
r = (u_int32_t)data[4] << 24 | (u_int32_t)data[5] << 16 | (u_int32_t)data[6] << 8 | (u_int32_t)data[7]; r = (uint32_t)data[4] << 24 | (uint32_t)data[5] << 16 | (uint32_t)data[6] << 8 | (uint32_t)data[7];
Blowfish_decipher(c, &l, &r); Blowfish_decipher(c, &l, &r);
data[0] = (u_int8_t)(l >> 24 & 0xff); data[0] = (uint8_t)(l >> 24 & 0xff);
data[1] = (u_int8_t)(l >> 16 & 0xff); data[1] = (uint8_t)(l >> 16 & 0xff);
data[2] = (u_int8_t)(l >> 8 & 0xff); data[2] = (uint8_t)(l >> 8 & 0xff);
data[3] = (u_int8_t)(l & 0xff); data[3] = (uint8_t)(l & 0xff);
data[4] = (u_int8_t)(r >> 24 & 0xff); data[4] = (uint8_t)(r >> 24 & 0xff);
data[5] = (u_int8_t)(r >> 16 & 0xff); data[5] = (uint8_t)(r >> 16 & 0xff);
data[6] = (u_int8_t)(r >> 8 & 0xff); data[6] = (uint8_t)(r >> 8 & 0xff);
data[7] = (u_int8_t)(r & 0xff); data[7] = (uint8_t)(r & 0xff);
for (j = 0; j < 8; j++) for (j = 0; j < 8; j++)
data[j] ^= iva[j]; data[j] ^= iva[j];
} }

View File

@@ -16,27 +16,33 @@
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>. // LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//============================================================================== //==============================================================================
#define _POSIX_C_SOURCE 200809L
#include "mpw-cli-util.h" #include "mpw-cli-util.h"
#include <unistd.h> #include <unistd.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/wait.h>
#include <pwd.h> #include <pwd.h>
#include <string.h> #include <string.h>
#include <errno.h> #include <errno.h>
#include <sysexits.h> #include <sysexits.h>
#define MPW_MAX_INPUT 60
#if MPW_COLOR
#include <curses.h>
#include <term.h>
#endif
#include "mpw-util.h" #include "mpw-util.h"
/** Read the value of an environment variable.
* @return A newly allocated string or NULL if the variable doesn't exist. */
const char *mpw_getenv(const char *variableName) { const char *mpw_getenv(const char *variableName) {
char *envBuf = getenv( variableName ); char *envBuf = getenv( variableName );
return envBuf? strdup( envBuf ): NULL; return envBuf? mpw_strdup( envBuf ): NULL;
} }
/** Use the askpass program to prompt the user.
* @return A newly allocated string or NULL if askpass is not supported or an error occurred. */
char *mpw_askpass(const char *prompt) { char *mpw_askpass(const char *prompt) {
const char *askpass = mpw_getenv( MP_ENV_askpass ); const char *askpass = mpw_getenv( MP_ENV_askpass );
@@ -88,15 +94,60 @@ char *mpw_askpass(const char *prompt) {
return NULL; return NULL;
} }
/** Ask the user a question. static const char *_mpw_getline(const char *prompt, bool silent) {
* @return A newly allocated string or NULL if an error occurred trying to read from the user. */
const char *mpw_getline(const char *prompt) {
// Get answer from askpass. // Get answer from askpass.
char *answer = mpw_askpass( prompt ); char *answer = mpw_askpass( prompt );
if (answer) if (answer)
return answer; return answer;
#if MPW_COLOR
// Initialize a curses screen.
SCREEN *screen = newterm( NULL, stderr, stdin );
start_color();
init_pair( 1, COLOR_WHITE, COLOR_BLUE );
init_pair( 2, COLOR_BLACK, COLOR_WHITE );
int rows, cols;
getmaxyx( stdscr, rows, cols );
// Display a dialog box.
int width = max( prompt? (int)strlen( prompt ): 0, MPW_MAX_INPUT ) + 6;
char *version = "mpw v" stringify_def( MP_VERSION );
mvprintw( rows - 1, (cols - (int)strlen( version )) / 2, "%s", version );
attron( A_BOLD );
color_set( 2, NULL );
mvprintw( rows / 2 - 1, (cols - width) / 2, "%s%*s%s", "*", width - 2, "", "*" );
mvprintw( rows / 2 - 1, (cols - (int)strlen( prompt )) / 2, "%s", prompt );
color_set( 1, NULL );
mvprintw( rows / 2 + 0, (cols - width) / 2, "%s%*s%s", "|", width - 2, "", "|" );
mvprintw( rows / 2 + 1, (cols - width) / 2, "%s%*s%s", "|", width - 2, "", "|" );
mvprintw( rows / 2 + 2, (cols - width) / 2, "%s%*s%s", "|", width - 2, "", "|" );
// Read response.
color_set( 2, NULL );
attron( A_STANDOUT );
int result = ERR;
char str[MPW_MAX_INPUT + 1];
if (silent) {
mvprintw( rows / 2 + 1, (cols - 5) / 2, "[ * ]" );
refresh();
noecho();
result = mvgetnstr( rows / 2 + 1, (cols - 1) / 2, str, MPW_MAX_INPUT );
echo();
} else {
mvprintw( rows / 2 + 1, (cols - (MPW_MAX_INPUT + 2)) / 2, "%*s", MPW_MAX_INPUT + 2, "" );
refresh();
echo();
result = mvgetnstr( rows / 2 + 1, (cols - MPW_MAX_INPUT) / 2, str, MPW_MAX_INPUT );
}
attrset( 0 );
endwin();
delscreen( screen );
return result == ERR? NULL: mpw_strndup( str, MPW_MAX_INPUT );
#else
// Get password from terminal. // Get password from terminal.
fprintf( stderr, "%s ", prompt ); fprintf( stderr, "%s ", prompt );
@@ -110,56 +161,44 @@ const char *mpw_getline(const char *prompt) {
// Remove trailing newline. // Remove trailing newline.
answer[lineSize - 1] = '\0'; answer[lineSize - 1] = '\0';
return answer; return answer;
#endif
}
const char *mpw_getline(const char *prompt) {
return _mpw_getline( prompt, false );
} }
/** Ask the user for a password.
* @return A newly allocated string or NULL if an error occurred trying to read from the user. */
const char *mpw_getpass(const char *prompt) { const char *mpw_getpass(const char *prompt) {
// Get password from askpass. return _mpw_getline( prompt, true );
const char *password = mpw_askpass( prompt );
if (password)
return password;
// Get password from terminal.
char *answer = getpass( prompt );
if (!answer)
return NULL;
password = strdup( answer );
bzero( answer, strlen( answer ) );
return password;
} }
/** Get the absolute path to the mpw configuration file with the given prefix name and file extension.
* Resolves the file <prefix.extension> as located in the <.mpw.d> directory inside the user's home directory
* or current directory if it couldn't be resolved.
* @return A newly allocated string. */
const char *mpw_path(const char *prefix, const char *extension) { const char *mpw_path(const char *prefix, const char *extension) {
// Resolve user's home directory. // Resolve user's home directory.
char *homeDir = NULL; char *homeDir = NULL;
if (!homeDir) if (!homeDir)
if ((homeDir = getenv( "HOME" ))) if ((homeDir = getenv( "HOME" )))
homeDir = strdup( homeDir ); homeDir = mpw_strdup( homeDir );
if (!homeDir) if (!homeDir)
if ((homeDir = getenv( "USERPROFILE" ))) if ((homeDir = getenv( "USERPROFILE" )))
homeDir = strdup( homeDir ); homeDir = mpw_strdup( homeDir );
if (!homeDir) { if (!homeDir) {
const char *homeDrive = getenv( "HOMEDRIVE" ), *homePath = getenv( "HOMEPATH" ); const char *homeDrive = getenv( "HOMEDRIVE" ), *homePath = getenv( "HOMEPATH" );
if (homeDrive && homePath) if (homeDrive && homePath)
homeDir = strdup( mpw_str( "%s%s", homeDrive, homePath ) ); homeDir = mpw_strdup( mpw_str( "%s%s", homeDrive, homePath ) );
} }
if (!homeDir) { if (!homeDir) {
struct passwd *passwd = getpwuid( getuid() ); struct passwd *passwd = getpwuid( getuid() );
if (passwd) if (passwd)
homeDir = strdup( passwd->pw_dir ); homeDir = mpw_strdup( passwd->pw_dir );
} }
if (!homeDir) if (!homeDir)
homeDir = getcwd( NULL, 0 ); homeDir = getcwd( NULL, 0 );
// Compose filename. // Compose filename.
char *path = strdup( mpw_str( "%s.%s", prefix, extension ) ); char *path = mpw_strdup( mpw_str( "%s.%s", prefix, extension ) );
// This is a filename, remove all potential directory separators. // This is a filename, remove all potential directory separators.
for (char *slash; (slash = strstr( path, "/" )); *slash = '_'); for (char *slash; (slash = strstr( path, "/" )); *slash = '_');
@@ -171,32 +210,31 @@ const char *mpw_path(const char *prefix, const char *extension) {
free( path ); free( path );
if (homePath) if (homePath)
path = strdup( homePath ); path = mpw_strdup( homePath );
} }
return path; return path;
} }
/** mkdir all the directories up to the directory of the given file path.
* @return true if the file's path exists. */
bool mpw_mkdirs(const char *filePath) { bool mpw_mkdirs(const char *filePath) {
if (!filePath) if (!filePath)
return false; return false;
// The path to mkdir is the filePath without the last path component.
char *pathEnd = strrchr( filePath, '/' );
char *path = pathEnd? strndup( filePath, (size_t)(pathEnd - filePath) ): NULL;
if (!path)
return false;
// Save the cwd and for absolute paths, start at the root. // Save the cwd and for absolute paths, start at the root.
char *cwd = getcwd( NULL, 0 ); char *cwd = getcwd( NULL, 0 );
if (*filePath == '/') if (*filePath == '/')
chdir( "/" ); if (chdir( "/" ) == ERR)
return false;
// The path to mkdir is the filePath without the last path component.
char *pathEnd = strrchr( filePath, '/' );
if (pathEnd)
return true;
// Walk the path. // Walk the path.
bool success = true; bool success = true;
char *path = mpw_strndup( filePath, (size_t)(pathEnd - filePath) );
for (char *dirName = strtok( path, "/" ); success && dirName; dirName = strtok( NULL, "/" )) { for (char *dirName = strtok( path, "/" ); success && dirName; dirName = strtok( NULL, "/" )) {
if (!strlen( dirName )) if (!strlen( dirName ))
continue; continue;
@@ -212,8 +250,6 @@ bool mpw_mkdirs(const char *filePath) {
return success; return success;
} }
/** Read until EOF from the given file descriptor.
* @return A newly allocated string or NULL if the read buffer couldn't be allocated or an error occurred. */
char *mpw_read_fd(int fd) { char *mpw_read_fd(int fd) {
char *buf = NULL; char *buf = NULL;
@@ -227,8 +263,6 @@ char *mpw_read_fd(int fd) {
return buf; return buf;
} }
/** Read the file contents of a given file.
* @return A newly allocated string or NULL if the read buffer couldn't be allocated. */
char *mpw_read_file(FILE *file) { char *mpw_read_file(FILE *file) {
if (!file) if (!file)
@@ -242,3 +276,73 @@ char *mpw_read_file(FILE *file) {
return buf; return buf;
} }
#if MPW_COLOR
static char *str_tputs;
static int str_tputs_cursor;
static const int str_tputs_max = 256;
static bool mpw_setupterm() {
if (!isatty( STDERR_FILENO ))
return false;
static bool termsetup;
if (!termsetup) {
int errret;
if (!(termsetup = (setupterm( NULL, STDERR_FILENO, &errret ) == OK))) {
wrn( "Terminal doesn't support color (setupterm errret %d).\n", errret );
return false;
}
}
return true;
}
static int mpw_tputc(int c) {
if (++str_tputs_cursor < str_tputs_max) {
str_tputs[str_tputs_cursor] = (char)c;
return OK;
}
return ERR;
}
static char *mpw_tputs(const char *str, int affcnt) {
if (str_tputs)
mpw_free( &str_tputs, str_tputs_max );
str_tputs = calloc( str_tputs_max, sizeof( char ) );
str_tputs_cursor = -1;
char *result = tputs( str, affcnt, mpw_tputc ) == ERR? NULL: mpw_strndup( str_tputs, str_tputs_max );
if (str_tputs)
mpw_free( &str_tputs, str_tputs_max );
return result;
}
#endif
const char *mpw_identicon_str(MPIdenticon identicon) {
char *colorString, *resetString;
#ifdef MPW_COLOR
if (mpw_setupterm()) {
colorString = mpw_tputs( tparm( tgetstr( "AF", NULL ), identicon.color ), 1 );
resetString = mpw_tputs( tgetstr( "me", NULL ), 1 );
}
else
#endif
{
colorString = calloc( 1, sizeof( char ) );
resetString = calloc( 1, sizeof( char ) );
}
const char *str = mpw_str( "%s%s%s%s%s%s",
colorString, identicon.leftArm, identicon.body, identicon.rightArm, identicon.accessory, resetString );
mpw_free_strings( &colorString, &resetString, NULL );
return mpw_strdup( str );
}

View File

@@ -19,6 +19,7 @@
#include <stdio.h> #include <stdio.h>
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include "mpw-types.h"
#ifndef MP_VERSION #ifndef MP_VERSION
#define MP_VERSION ? #define MP_VERSION ?
@@ -62,3 +63,7 @@ char *mpw_read_fd(int fd);
/** Read the file contents of a given file. /** Read the file contents of a given file.
* @return A newly allocated string or NULL the read buffer couldn't be allocated. */ * @return A newly allocated string or NULL the read buffer couldn't be allocated. */
char *mpw_read_file(FILE *file); char *mpw_read_file(FILE *file);
/** Encode a visual fingerprint for a user.
* @return A newly allocated string. */
const char *mpw_identicon_str(MPIdenticon identicon);

View File

@@ -16,6 +16,8 @@
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>. // LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//============================================================================== //==============================================================================
#define _POSIX_C_SOURCE 200809L
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
#include <stdlib.h> #include <stdlib.h>
@@ -26,7 +28,7 @@
#include "mpw-cli-util.h" #include "mpw-cli-util.h"
#include "mpw-algorithm.h" #include "mpw-algorithm.h"
#include "mpw-util.h" #include "mpw-util.h"
#include "mpw-marshall.h" #include "mpw-marshal.h"
/** Output the program's usage documentation. */ /** Output the program's usage documentation. */
static void usage() { static void usage() {
@@ -63,8 +65,8 @@ static void usage() {
" i, pin | 4 numbers.\n" " i, pin | 4 numbers.\n"
" n, name | 9 letter name.\n" " n, name | 9 letter name.\n"
" p, phrase | 20 character sentence.\n" " p, phrase | 20 character sentence.\n"
" K, key | encryption key (set key size -s bits).\n" " K, key | encryption key (512 bit or -P bits).\n"
" P, personal | saved personal password (save with -s pw).\n\n" ); " P, personal | saved personal password (save with -P pw).\n\n" );
inf( "" inf( ""
" -P value The parameter value.\n" " -P value The parameter value.\n"
" -p i | The login name for the site.\n" " -p i | The login name for the site.\n"
@@ -97,7 +99,7 @@ static void usage() {
" n, none | No file\n" " n, none | No file\n"
" f, flat | ~/.mpw.d/Full Name.%s\n" " f, flat | ~/.mpw.d/Full Name.%s\n"
" j, json | ~/.mpw.d/Full Name.%s\n\n", " j, json | ~/.mpw.d/Full Name.%s\n\n",
MP_ENV_format, mpw_marshall_format_extension( MPMarshallFormatFlat ), mpw_marshall_format_extension( MPMarshallFormatJSON ) ); MP_ENV_format, mpw_marshal_format_extension( MPMarshalFormatFlat ), mpw_marshal_format_extension( MPMarshalFormatJSON ) );
inf( "" inf( ""
" -R redacted Whether to save the mpsites in redacted format or not.\n" " -R redacted Whether to save the mpsites in redacted format or not.\n"
" Redaction omits or encrypts any secrets, making the file safe\n" " Redaction omits or encrypts any secrets, making the file safe\n"
@@ -142,7 +144,7 @@ typedef struct {
const char *masterPassword; const char *masterPassword;
const char *identicon; const char *identicon;
const char *siteName; const char *siteName;
MPMarshallFormat sitesFormat; MPMarshalFormat sitesFormat;
MPKeyPurpose keyPurpose; MPKeyPurpose keyPurpose;
const char *keyContext; const char *keyContext;
const char *sitesPath; const char *sitesPath;
@@ -191,7 +193,7 @@ int main(const int argc, char *const argv[]) {
Operation operation = { Operation operation = {
.allowPasswordUpdate = false, .allowPasswordUpdate = false,
.sitesFormatFixed = false, .sitesFormatFixed = false,
.sitesFormat = MPMarshallFormatDefault, .sitesFormat = MPMarshalFormatDefault,
.keyPurpose = MPKeyPurposeAuthentication, .keyPurpose = MPKeyPurposeAuthentication,
.resultType = MPResultTypeDefault, .resultType = MPResultTypeDefault,
.siteCounter = MPCounterValueDefault, .siteCounter = MPCounterValueDefault,
@@ -272,51 +274,52 @@ void cli_free(Arguments *args, Operation *operation) {
void cli_args(Arguments *args, Operation *operation, const int argc, char *const argv[]) { void cli_args(Arguments *args, Operation *operation, const int argc, char *const argv[]) {
for (int opt; (opt = getopt( argc, argv, "u:U:m:M:t:P:c:a:p:C:f:F:R:vqh" )) != EOF; optarg? bzero( optarg, strlen( optarg ) ): NULL) for (int opt; (opt = getopt( argc, argv, "u:U:m:M:t:P:c:a:p:C:f:F:R:vqh" )) != EOF;
optarg? mpw_zero( optarg, strlen( optarg ) ): (void)0)
switch (opt) { switch (opt) {
case 'u': case 'u':
args->fullName = optarg && strlen( optarg )? strdup( optarg ): NULL; args->fullName = optarg && strlen( optarg )? mpw_strdup( optarg ): NULL;
operation->allowPasswordUpdate = false; operation->allowPasswordUpdate = false;
break; break;
case 'U': case 'U':
args->fullName = optarg && strlen( optarg )? strdup( optarg ): NULL; args->fullName = optarg && strlen( optarg )? mpw_strdup( optarg ): NULL;
operation->allowPasswordUpdate = true; operation->allowPasswordUpdate = true;
break; break;
case 'm': case 'm':
args->masterPasswordFD = optarg && strlen( optarg )? strdup( optarg ): NULL; args->masterPasswordFD = optarg && strlen( optarg )? mpw_strdup( optarg ): NULL;
break; break;
case 'M': case 'M':
// Passing your master password via the command-line is insecure. Testing purposes only. // Passing your master password via the command-line is insecure. Testing purposes only.
args->masterPassword = optarg && strlen( optarg )? strdup( optarg ): NULL; args->masterPassword = optarg && strlen( optarg )? mpw_strdup( optarg ): NULL;
break; break;
case 't': case 't':
args->resultType = optarg && strlen( optarg )? strdup( optarg ): NULL; args->resultType = optarg && strlen( optarg )? mpw_strdup( optarg ): NULL;
break; break;
case 'P': case 'P':
args->resultParam = optarg && strlen( optarg )? strdup( optarg ): NULL; args->resultParam = optarg && strlen( optarg )? mpw_strdup( optarg ): NULL;
break; break;
case 'c': case 'c':
args->siteCounter = optarg && strlen( optarg )? strdup( optarg ): NULL; args->siteCounter = optarg && strlen( optarg )? mpw_strdup( optarg ): NULL;
break; break;
case 'a': case 'a':
args->algorithmVersion = optarg && strlen( optarg )? strdup( optarg ): NULL; args->algorithmVersion = optarg && strlen( optarg )? mpw_strdup( optarg ): NULL;
break; break;
case 'p': case 'p':
args->keyPurpose = optarg && strlen( optarg )? strdup( optarg ): NULL; args->keyPurpose = optarg && strlen( optarg )? mpw_strdup( optarg ): NULL;
break; break;
case 'C': case 'C':
args->keyContext = optarg && strlen( optarg )? strdup( optarg ): NULL; args->keyContext = optarg && strlen( optarg )? mpw_strdup( optarg ): NULL;
break; break;
case 'f': case 'f':
args->sitesFormat = optarg && strlen( optarg )? strdup( optarg ): NULL; args->sitesFormat = optarg && strlen( optarg )? mpw_strdup( optarg ): NULL;
operation->sitesFormatFixed = false; operation->sitesFormatFixed = false;
break; break;
case 'F': case 'F':
args->sitesFormat = optarg && strlen( optarg )? strdup( optarg ): NULL; args->sitesFormat = optarg && strlen( optarg )? mpw_strdup( optarg ): NULL;
operation->sitesFormatFixed = true; operation->sitesFormatFixed = true;
break; break;
case 'R': case 'R':
args->sitesRedacted = optarg && strlen( optarg )? strdup( optarg ): NULL; args->sitesRedacted = optarg && strlen( optarg )? mpw_strdup( optarg ): NULL;
break; break;
case 'v': case 'v':
++mpw_verbosity; ++mpw_verbosity;
@@ -348,13 +351,13 @@ void cli_args(Arguments *args, Operation *operation, const int argc, char *const
} }
if (optind < argc && argv[optind]) if (optind < argc && argv[optind])
args->siteName = strdup( argv[optind] ); args->siteName = mpw_strdup( argv[optind] );
} }
void cli_fullName(Arguments *args, Operation *operation) { void cli_fullName(Arguments *args, Operation *operation) {
if ((!operation->fullName || !strlen( operation->fullName )) && args->fullName) if ((!operation->fullName || !strlen( operation->fullName )) && args->fullName)
operation->fullName = strdup( args->fullName ); operation->fullName = mpw_strdup( args->fullName );
if (!operation->fullName || !strlen( operation->fullName )) if (!operation->fullName || !strlen( operation->fullName ))
do { do {
@@ -377,7 +380,7 @@ void cli_masterPassword(Arguments *args, Operation *operation) {
} }
if ((!operation->masterPassword || !strlen( operation->masterPassword )) && args->masterPassword) if ((!operation->masterPassword || !strlen( operation->masterPassword )) && args->masterPassword)
operation->masterPassword = strdup( args->masterPassword ); operation->masterPassword = mpw_strdup( args->masterPassword );
if (!operation->masterPassword || !strlen( operation->masterPassword )) if (!operation->masterPassword || !strlen( operation->masterPassword ))
do { do {
@@ -394,7 +397,7 @@ void cli_masterPassword(Arguments *args, Operation *operation) {
void cli_siteName(Arguments *args, Operation *operation) { void cli_siteName(Arguments *args, Operation *operation) {
if ((!operation->siteName || !strlen( operation->siteName )) && args->siteName) if ((!operation->siteName || !strlen( operation->siteName )) && args->siteName)
operation->siteName = strdup( args->siteName ); operation->siteName = mpw_strdup( args->siteName );
if (!operation->siteName || !strlen( operation->siteName )) if (!operation->siteName || !strlen( operation->siteName ))
do { do {
operation->siteName = mpw_getline( "Site name:" ); operation->siteName = mpw_getline( "Site name:" );
@@ -438,7 +441,7 @@ void cli_keyContext(Arguments *args, Operation *operation) {
if (!args->keyContext) if (!args->keyContext)
return; return;
operation->keyContext = strdup( args->keyContext ); operation->keyContext = mpw_strdup( args->keyContext );
} }
void cli_user(Arguments *args, Operation *operation) { void cli_user(Arguments *args, Operation *operation) {
@@ -446,16 +449,16 @@ void cli_user(Arguments *args, Operation *operation) {
// Find mpsites file from parameters. // Find mpsites file from parameters.
FILE *sitesFile = NULL; FILE *sitesFile = NULL;
mpw_free_string( &operation->sitesPath ); mpw_free_string( &operation->sitesPath );
operation->sitesPath = mpw_path( operation->fullName, mpw_marshall_format_extension( operation->sitesFormat ) ); operation->sitesPath = mpw_path( operation->fullName, mpw_marshal_format_extension( operation->sitesFormat ) );
if (!operation->sitesPath || !(sitesFile = fopen( operation->sitesPath, "r" ))) { if (!operation->sitesPath || !(sitesFile = fopen( operation->sitesPath, "r" ))) {
dbg( "Couldn't open configuration file:\n %s: %s\n", operation->sitesPath, strerror( errno ) ); dbg( "Couldn't open configuration file:\n %s: %s\n", operation->sitesPath, strerror( errno ) );
// Try to fall back to the flat format. // Try to fall back to the flat format.
if (!operation->sitesFormatFixed) { if (!operation->sitesFormatFixed) {
mpw_free_string( &operation->sitesPath ); mpw_free_string( &operation->sitesPath );
operation->sitesPath = mpw_path( operation->fullName, mpw_marshall_format_extension( MPMarshallFormatFlat ) ); operation->sitesPath = mpw_path( operation->fullName, mpw_marshal_format_extension( MPMarshalFormatFlat ) );
if (operation->sitesPath && (sitesFile = fopen( operation->sitesPath, "r" ))) if (operation->sitesPath && (sitesFile = fopen( operation->sitesPath, "r" )))
operation->sitesFormat = MPMarshallFormatFlat; operation->sitesFormat = MPMarshalFormatFlat;
else else
dbg( "Couldn't open configuration file:\n %s: %s\n", operation->sitesPath, strerror( errno ) ); dbg( "Couldn't open configuration file:\n %s: %s\n", operation->sitesPath, strerror( errno ) );
} }
@@ -473,14 +476,14 @@ void cli_user(Arguments *args, Operation *operation) {
fclose( sitesFile ); fclose( sitesFile );
// Parse file. // Parse file.
MPMarshallInfo *sitesInputInfo = mpw_marshall_read_info( sitesInputData ); MPMarshalInfo *sitesInputInfo = mpw_marshal_read_info( sitesInputData );
MPMarshallFormat sitesInputFormat = args->sitesFormat? operation->sitesFormat: sitesInputInfo->format; MPMarshalFormat sitesInputFormat = args->sitesFormat? operation->sitesFormat: sitesInputInfo->format;
MPMarshallError marshallError = { .type = MPMarshallSuccess }; MPMarshalError marshalError = { .type = MPMarshalSuccess };
mpw_marshal_info_free( &sitesInputInfo ); mpw_marshal_info_free( &sitesInputInfo );
operation->user = mpw_marshall_read( sitesInputData, sitesInputFormat, operation->masterPassword, &marshallError ); operation->user = mpw_marshal_read( sitesInputData, sitesInputFormat, operation->masterPassword, &marshalError );
if (marshallError.type == MPMarshallErrorMasterPassword && operation->allowPasswordUpdate) { if (marshalError.type == MPMarshalErrorMasterPassword && operation->allowPasswordUpdate) {
// Update master password in mpsites. // Update master password in mpsites.
while (marshallError.type == MPMarshallErrorMasterPassword) { while (marshalError.type == MPMarshalErrorMasterPassword) {
inf( "Given master password does not match configuration.\n" ); inf( "Given master password does not match configuration.\n" );
inf( "To update the configuration with this new master password, first confirm the old master password.\n" ); inf( "To update the configuration with this new master password, first confirm the old master password.\n" );
@@ -489,26 +492,26 @@ void cli_user(Arguments *args, Operation *operation) {
importMasterPassword = mpw_getpass( "Old master password: " ); importMasterPassword = mpw_getpass( "Old master password: " );
mpw_marshal_free( &operation->user ); mpw_marshal_free( &operation->user );
operation->user = mpw_marshall_read( sitesInputData, sitesInputFormat, importMasterPassword, &marshallError ); operation->user = mpw_marshal_read( sitesInputData, sitesInputFormat, importMasterPassword, &marshalError );
mpw_free_string( &importMasterPassword ); mpw_free_string( &importMasterPassword );
} }
if (operation->user) { if (operation->user) {
mpw_free_string( &operation->user->masterPassword ); mpw_free_string( &operation->user->masterPassword );
operation->user->masterPassword = strdup( operation->masterPassword ); operation->user->masterPassword = mpw_strdup( operation->masterPassword );
} }
} }
mpw_free_string( &sitesInputData ); mpw_free_string( &sitesInputData );
// Incorrect master password. // Incorrect master password.
if (marshallError.type == MPMarshallErrorMasterPassword) { if (marshalError.type == MPMarshalErrorMasterPassword) {
ftl( "Incorrect master password according to configuration:\n %s: %s\n", operation->sitesPath, marshallError.description ); ftl( "Incorrect master password according to configuration:\n %s: %s\n", operation->sitesPath, marshalError.description );
cli_free( args, operation ); cli_free( args, operation );
exit( EX_DATAERR ); exit( EX_DATAERR );
} }
// Any other parse error. // Any other parse error.
if (!operation->user || marshallError.type != MPMarshallSuccess) { if (!operation->user || marshalError.type != MPMarshalSuccess) {
err( "Couldn't parse configuration file:\n %s: %s\n", operation->sitesPath, marshallError.description ); err( "Couldn't parse configuration file:\n %s: %s\n", operation->sitesPath, marshalError.description );
cli_free( args, operation ); cli_free( args, operation );
exit( EX_DATAERR ); exit( EX_DATAERR );
} }
@@ -516,7 +519,7 @@ void cli_user(Arguments *args, Operation *operation) {
// If no user from mpsites, create a new one. // If no user from mpsites, create a new one.
if (!operation->user) if (!operation->user)
operation->user = mpw_marshall_user( operation->user = mpw_marshal_user(
operation->fullName, operation->masterPassword, MPAlgorithmVersionCurrent ); operation->fullName, operation->masterPassword, MPAlgorithmVersionCurrent );
} }
@@ -532,7 +535,7 @@ void cli_site(Arguments __unused *args, Operation *operation) {
// If no site from mpsites, create a new one. // If no site from mpsites, create a new one.
if (!operation->site) if (!operation->site)
operation->site = mpw_marshall_site( operation->site = mpw_marshal_site(
operation->user, operation->siteName, MPResultTypeDefault, MPCounterValueDefault, operation->user->algorithm ); operation->user, operation->siteName, MPResultTypeDefault, MPCounterValueDefault, operation->user->algorithm );
} }
@@ -549,7 +552,7 @@ void cli_question(Arguments __unused *args, Operation *operation) {
case MPKeyPurposeRecovery: case MPKeyPurposeRecovery:
for (size_t q = 0; !operation->question && q < operation->site->questions_count; ++q) for (size_t q = 0; !operation->question && q < operation->site->questions_count; ++q)
if ((!operation->keyContext && !strlen( (&operation->site->questions[q])->keyword )) || if ((!operation->keyContext && !strlen( (&operation->site->questions[q])->keyword )) ||
(operation->keyContext && strcmp( (&operation->site->questions[q])->keyword, operation->keyContext ) != 0)) (operation->keyContext && strcmp( (&operation->site->questions[q])->keyword, operation->keyContext ) == 0))
operation->question = &operation->site->questions[q]; operation->question = &operation->site->questions[q];
// If no question from mpsites, create a new one. // If no question from mpsites, create a new one.
@@ -561,7 +564,7 @@ void cli_question(Arguments __unused *args, Operation *operation) {
void cli_operation(Arguments __unused *args, Operation *operation) { void cli_operation(Arguments __unused *args, Operation *operation) {
operation->identicon = mpw_identicon( operation->user->fullName, operation->user->masterPassword ); operation->identicon = mpw_identicon_str( mpw_identicon( operation->user->fullName, operation->user->masterPassword ) );
if (!operation->site) if (!operation->site)
abort(); abort();
@@ -570,23 +573,23 @@ void cli_operation(Arguments __unused *args, Operation *operation) {
case MPKeyPurposeAuthentication: { case MPKeyPurposeAuthentication: {
operation->purposeResult = "password"; operation->purposeResult = "password";
operation->resultType = operation->site->type; operation->resultType = operation->site->type;
operation->resultState = operation->site->content? strdup( operation->site->content ): NULL; operation->resultState = operation->site->content? mpw_strdup( operation->site->content ): NULL;
operation->siteCounter = operation->site->counter; operation->siteCounter = operation->site->counter;
break; break;
} }
case MPKeyPurposeIdentification: { case MPKeyPurposeIdentification: {
operation->purposeResult = "login"; operation->purposeResult = "login";
operation->resultType = operation->site->loginType; operation->resultType = operation->site->loginType;
operation->resultState = operation->site->loginContent? strdup( operation->site->loginContent ): NULL; operation->resultState = operation->site->loginContent? mpw_strdup( operation->site->loginContent ): NULL;
operation->siteCounter = MPCounterValueInitial; operation->siteCounter = MPCounterValueInitial;
break; break;
} }
case MPKeyPurposeRecovery: { case MPKeyPurposeRecovery: {
mpw_free_string( &operation->keyContext ); mpw_free_string( &operation->keyContext );
operation->purposeResult = "answer"; operation->purposeResult = "answer";
operation->keyContext = operation->question->keyword? strdup( operation->question->keyword ): NULL; operation->keyContext = operation->question->keyword? mpw_strdup( operation->question->keyword ): NULL;
operation->resultType = operation->question->type; operation->resultType = operation->question->type;
operation->resultState = operation->question->content? strdup( operation->question->content ): NULL; operation->resultState = operation->question->content? mpw_strdup( operation->question->content ): NULL;
operation->siteCounter = MPCounterValueInitial; operation->siteCounter = MPCounterValueInitial;
break; break;
} }
@@ -652,7 +655,7 @@ void cli_resultParam(Arguments *args, Operation *operation) {
if (!args->resultParam) if (!args->resultParam)
return; return;
operation->resultParam = strdup( args->resultParam ); operation->resultParam = mpw_strdup( args->resultParam );
} }
void cli_algorithmVersion(Arguments *args, Operation *operation) { void cli_algorithmVersion(Arguments *args, Operation *operation) {
@@ -712,18 +715,18 @@ void cli_mpw(Arguments *args, Operation *operation) {
switch (operation->keyPurpose) { switch (operation->keyPurpose) {
case MPKeyPurposeAuthentication: { case MPKeyPurposeAuthentication: {
mpw_free_string( &operation->site->content ); mpw_free_string( &operation->site->content );
operation->site->content = strdup( operation->resultState ); operation->site->content = mpw_strdup( operation->resultState );
break; break;
} }
case MPKeyPurposeIdentification: { case MPKeyPurposeIdentification: {
mpw_free_string( &operation->site->loginContent ); mpw_free_string( &operation->site->loginContent );
operation->site->loginContent = strdup( operation->resultState ); operation->site->loginContent = mpw_strdup( operation->resultState );
break; break;
} }
case MPKeyPurposeRecovery: { case MPKeyPurposeRecovery: {
mpw_free_string( &operation->question->content ); mpw_free_string( &operation->question->content );
operation->question->content = strdup( operation->resultState ); operation->question->content = mpw_strdup( operation->resultState );
break; break;
} }
} }
@@ -734,7 +737,7 @@ void cli_mpw(Arguments *args, Operation *operation) {
// resultParam defaults to state. // resultParam defaults to state.
if (!operation->resultParam && operation->resultState) if (!operation->resultParam && operation->resultState)
operation->resultParam = strdup( operation->resultState ); operation->resultParam = mpw_strdup( operation->resultState );
// Generate result. // Generate result.
const char *result = mpw_siteResult( masterKey, operation->site->name, operation->siteCounter, const char *result = mpw_siteResult( masterKey, operation->site->name, operation->siteCounter,
@@ -757,12 +760,12 @@ void cli_mpw(Arguments *args, Operation *operation) {
void cli_save(Arguments __unused *args, Operation *operation) { void cli_save(Arguments __unused *args, Operation *operation) {
if (operation->sitesFormat == MPMarshallFormatNone) if (operation->sitesFormat == MPMarshalFormatNone)
return; return;
if (!operation->sitesFormatFixed) if (!operation->sitesFormatFixed)
operation->sitesFormat = MPMarshallFormatDefault; operation->sitesFormat = MPMarshalFormatDefault;
operation->sitesPath = mpw_path( operation->user->fullName, mpw_marshall_format_extension( operation->sitesFormat ) ); operation->sitesPath = mpw_path( operation->user->fullName, mpw_marshal_format_extension( operation->sitesFormat ) );
dbg( "Updating: %s (%s)\n", operation->sitesPath, mpw_nameForFormat( operation->sitesFormat ) ); dbg( "Updating: %s (%s)\n", operation->sitesPath, mpw_nameForFormat( operation->sitesFormat ) );
FILE *sitesFile = NULL; FILE *sitesFile = NULL;
@@ -772,9 +775,9 @@ void cli_save(Arguments __unused *args, Operation *operation) {
} }
char *buf = NULL; char *buf = NULL;
MPMarshallError marshallError = { .type = MPMarshallSuccess }; MPMarshalError marshalError = { .type = MPMarshalSuccess };
if (!mpw_marshall_write( &buf, operation->sitesFormat, operation->user, &marshallError ) || marshallError.type != MPMarshallSuccess) if (!mpw_marshal_write( &buf, operation->sitesFormat, operation->user, &marshalError ) || marshalError.type != MPMarshalSuccess)
wrn( "Couldn't encode updated configuration file:\n %s: %s\n", operation->sitesPath, marshallError.description ); wrn( "Couldn't encode updated configuration file:\n %s: %s\n", operation->sitesPath, marshalError.description );
else if (fwrite( buf, sizeof( char ), strlen( buf ), sitesFile ) != strlen( buf )) else if (fwrite( buf, sizeof( char ), strlen( buf ), sitesFile ) != strlen( buf ))
wrn( "Error while writing updated configuration file:\n %s: %d\n", operation->sitesPath, ferror( sitesFile ) ); wrn( "Error while writing updated configuration file:\n %s: %d\n", operation->sitesPath, ferror( sitesFile ) );

View File

@@ -57,6 +57,7 @@ int main(int argc, char *const argv[]) {
MPKeyPurpose keyPurpose = mpw_purposeWithName( (char *)keyPurposeString ); MPKeyPurpose keyPurpose = mpw_purposeWithName( (char *)keyPurposeString );
// Run the test case. // Run the test case.
do {
fprintf( stdout, "test case %s... ", id ); fprintf( stdout, "test case %s... ", id );
if (!xmlStrlen( result )) { if (!xmlStrlen( result )) {
fprintf( stdout, "abstract.\n" ); fprintf( stdout, "abstract.\n" );
@@ -68,29 +69,39 @@ int main(int argc, char *const argv[]) {
(char *)fullName, (char *)masterPassword, algorithm ); (char *)fullName, (char *)masterPassword, algorithm );
if (!masterKey) { if (!masterKey) {
ftl( "Couldn't derive master key.\n" ); ftl( "Couldn't derive master key.\n" );
abort();
}
// Check the master key.
MPKeyID testKeyID = mpw_id_buf( masterKey, MPMasterKeySize );
if (xmlStrcmp( keyID, BAD_CAST testKeyID ) != 0) {
++failedTests;
fprintf( stdout, "FAILED! (keyID: got %s != expected %s)\n", testKeyID, keyID );
continue; continue;
} }
// 2. calculate the site password. // 2. calculate the site password.
const char *sitePassword = mpw_siteResult( const char *testResult = mpw_siteResult(
masterKey, (char *)siteName, siteCounter, keyPurpose, (char *)keyContext, resultType, NULL, algorithm ); masterKey, (char *)siteName, siteCounter, keyPurpose, (char *)keyContext, resultType, NULL, algorithm );
mpw_free( &masterKey, MPMasterKeySize ); mpw_free( &masterKey, MPMasterKeySize );
if (!sitePassword) { if (!testResult) {
ftl( "Couldn't derive site password.\n" ); ftl( "Couldn't derive site password.\n" );
continue; continue;
} }
// Check the result. // Check the site result.
if (xmlStrcmp( result, BAD_CAST sitePassword ) == 0) if (xmlStrcmp( result, BAD_CAST testResult ) != 0) {
fprintf( stdout, "pass.\n" );
else {
++failedTests; ++failedTests;
fprintf( stdout, "FAILED! (got %s != expected %s)\n", sitePassword, result ); fprintf( stdout, "FAILED! (result: got %s != expected %s)\n", testResult, result );
mpw_free_string( &testResult );
continue;
} }
mpw_free_string( &testResult );
fprintf( stdout, "pass.\n" );
} while(false);
// Free test case. // Free test case.
mpw_free_string( &sitePassword );
xmlFree( id ); xmlFree( id );
xmlFree( fullName ); xmlFree( fullName );
xmlFree( masterPassword ); xmlFree( masterPassword );

View File

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

View File

@@ -1 +1 @@
../../core/java/tests/src/main/resources/mpw_tests.xml ../../core/mpw_tests.xml

View File

@@ -1,14 +0,0 @@
plugins {
id 'java'
id 'application'
id 'com.github.johnrengelman.shadow' version '1.2.4'
}
description = 'Master Password CLI'
mainClassName = 'com.lyndir.masterpassword.CLI'
dependencies {
compile project(':masterpassword-algorithm')
compile group: 'ch.qos.logback', name: 'logback-classic', version:'1.1.2'
}

View File

@@ -1,98 +0,0 @@
<?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 CLI</name>
<description>A CLI interface to the Master Password algorithm</description>
<artifactId>masterpassword-cli</artifactId>
<packaging>jar</packaging>
<!-- BUILD CONFIGURATION -->
<build>
<resources>
<resource>
<directory>src/main/scripts</directory>
<filtering>true</filtering>
<targetPath>${project.build.directory}</targetPath>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.7</version>
<executions>
<execution>
<id>prepare-package</id>
<phase>prepare-package</phase>
<configuration>
<target>
<chmod file="${project.build.directory}/install" perm="755" />
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.2</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.lyndir.masterpassword.CLI</mainClass>
</transformer>
</transformers>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<!-- DEPENDENCY MANAGEMENT -->
<dependencies>
<!-- PROJECT REFERENCES -->
<dependency>
<groupId>com.lyndir.masterpassword</groupId>
<artifactId>masterpassword-algorithm</artifactId>
<version>GIT-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,189 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
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.Charsets;
import com.google.common.base.Joiner;
import com.google.common.collect.Maps;
import com.google.common.io.LineReader;
import com.google.common.primitives.UnsignedInteger;
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;
/**
* <p> <i>Jun 10, 2008</i> </p>
*
* @author mbillemo
*/
@SuppressWarnings({ "UseOfSystemOutOrSystemErr", "HardCodedStringLiteral" })
public final class CLI {
public static void main(final String... args)
throws IOException {
// Read information from the environment.
char[] masterPassword;
String siteName = null, context = null;
String userName = System.getenv( MPConstant.env_userName );
String siteTypeName = ifNotNullElse( System.getenv( MPConstant.env_siteType ), "" );
MPSiteType siteType = siteTypeName.isEmpty()? MPSiteType.GeneratedLong: MPSiteType.forOption( siteTypeName );
MPSiteVariant variant = MPSiteVariant.Password;
String siteCounterName = ifNotNullElse( System.getenv( MPConstant.env_siteCounter ), "" );
UnsignedInteger siteCounter = siteCounterName.isEmpty()? UnsignedInteger.valueOf( 1 ): UnsignedInteger.valueOf( siteCounterName );
// Parse information from option arguments.
boolean userNameArg = false, typeArg = false, counterArg = false, variantArg = false, contextArg = false;
for (final String arg : Arrays.asList( args ))
// Full Name
if ("-u".equals( arg ) || "--username".equals( arg ))
userNameArg = true;
else if (userNameArg) {
userName = arg;
userNameArg = false;
}
// 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 = UnsignedInteger.valueOf( 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.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", MPConstant.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", MPConstant.env_siteType );
int optionsLength = 0;
Map<String, MPSiteType> typeMap = Maps.newLinkedHashMap();
for (final MPSiteType elementType : MPSiteType.values()) {
String options = Joiner.on( ", " ).join( elementType.getOptions() );
typeMap.put( options, elementType );
optionsLength = Math.max( optionsLength, options.length() );
}
for (final Map.Entry<String, MPSiteType> entry : typeMap.entrySet()) {
String infoString = strf( " -v %" + optionsLength + "s | ", entry.getKey() );
String infoNewline = strf( "%n%s | ", StringUtils.repeat( " ", infoString.length() - 3 ) );
infoString += entry.getValue().getDescription().replaceAll( strf( "%n" ), infoNewline );
System.out.println( infoString );
}
System.out.println();
System.out.format( " -c counter The value of the counter.%n" );
System.out.format( " Defaults to %s in env or '1'.%n%n", MPConstant.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 (final MPSiteVariant elementVariant : MPSiteVariant.values()) {
String options = Joiner.on( ", " ).join( elementVariant.getOptions() );
variantMap.put( options, elementVariant );
optionsLength = Math.max( optionsLength, options.length() );
}
for (final Map.Entry<String, MPSiteVariant> entry : variantMap.entrySet()) {
String infoString = strf( " -v %" + optionsLength + "s | ", entry.getKey() );
String infoNewline = strf( "%n%s | ", StringUtils.repeat( " ", infoString.length() - 3 ) );
infoString += entry.getValue().getDescription().replaceAll( strf( "%n" ), infoNewline );
System.out.println( infoString );
}
System.out.println();
System.out.format( " -C context A variant-specific context.%n" );
System.out.format( " Defaults to empty.%n" );
for (final Map.Entry<String, MPSiteVariant> entry : variantMap.entrySet()) {
String infoString = strf( " -v %" + optionsLength + "s | ", entry.getKey() );
String infoNewline = strf( "%n%s | ", StringUtils.repeat( " ", infoString.length() - 3 ) );
infoString += entry.getValue().getContextDescription().replaceAll( strf( "%n" ), infoNewline );
System.out.println( infoString );
}
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;
// Read missing information from the console.
Console console = System.console();
try (InputStreamReader inReader = new InputStreamReader( System.in, Charsets.UTF_8 )) {
LineReader lineReader = new LineReader( inReader );
if (siteName == null) {
System.err.format( "Site name: " );
siteName = lineReader.readLine();
}
if (userName == null) {
System.err.format( "User's name: " );
userName = lineReader.readLine();
}
if (console != null)
masterPassword = console.readPassword( "%s's master password: ", userName );
else {
System.err.format( "%s's master password: ", userName );
masterPassword = lineReader.readLine().toCharArray();
}
}
// Encode and write out the site password.
System.out.println( MasterKey.create( userName, masterPassword ).encode( siteName, siteType, siteCounter, variant, context ) );
}
}

View File

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

View File

@@ -1,56 +0,0 @@
#!/usr/bin/env bash
#
# Install the Master Password CLI tool.
set -e
cd "${BASH_SOURCE%/*}"
source bashlib
inf "This will install the mpw tool."
# Try to guess then ask for the bin dir to install to.
IFS=: read -a paths <<< "$PATH"
if inArray ~/bin "${paths[@]}"; then
bindir=~/bin
elif inArray ~/.bin "${paths[@]}"; then
bindir=~/.bin
elif inArray /usr/local/bin "${paths[@]}"; then
bindir=/usr/local/bin
else
bindir=~/bin
fi
bindir=$(ask -d "$bindir" "What bin directory should I install to?")
[[ -d "$bindir" ]] || mkdir "$bindir" || ftl 'Cannot create missing bin directory: %s' "$bindir" || exit
[[ -w "$bindir" ]] || ftl 'Cannot write to bin directory: %s' "$bindir" || exit
# Try to guess then ask for the share dir to install to.
sharedir=$(cd -P "$bindir/.."; [[ $bindir = */.bin ]] && printf '%s/.share' "$PWD" || printf '%s/share' "$PWD")
sharedir=$(ask -d "$sharedir" "What share directory should I install to?")
[[ -d "$sharedir" ]] || mkdir "$sharedir" || ftl 'Cannot create missing share directory: %s' "$sharedir" || exit
[[ -w "$sharedir" ]] || ftl 'Cannot write to share directory: %s' "$sharedir" || exit
# Install Master Password.
sharepath=$sharedir/masterpassword
mkdir -p "$sharepath"
cp -a "masterpassword-cli-"*".jar" bashlib mpw "$sharepath"
ex -c "%s~%SHAREPATH%~$sharepath~g|x" "$sharepath/mpw"
ln -sf "$sharepath/mpw" "$bindir/mpw"
chmod +x "$sharepath/mpw"
[[ ! -e "$bindir/bashlib" ]] && ln -s "$sharepath/bashlib" "$bindir/bashlib" ||:
# Convenience bash function.
inf "Installation successful!"
echo
inf "To improve usability, you can install an mpw function in your bash shell."
inf "This function adds the following features:"
inf " - Ask the Master Password once, remember in the shell."
inf " - Automatically put the password in the clipboard (some platforms)."
echo
inf "To do this you need the following function in ~/.bashrc:\n%s" "$(<mpw.bashrc)"
echo
inf "We can do this for you automatically now."
if ask -c Y!n "Append the mpw function to your .bashrc?"; then
cat mpw.bashrc >> ~/.bashrc
inf "Done! Don't forget to run '%s' to apply the changes!" "source ~/.bashrc"
fi
echo
inf "To begin using Master Password, type: mpw [site name]"

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