From d43c19a749e63f5d75ed453ce74be8f59d1073d2 Mon Sep 17 00:00:00 2001 From: Maarten Billemont Date: Mon, 5 May 2014 11:16:59 -0400 Subject: [PATCH] Add C implementation of Master Password. --- .gitignore | 5 + MasterPassword/C/build | 2 + MasterPassword/C/install | 15 ++ MasterPassword/C/lib/proplib/.source | 1 + MasterPassword/C/lib/scrypt-osx.patch | 38 +++++ MasterPassword/C/lib/scrypt/.source | 1 + MasterPassword/C/mpw.c | 220 ++++++++++++++++++++++++++ 7 files changed, 282 insertions(+) create mode 100755 MasterPassword/C/build create mode 100755 MasterPassword/C/install create mode 100644 MasterPassword/C/lib/proplib/.source create mode 100644 MasterPassword/C/lib/scrypt-osx.patch create mode 100644 MasterPassword/C/lib/scrypt/.source create mode 100644 MasterPassword/C/mpw.c diff --git a/.gitignore b/.gitignore index 9b0d501c..14785cf2 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,8 @@ Press/MasterPassword_PressKit/MasterPassword_pressrelease_*.pdf # Java MasterPassword/Java/**/target + +# C +MasterPassword/C/mpw +MasterPassword/C/lib/*/* +!MasterPassword/C/lib/*/.source diff --git a/MasterPassword/C/build b/MasterPassword/C/build new file mode 100755 index 00000000..1469f69f --- /dev/null +++ b/MasterPassword/C/build @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +gcc -I"lib/scrypt/lib" -I"lib/scrypt/libcperciva" -I"lib/proplib/include" -l "crypto_aesctr.o" -l "sha256.o" -l "crypto_scrypt-nosse.o" -l "memlimit.o" -l "scryptenc_cpuperf.o" -l"scryptenc.o" -l"crypto" -l"prop" -L"lib/scrypt" -L"lib/proplib/src/.libs" mpw.c -o mpw diff --git a/MasterPassword/C/install b/MasterPassword/C/install new file mode 100755 index 00000000..d6715f9d --- /dev/null +++ b/MasterPassword/C/install @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +BINDIR=${BINDIR:+${PREFIX:+$PREFIX/bin}} +[[ $BINDIR ]] && mkdir -p "$BINDIR" +if [[ ! -w $BINDIR ]]; then + for dir in /usr/local/bin ~/.bin ~/bin /usr/bin; do + [[ -w $dir ]] && BINDIR=$dir && break + done + if [[ ! -w $BINDIR ]]; then + echo >&2 "Could not find directory to install to." + echo >&2 "You can specify a prefix to install to, eg. PREFIX=/usr/local ./install" + echo >&2 "You can specify a bin directory to install to, eg. BINDIR=~/bin ./install" + echo >&2 "Make sure you have write permission to the bin directory." + fi +fi +install -v -s mpw "$BINDIR" diff --git a/MasterPassword/C/lib/proplib/.source b/MasterPassword/C/lib/proplib/.source new file mode 100644 index 00000000..b9266190 --- /dev/null +++ b/MasterPassword/C/lib/proplib/.source @@ -0,0 +1 @@ +https://code.google.com/p/portableproplib/ diff --git a/MasterPassword/C/lib/scrypt-osx.patch b/MasterPassword/C/lib/scrypt-osx.patch new file mode 100644 index 00000000..cd8f1eed --- /dev/null +++ b/MasterPassword/C/lib/scrypt-osx.patch @@ -0,0 +1,38 @@ +diff -ruN /Users/lhunath/.src/scrypt/Makefile ./Makefile +--- /Users/lhunath/.src/scrypt/Makefile 2014-05-02 11:28:58.000000000 -0400 ++++ ./Makefile 2014-05-02 12:07:27.000000000 -0400 +@@ -2,11 +2,11 @@ + VER?= nosse + SRCS= main.c + LDADD+= -lcrypto +-WARNS?= 6 ++WARNS?= 0 + + # We have a config file for FreeBSD + CFLAGS += -I . +-CFLAGS += -DCONFIG_H_FILE=\"config_freebsd.h\" ++CFLAGS += -DCONFIG_H_FILE=\"config_osx.h\" + + # Include all possible object files containing built scrypt code. + CLEANFILES += crypto_scrypt-ref.o +diff -ruN /Users/lhunath/.src/scrypt/lib/util/memlimit.c ./lib/util/memlimit.c +--- /Users/lhunath/.src/scrypt/lib/util/memlimit.c 2014-05-02 11:28:58.000000000 -0400 ++++ ./lib/util/memlimit.c 2014-05-02 11:52:42.000000000 -0400 +@@ -75,7 +75,7 @@ + * have returned to us. + */ + if (usermemlen == sizeof(uint64_t)) +- usermem = *(uint64_t *)usermembuf; ++ usermem = *(uint64_t *)(void *)usermembuf; + else if (usermemlen == sizeof(uint32_t)) + usermem = SIZE_MAX; + else +diff -ruN /Users/lhunath/.src/scrypt/lib/util/memlimit.c ./lib/util/memlimit.c +--- /Users/lhunath/.src/scrypt/config_osx.h 1969-12-31 19:00:00.000000000 -0500 ++++ config_osx.h 2014-05-02 12:06:55.000000000 -0400 +@@ -0,0 +1,5 @@ ++/* A default configuration for FreeBSD, used if there is no config.h. */ ++ ++#define HAVE_POSIX_MEMALIGN 1 ++#define HAVE_SYSCTL_HW_USERMEM 1 ++#define HAVE_SYS_PARAM_H 1 diff --git a/MasterPassword/C/lib/scrypt/.source b/MasterPassword/C/lib/scrypt/.source new file mode 100644 index 00000000..53a77880 --- /dev/null +++ b/MasterPassword/C/lib/scrypt/.source @@ -0,0 +1 @@ +https://code.google.com/p/scrypt/ diff --git a/MasterPassword/C/mpw.c b/MasterPassword/C/mpw.c new file mode 100644 index 00000000..194b9805 --- /dev/null +++ b/MasterPassword/C/mpw.c @@ -0,0 +1,220 @@ +#include +#include +#include +#include +#if defined(__linux__) +#include +#elif defined(__CYGWIN__) +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define MP_N 32768 +#define MP_r 8 +#define MP_p 2 +#define MP_dkLen 64 +#define MP_hash PearlHashSHA256 + +#define MP_env_username "MP_USERNAME" +#define MP_env_sitetype "MP_SITETYPE" +#define MP_env_sitecounter "MP_SITECOUNTER" + +char *homedir(const char *filename) { + char *homedir = NULL; +#if defined(__CYGWIN__) + homedir = getenv("USERPROFILE"); + if (!homedir) { + const char *homeDrive = getenv("HOMEDRIVE"); + const char *homePath = getenv("HOMEPATH"); + homedir = char[strlen(homeDrive) + strlen(homePath) + 1]; + sprintf(homedir, "%s/%s", homeDrive, homePath); + } +#else + struct passwd* passwd = getpwuid(getuid()); + if (passwd) + homedir = passwd->pw_dir; + if (!homedir) + homedir = getenv("HOME"); +#endif + if (!homedir) + homedir = getcwd(NULL, 0); + + char *homefile = NULL; + asprintf(&homefile, "%s/%s", homedir, filename); + return homefile; +} + +int main(int argc, char *const argv[]) { + + // Read the environment. + const char *userName = getenv( MP_env_username ); + const char *masterPassword = NULL; + const char *siteName = NULL; + const char *siteTypeString = getenv( MP_env_sitetype ); + uint32_t siteCounter = 1; + const char *siteCounterString = getenv( MP_env_sitecounter ); + + // Read the options. + char opt; + while ((opt = getopt(argc, argv, "u:t:c:")) != -1) + switch (opt) { + case 'u': + userName = optarg; + break; + case 't': + siteTypeString = optarg; + break; + case 'c': + siteCounterString = optarg; + break; + case '?': + switch (optopt) { + case 'u': + fprintf (stderr, "Missing user name to option: -%c\n", optopt); + break; + case 't': + fprintf (stderr, "Missing type name to option: -%c\n", optopt); + break; + case 'c': + fprintf (stderr, "Missing counter value to option: -%c\n", optopt); + break; + default: + fprintf (stderr, "Unknown option: -%c\n", optopt); + } + return 1; + default: + abort(); + } + if (optind < argc) + siteName = argv[optind]; + + // Convert and validate input. + if (!userName) { + fprintf (stderr, "Missing user name.\n"); + return 1; + } + if (!siteName) { + fprintf (stderr, "Missing site name.\n"); + return 1; + } + if (siteCounterString) + siteCounter = atoi( siteCounterString ); + if (siteCounter < 1) { + fprintf (stderr, "Invalid site counter: %d\n", siteCounter); + return 1; + } + + // Read the master password. + char *mpwConfigPath = homedir(".mpw"); + if (!mpwConfigPath) { + fprintf (stderr, "Couldn't resolve path for configuration file: %d\n", errno); + return 1; + } + FILE *mpwConfig = fopen(mpwConfigPath, "r"); + if (!mpwConfig) { + fprintf (stderr, "Couldn't open configuration file: %s: %d\n", mpwConfigPath, errno); + return 1; + } + free(mpwConfigPath); + char *line = NULL; + size_t linecap = 0; + ssize_t linelen; + while ((linelen = getline(&line, &linecap, mpwConfig)) > 0) { + char *configUserName = strsep(&line, "\t: "); + if (configUserName == userName) { + while (line[0] && strlen(masterPassword = strsep(&line, "\t: ")) == 0); + break; + } + } + if (!masterPassword) { + fprintf (stderr, "Missing master password for user: %s\n", userName); + return 1; + } + + // Calculate the master key. + uint8_t *masterKey = malloc( MP_dkLen ); + if (!masterKey) { + fprintf (stderr, "Could not allocate master key: %d\n", errno); + return 1; + } + const uint32_t n_userNameLength = htonl(strlen(userName)); + char *masterKeySalt = NULL; + size_t masterKeySaltLength = asprintf(&masterKeySalt, "com.lyndir.masterpassword%s%s", (const char *) &n_userNameLength, userName); + if (!masterKeySalt) { + fprintf (stderr, "Could not allocate master key salt: %d\n", errno); + return 1; + } + if (crypto_scrypt( (const uint8_t *)masterPassword, strlen(masterPassword), (const uint8_t *)masterKeySalt, masterKeySaltLength, MP_N, MP_r, MP_p, masterKey, MP_dkLen ) < 0) { + fprintf (stderr, "Could not generate master key: %d\n", errno); + return 1; + } + memset(masterKeySalt, 0, masterKeySaltLength); + free(masterKeySalt); + + // Calculate the site seed. + const uint32_t n_siteCounter = htonl(siteCounter), n_siteNameLength = htonl(strlen(siteName)); + char *sitePasswordInfo = NULL; + size_t sitePasswordInfoLength = asprintf(&sitePasswordInfo, "com.lyndir.masterpassword%s%s%s", (const char *) &n_siteNameLength, siteName, (const char *) &n_siteCounter); + if (!sitePasswordInfo) { + fprintf (stderr, "Could not allocate site seed: %d\n", errno); + return 1; + } + uint8_t sitePasswordSeed[32]; + HMAC_SHA256_Buf(masterKey, MP_dkLen, sitePasswordInfo, sitePasswordInfoLength, sitePasswordSeed); + memset(masterKey, 0, MP_dkLen); + memset(sitePasswordInfo, 0, sitePasswordInfoLength); + free(masterKey); + free(sitePasswordInfo); + + // Determine the cipher. + prop_dictionary_t MPTypes_ciphers = prop_dictionary_internalize_from_file("ciphers.plist"); + if (!MPTypes_ciphers) { + fprintf (stderr, "Could not read cipher definitions: %d\n", errno); + return 1; + } + prop_array_t typeCiphers = prop_dictionary_get(prop_dictionary_get(MPTypes_ciphers, "[self classNameOfType:type]"), "[self nameOfType:type]"); + if (!typeCiphers) { + fprintf (stderr, "Could not find cipher definition for type: %s\n", siteTypeString); + return 1; + } + prop_string_t cipher = prop_array_get(typeCiphers, sitePasswordSeed[0] % prop_array_count(typeCiphers)); + if (!typeCiphers) { + fprintf (stderr, "Missing cipher definitions for type: %s\n", siteTypeString); + return 1; + } + //trc(@"type %@, ciphers: %@, selected: %@", [self nameOfType:type], typeCiphers, cipher); + + // Encode the password from the seed using the cipher. + //NSAssert([seed length] >= [cipher length] + 1, @"Insufficient seed bytes to encode cipher."); + const prop_dictionary_t characterClasses = prop_dictionary_get(MPTypes_ciphers, "MPCharacterClasses"); + char *sitePassword = calloc(prop_string_size(cipher) + 1, sizeof(char)); + char cipherClass[2] = {0, 0}; + for (int c = 0; c < prop_string_size(cipher); ++c) { + + const uint16_t keyByte = sitePasswordSeed[c + 1]; + cipherClass[0] = prop_string_cstring_nocopy(cipher)[c]; + const prop_string_t cipherClassCharacters = prop_dictionary_get(characterClasses, cipherClass); + const char character = prop_string_cstring_nocopy(cipherClassCharacters)[ keyByte % prop_string_size(cipherClassCharacters) ]; + + //trc(@"class %@ has characters: %@, index: %u, selected: %@", cipherClass, cipherClassCharacters, keyByte, character); + sitePassword[c] = character; + } + memset(sitePasswordSeed, 0, sizeof(sitePasswordSeed)); + + // Output the password. + fprintf( stdout, "%s\n", sitePassword ); + return 0; +}