diff --git a/core/c/mpw-types.c b/core/c/mpw-types.c index ad74b057..93da1aba 100644 --- a/core/c/mpw-types.c +++ b/core/c/mpw-types.c @@ -262,3 +262,40 @@ const char mpw_characterFromClass(char characterClass, uint8_t seedByte) { 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; +} diff --git a/core/c/mpw-types.h b/core/c/mpw-types.h index 990e6fef..2541ed60 100644 --- a/core/c/mpw-types.h +++ b/core/c/mpw-types.h @@ -111,6 +111,14 @@ typedef mpw_enum ( uint32_t, MPCounterValue ) { MPCounterValueLast = UINT32_MAX, }; +typedef struct { + const char *leftArm; + const char *body; + const char *rightArm; + const char *accessory; + uint8_t color; +} MPIdenticon; + //// Type utilities. /** @@ -157,4 +165,7 @@ const char *mpw_charactersInClass(char characterClass); */ 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 diff --git a/core/c/mpw-util.c b/core/c/mpw-util.c index b0034241..1eca80ae 100644 --- a/core/c/mpw-util.c +++ b/core/c/mpw-util.c @@ -20,12 +20,6 @@ #include #include -#if MPW_COLOR -#include -#include -#include -#endif - #if MPW_CPERCIVA #include #include @@ -141,7 +135,7 @@ void mpw_zero(void *buffer, size_t bufferSize) { uint8_t *b = buffer; for (; bufferSize > 0; --bufferSize) - *b++ = 0; + *b++ = 0; } bool __mpw_free(void **buffer, const size_t bufferSize) { @@ -274,9 +268,10 @@ uint8_t const *mpw_hash_hmac_sha256(const uint8_t *key, const size_t keySize, co return mac; } +// 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 (!key || keySize < 16) + if (!key || keySize < 16 || !*bufSize) return NULL; // IV = zero @@ -284,9 +279,9 @@ static uint8_t const *mpw_aes(bool encrypt, const uint8_t *key, const size_t key mpw_zero( iv, sizeof iv ); // Add PKCS#7 padding - uint32_t aesSize = (uint32_t)*bufSize; - if (encrypt) - aesSize = (aesSize / 16) * 16 + 16; + uint32_t aesSize = ((uint32_t)*bufSize + 15 / 16) * 16; // round up to block size. + if (encrypt && !(*bufSize % 16)) // add pad block if plain text fits block size. + encrypt += 16; uint8_t aesBuf[aesSize]; memcpy( aesBuf, buf, *bufSize ); memset( aesBuf + *bufSize, aesSize - *bufSize, aesSize - *bufSize ); @@ -302,7 +297,7 @@ static uint8_t const *mpw_aes(bool encrypt, const uint8_t *key, const size_t key // Truncate PKCS#7 padding if (encrypt) *bufSize = aesSize; - else + else if (*bufSize % 16 == 0 && resultBuf[aesSize - 1] < 16) *bufSize -= resultBuf[aesSize - 1]; return resultBuf; @@ -443,99 +438,6 @@ const char *mpw_hex_l(uint32_t number) { 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: mpw_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. */ @@ -568,6 +470,10 @@ const size_t mpw_utf8_strlen(const char *utf8String) { } char *mpw_strdup(const char *src) { + + if (!src) + return NULL; + size_t len = strlen( src ); char *dst = malloc( len + 1 ); memcpy( dst, src, len ); @@ -577,6 +483,10 @@ char *mpw_strdup(const char *src) { } char *mpw_strndup(const char *src, size_t max) { + + if (!src) + return NULL; + size_t len = 0; for (; len < max && src[len] != '\0'; ++len); diff --git a/core/c/mpw-util.h b/core/c/mpw-util.h index 18bd1755..ee90133a 100644 --- a/core/c/mpw-util.h +++ b/core/c/mpw-util.h @@ -203,17 +203,14 @@ MPKeyID mpw_id_buf(const void *buf, size_t length); /** Compare two fingerprints for equality. * @return true if the buffers represent identical fingerprints. */ 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. /** @return The amount of display characters in the given UTF-8 string. */ const size_t mpw_utf8_strlen(const char *utf8String); -/** Drop-in for non-standard strdup(3). */ +/** Drop-in for POSIX strdup(3). */ char *mpw_strdup(const char *src); -/** Drop-in for non-standard strndup(3). */ +/** Drop-in for POSIX strndup(3). */ char *mpw_strndup(const char *src, size_t max); #endif // _MPW_UTIL_H diff --git a/platform-independent/cli-c/cli/mpw-cli-util.c b/platform-independent/cli-c/cli/mpw-cli-util.c index 47d3fd4b..8fc71ad8 100644 --- a/platform-independent/cli-c/cli/mpw-cli-util.c +++ b/platform-independent/cli-c/cli/mpw-cli-util.c @@ -28,18 +28,21 @@ #include #include +#define MPW_MAX_INPUT 60 + +#if MPW_COLOR +#include +#include +#endif + #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) { char *envBuf = getenv( variableName ); 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) { const char *askpass = mpw_getenv( MP_ENV_askpass ); @@ -91,15 +94,59 @@ char *mpw_askpass(const char *prompt) { return NULL; } -/** Ask the user a question. - * @return A newly allocated string or NULL if an error occurred trying to read from the user. */ -const char *mpw_getline(const char *prompt) { +static const char *_mpw_getline(const char *prompt, bool silent) { // Get answer from askpass. char *answer = mpw_askpass( prompt ); if (answer) return answer; +#if MPW_COLOR + // Initialize a curses screen. + initscr(); + 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(); + + return result == ERR? NULL: mpw_strndup( str, MPW_MAX_INPUT ); +#else // Get password from terminal. fprintf( stderr, "%s ", prompt ); @@ -113,31 +160,19 @@ const char *mpw_getline(const char *prompt) { // Remove trailing newline. answer[lineSize - 1] = '\0'; 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) { - // Get password from askpass. - const char *password = mpw_askpass( prompt ); - if (password) - return password; - - // Get password from terminal. - char *answer = getpass( prompt ); - if (!answer) - return NULL; - - password = mpw_strdup( answer ); - mpw_zero( answer, strlen( answer ) ); - return password; + return _mpw_getline( prompt, true ); } -/** Get the absolute path to the mpw configuration file with the given prefix name and file extension. - * Resolves the file 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) { // Resolve user's home directory. @@ -180,8 +215,6 @@ const char *mpw_path(const char *prefix, const char *extension) { 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) { if (!filePath) @@ -215,8 +248,6 @@ bool mpw_mkdirs(const char *filePath) { 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 *buf = NULL; @@ -230,8 +261,6 @@ char *mpw_read_fd(int fd) { 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) { if (!file) @@ -245,3 +274,73 @@ char *mpw_read_file(FILE *file) { 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 ); +} diff --git a/platform-independent/cli-c/cli/mpw-cli-util.h b/platform-independent/cli-c/cli/mpw-cli-util.h index 0ee275fc..1641a8ab 100644 --- a/platform-independent/cli-c/cli/mpw-cli-util.h +++ b/platform-independent/cli-c/cli/mpw-cli-util.h @@ -19,6 +19,7 @@ #include #include #include +#include "mpw-types.h" #ifndef MP_VERSION #define MP_VERSION ? @@ -62,3 +63,7 @@ char *mpw_read_fd(int fd); /** Read the file contents of a given file. * @return A newly allocated string or NULL the read buffer couldn't be allocated. */ 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); diff --git a/platform-independent/cli-c/cli/mpw-cli.c b/platform-independent/cli-c/cli/mpw-cli.c index 0704f32d..1035b4c7 100644 --- a/platform-independent/cli-c/cli/mpw-cli.c +++ b/platform-independent/cli-c/cli/mpw-cli.c @@ -564,7 +564,7 @@ void cli_question(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) abort();