2
0

Improved error checking, NULL handling and API documentation.

This commit is contained in:
Maarten Billemont 2020-01-23 15:55:03 -05:00
parent b42bc732ac
commit ad4081be61
12 changed files with 136 additions and 79 deletions

View File

@ -128,7 +128,7 @@ static const char *_mpw_getline(const char *prompt, bool silent) {
// Read response.
color_set( 2, NULL );
attron( A_STANDOUT );
int result = ERR;
int result;
char str[MPW_MAX_INPUT + 1];
if (silent) {
mvprintw( rows / 2 + 1, (cols - 5) / 2, "[ * ]" );
@ -180,6 +180,17 @@ const char *mpw_getpass(const char *prompt) {
const char *mpw_path(const char *prefix, const char *extension) {
if (!prefix || !extension)
return NULL;
// Compose filename.
char *path = mpw_strdup( mpw_str( "%s.%s", prefix, extension ) );
if (!path)
return NULL;
// This is a filename, remove all potential directory separators.
for (char *slash; (slash = strstr( path, "/" )); *slash = '_');
// Resolve user's home directory.
char *homeDir = NULL;
if (!homeDir)
@ -201,12 +212,6 @@ const char *mpw_path(const char *prefix, const char *extension) {
if (!homeDir)
homeDir = getcwd( NULL, 0 );
// Compose filename.
char *path = mpw_strdup( mpw_str( "%s.%s", prefix, extension ) );
// This is a filename, remove all potential directory separators.
for (char *slash; (slash = strstr( path, "/" )); *slash = '_');
// Compose pathname.
if (homeDir) {
const char *homePath = mpw_str( "%s/.mpw.d/%s", homeDir, path );
@ -277,6 +282,8 @@ const char *mpw_read_file(FILE *file) {
while ((mpw_realloc( &buf, &bufSize, blockSize )) &&
(bufOffset += (readSize = fread( buf + bufOffset, 1, blockSize, file ))) &&
(readSize == blockSize));
if (ferror( file ))
mpw_free( &buf, bufSize );
return buf;
}
@ -345,7 +352,9 @@ const char *mpw_identicon_str(MPIdenticon identicon) {
}
const char *str = mpw_str( "%s%s%s%s%s%s",
colorString, identicon.leftArm, identicon.body, identicon.rightArm, identicon.accessory, resetString );
colorString? colorString: "",
identicon.leftArm, identicon.body, identicon.rightArm, identicon.accessory,
resetString? resetString: "" );
mpw_free_strings( &colorString, &resetString, NULL );
return mpw_strdup( str );

View File

@ -35,7 +35,7 @@
const char *mpw_getenv(const char *variableName);
/** Use the askpass program to prompt the user.
* @return A newly allocated string or NULL if askpass is not supported or an error occurred. */
* @return A newly allocated string or NULL if askpass is not enabled or could not be executed. */
const char *mpw_askpass(const char *prompt);
/** Ask the user a question.
@ -49,7 +49,7 @@ const char *mpw_getpass(const char *prompt);
/** 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. */
* @return A newly allocated string or NULL if the prefix or extension is missing or the path could not be allocated. */
const char *mpw_path(const char *prefix, const char *extension);
/** mkdir all the directories up to the directory of the given file path.
@ -57,13 +57,13 @@ const char *mpw_path(const char *prefix, const char *extension);
bool mpw_mkdirs(const char *filePath);
/** Read until EOF from the given file descriptor.
* @return A newly allocated string or NULL the read buffer couldn't be allocated. */
* @return A newly allocated string or NULL if the an IO error occurred or the read buffer couldn't be allocated. */
const 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. */
* @return A newly allocated string or NULL if the file is missing, an IO error occurred or the read buffer couldn't be allocated. */
const char *mpw_read_file(FILE *file);
/** Encode a visual fingerprint for a user.
* @return A newly allocated string. */
* @return A newly allocated string or NULL if the identicon couldn't be allocated. */
const char *mpw_identicon_str(MPIdenticon identicon);

View File

@ -32,8 +32,14 @@ MPMasterKey mpw_master_key(const char *fullName, const char *masterPassword, con
trc( "-- mpw_master_key (algorithm: %u)", algorithmVersion );
trc( "fullName: %s", fullName );
trc( "masterPassword.id: %s", masterPassword? mpw_id_buf( masterPassword, strlen( masterPassword ) ): NULL );
if (!fullName || !masterPassword)
if (!fullName) {
err( "Missing fullName" );
return NULL;
}
if (!masterPassword) {
err( "Missing masterPassword" );
return NULL;
}
switch (algorithmVersion) {
case MPAlgorithmVersion0:
@ -62,8 +68,14 @@ MPSiteKey mpw_site_key(
trc( "siteCounter: %d", siteCounter );
trc( "keyPurpose: %d (%s)", keyPurpose, mpw_purpose_name( keyPurpose ) );
trc( "keyContext: %s", keyContext );
if (!masterKey || !siteName)
if (!masterKey) {
err( "Missing masterKey" );
return NULL;
}
if (!siteName) {
err( "Missing siteName" );
return NULL;
}
switch (algorithmVersion) {
case MPAlgorithmVersion0:
@ -92,12 +104,18 @@ const char *mpw_site_result(
resultParam = NULL;
MPSiteKey siteKey = mpw_site_key( masterKey, siteName, siteCounter, keyPurpose, keyContext, algorithmVersion );
if (!siteKey)
return NULL;
trc( "-- mpw_site_result (algorithm: %u)", algorithmVersion );
trc( "resultType: %d (%s)", resultType, mpw_type_short_name( resultType ) );
trc( "resultParam: %s", resultParam );
if (!masterKey) {
err( "Missing masterKey" );
return NULL;
}
if (!siteKey) {
err( "Missing siteKey" );
return NULL;
}
char *sitePassword = NULL;
if (resultType & MPResultTypeClassTemplate) {
@ -164,14 +182,22 @@ const char *mpw_site_state(
resultParam = NULL;
MPSiteKey siteKey = mpw_site_key( masterKey, siteName, siteCounter, keyPurpose, keyContext, algorithmVersion );
if (!siteKey)
return NULL;
trc( "-- mpw_site_state (algorithm: %u)", algorithmVersion );
trc( "resultType: %d (%s)", resultType, mpw_type_short_name( resultType ) );
trc( "resultParam: %zu bytes = %s", sizeof( resultParam ), resultParam );
if (!masterKey || !resultParam)
trc( "resultParam: %zu bytes = %s", resultParam? strlen( resultParam ): 0, resultParam );
if (!masterKey) {
err( "Missing masterKey" );
return NULL;
}
if (!siteKey) {
err( "Missing siteKey" );
return NULL;
}
if (!resultParam) {
err( "Missing resultParam" );
return NULL;
}
switch (algorithmVersion) {
case MPAlgorithmVersion0:
@ -214,10 +240,10 @@ MPIdenticon mpw_identicon(const char *fullName, const char *masterPassword) {
};
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] ))],
.leftArm = leftArm[identiconSeed[0] % (sizeof( leftArm ) / sizeof( *leftArm ))],
.body = body[identiconSeed[1] % (sizeof( body ) / sizeof( *body ))],
.rightArm = rightArm[identiconSeed[2] % (sizeof( rightArm ) / sizeof( *rightArm ))],
.accessory = accessory[identiconSeed[3] % (sizeof( accessory ) / sizeof( *accessory ))],
.color = (uint8_t)(identiconSeed[4] % (MPIdenticonColorLast - MPIdenticonColorFirst + 1) + MPIdenticonColorFirst),
};
mpw_free( &identiconSeed, 32 );

View File

@ -38,19 +38,19 @@ typedef mpw_enum( unsigned int, MPAlgorithmVersion ) {
};
/** Derive the master key for a user based on their name and master password.
* @return A new MPMasterKeySize-byte allocated buffer or NULL if an error occurred. */
* @return A new MPMasterKeySize-byte allocated buffer or NULL if the fullName or masterPassword is missing, the algorithm is unknown, or an algorithm error occurred. */
MPMasterKey mpw_master_key(
const char *fullName, const char *masterPassword, const MPAlgorithmVersion algorithmVersion);
/** Derive the site key for a user's site from the given master key and site parameters.
* @return A new MPSiteKeySize-byte allocated buffer or NULL if an error occurred. */
* @return A new MPSiteKeySize-byte allocated buffer or NULL if the masterKey or siteName is missing, the algorithm is unknown, or an algorithm error occurred. */
MPSiteKey mpw_site_key(
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
const MPKeyPurpose keyPurpose, const char *keyContext, const MPAlgorithmVersion algorithmVersion);
/** Generate a site result token from the given parameters.
* @param resultParam A parameter for the resultType. For stateful result types, the output of mpw_site_state.
* @return A newly allocated string or NULL if an error occurred. */
* @return A newly allocated string or NULL if the masterKey or siteName is missing, the algorithm is unknown, or an algorithm error occurred. */
const char *mpw_site_result(
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
const MPKeyPurpose keyPurpose, const char *keyContext,
@ -59,7 +59,7 @@ const char *mpw_site_result(
/** Encrypt a stateful site token for persistence.
* @param resultParam A parameter for the resultType. For stateful result types, the desired mpw_site_result.
* @return A newly allocated string or NULL if an error occurred. */
* @return A newly allocated string or NULL if the masterKey, siteName or resultParam is missing, the algorithm is unknown, or an algorithm error occurred. */
const char *mpw_site_state(
MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter,
const MPKeyPurpose keyPurpose, const char *keyContext,

View File

@ -52,7 +52,7 @@ time_t mpw_timegm(const char *time) {
return local_time + gmtoff;
}
return false;
return ERR;
}
#if MPW_JSON

View File

@ -33,7 +33,8 @@
* @return A new string containing the token or NULL if the delim wasn't found before eol. */
char *mpw_get_token(
const char **in, const char *eol, char *delim);
/** Convert an RFC 3339 time string into epoch time. */
/** Convert an RFC 3339 time string into epoch time.
* @return ERR if the string could not be parsed. */
time_t mpw_timegm(
const char *time);

View File

@ -861,7 +861,7 @@ MPMarshalledUser *mpw_marshal_read(
switch (inFormat) {
case MPMarshalFormatNone:
*error = (MPMarshalError){ .type = MPMarshalSuccess };
return false;
return NULL;
case MPMarshalFormatFlat:
return mpw_marshal_read_flat( in, masterKeyProvider, error );
#if MPW_JSON

View File

@ -125,20 +125,24 @@ bool mpw_marshal_write(
/** Try to read metadata on the sites in the input buffer. */
MPMarshalInfo *mpw_marshal_read_info(
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.
* @return NULL if the format provides no marshalling or an error occurred. */
MPMarshalledUser *mpw_marshal_read(
const char *in, const MPMarshalFormat inFormat, MPMasterKeyProvider masterKeyProvider, MPMarshalError *error);
//// Utilities.
/** Create a new user object ready for marshalling. */
/** Create a new user object ready for marshalling.
* @return NULL if the fullName or masterKeyProvider is missing, or the marshalled user couldn't be allocated. */
MPMarshalledUser *mpw_marshal_user(
const char *fullName, MPMasterKeyProvider masterKeyProvider, 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.
* @return NULL if the siteName is missing, or the marshalled site couldn't be allocated. */
MPMarshalledSite *mpw_marshal_site(
MPMarshalledUser *user,
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.
* @return NULL if the marshalled question couldn't be allocated. */
MPMarshalledQuestion *mpw_marshal_question(
MPMarshalledSite *site, const char *keyword);
/** Free the given user object and all associated data. */
@ -150,24 +154,24 @@ bool mpw_marshal_free(
//// Format.
/**
* @return The purpose represented by the given name.
* @return The purpose represented by the given name or ERR if the format was not recognized.
*/
const MPMarshalFormat mpw_format_named(
const char *formatName);
/**
* @return The standard name for the given purpose.
* @return The standard name for the given purpose or NULL if the format was not recognized.
*/
const char *mpw_format_name(
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 or NULL if the format was not recognized.
*/
const char *mpw_marshal_format_extension(
const MPMarshalFormat format);
/**
* @param extensions An array of filename extensions that are used for files of this format,
* the first being the currently preferred/output extension. Free after use.
* @return The amount of filename extensions returned in the array.
* @return The amount of filename extensions returned in the array or 0 if the format does not support marshalling or was not recognized.
*/
int mpw_marshal_format_extensions(
const MPMarshalFormat format, const char ***extensions);

View File

@ -310,7 +310,7 @@ const char *mpw_class_characters(char characterClass) {
const char mpw_class_character(char characterClass, uint8_t seedByte) {
const char *classCharacters = mpw_class_characters( characterClass );
if (!classCharacters)
if (!classCharacters || !strlen( classCharacters ))
return '\0';
return classCharacters[seedByte % strlen( classCharacters )];

View File

@ -131,32 +131,32 @@ typedef struct {
//// Type utilities.
/**
* @return The purpose represented by the given name.
* @return The purpose represented by the given name or ERR if the name does not represent a known purpose.
*/
const MPKeyPurpose mpw_purpose_named(const char *purposeName);
/**
* @return The standard name for the given purpose.
* @return The standard name for the given purpose or NULL if the purpose is not known.
*/
const char *mpw_purpose_name(MPKeyPurpose purpose);
/**
* @return An internal string containing the scope identifier to apply when encoding for the given purpose.
* @return An internal string containing the scope identifier to apply when encoding for the given purpose or NULL if the purpose is not known.
*/
const char *mpw_purpose_scope(MPKeyPurpose purpose);
/**
* @return The password type represented by the given name.
* @return The password type represented by the given name or ERR if the name does not represent a known type.
*/
const MPResultType mpw_type_named(const char *typeName);
/**
* @return The standard identifying name for the given password type.
* @return The standard identifying name for the given password type or NULL if the type is not known.
*/
const char *mpw_type_abbreviation(MPResultType resultType);
/**
* @return The standard identifying name for the given password type.
* @return The standard identifying name for the given password type or NULL if the type is not known.
*/
const char *mpw_type_short_name(MPResultType resultType);
/**
* @return The descriptive name for the given password type.
* @return The descriptive name for the given password type or NULL if the type is not known.
*/
const char *mpw_type_long_name(MPResultType resultType);
@ -165,20 +165,22 @@ const char *mpw_type_long_name(MPResultType resultType);
* The amount of elements in the array is stored in count.
* If an unsupported type is given, count will be 0 and will return NULL.
* The array needs to be free'ed, the strings themselves must not be free'ed or modified.
* NULL if the type is not known or is not a MPResultTypeClassTemplate.
*/
const char **mpw_type_templates(MPResultType type, size_t *count);
/**
* @return An internal string that contains the password encoding template of the given type
* for a seed that starts with the given byte.
* NULL if the type is not known or is not a MPResultTypeClassTemplate.
*/
const char *mpw_type_template(MPResultType type, uint8_t templateIndex);
/**
* @return An internal string that contains all the characters that occur in the given character class.
* @return An string literal with all the characters in the given character class or NULL if the character class is not known.
*/
const char *mpw_class_characters(char characterClass);
/**
* @return A character from given character class that encodes the given byte.
* @return A character from given character class that encodes the given byte or NUL if the character class is not known or is empty.
*/
const char mpw_class_character(char characterClass, uint8_t seedByte);

View File

@ -308,6 +308,8 @@ static uint8_t const *mpw_aes(bool encrypt, const uint8_t *key, const size_t key
memcpy( aesBuf, buf, *bufSize );
memset( aesBuf + *bufSize, aesSize - *bufSize, aesSize - *bufSize );
uint8_t *resultBuf = malloc( aesSize );
if (!resultBuf)
return NULL;
if (encrypt)
AES_CBC_encrypt_buffer( resultBuf, aesBuf, aesSize, key, iv );
@ -366,7 +368,7 @@ const char *mpw_hotp(const uint8_t *key, size_t keySize, uint64_t movingFactor,
MPKeyID mpw_id_buf(const void *buf, size_t length) {
if (!buf)
return "<unset>";
return NULL;
#if MPW_CPERCIVA
uint8_t hash[32];
@ -383,6 +385,9 @@ MPKeyID mpw_id_buf(const void *buf, size_t length) {
bool mpw_id_buf_equals(const char *id1, const char *id2) {
if (!id1 || !id2)
return !id1 && !id2;
size_t size = strlen( id1 );
if (size != strlen( id2 ))
return false;
@ -406,6 +411,9 @@ const char *mpw_str(const char *format, ...) {
const char *mpw_vstr(const char *format, va_list args) {
if (!format)
return NULL;
// TODO: We should find a way to get rid of this shared storage medium.
// TODO: Not thread-safe
static char *str_str;
@ -433,16 +441,20 @@ const char *mpw_vstr(const char *format, va_list args) {
const char *mpw_hex(const void *buf, size_t length) {
if (!buf || !length)
return NULL;
// TODO: We should find a way to get rid of this shared storage medium.
// TODO: Not thread-safe
static char **mpw_hex_buf;
static unsigned int mpw_hex_buf_i;
static size_t mpw_hex_buf_i;
if (!mpw_hex_buf && !(mpw_hex_buf = calloc( 10, sizeof( char * ) )))
return NULL;
if (!mpw_hex_buf)
mpw_hex_buf = calloc( 10, sizeof( char * ) );
mpw_hex_buf_i = (mpw_hex_buf_i + 1) % 10;
if (!mpw_realloc( &mpw_hex_buf[mpw_hex_buf_i], NULL, length * 2 + 1 ))
return NULL;
if (mpw_realloc( &mpw_hex_buf[mpw_hex_buf_i], NULL, length * 2 + 1 ))
for (size_t kH = 0; kH < length; kH++)
sprintf( &(mpw_hex_buf[mpw_hex_buf_i][kH * 2]), "%02X", ((const uint8_t *)buf)[kH] );
@ -484,7 +496,9 @@ const size_t mpw_utf8_strlen(const char *utf8String) {
size_t charlen = 0;
char *remainingString = (char *)utf8String;
for (int charByteSize; (charByteSize = mpw_utf8_sizeof( (unsigned char)*remainingString )); remainingString += charByteSize)
for (int charByteSize;
remainingString && (charByteSize = mpw_utf8_sizeof( (unsigned char)*remainingString ));
remainingString += charByteSize)
++charlen;
return charlen;

View File

@ -97,7 +97,8 @@ void mpw_uint16(const uint16_t number, uint8_t buf[2]);
void mpw_uint32(const uint32_t number, uint8_t buf[4]);
void mpw_uint64(const uint64_t number, uint8_t buf[8]);
/** Allocate a new array of _type, assign its element count to _count if not NULL and populate it with the varargs. */
/** Allocate a new array of _type, assign its element count to _count if not NULL and populate it with the varargs.
* @return NULL if no strings were given or we could not allocate space for the new array. */
const char **mpw_strings(
size_t *count, const char *strings, ...);
@ -142,7 +143,7 @@ bool mpw_push_int(
/* char** */strings, ...) \
({ __typeof__(strings) _s = strings; const char *__s = *_s; (void)__s; __mpw_free_strings( (char **)_s, __VA_ARGS__ ); })
/** Free a string after zero'ing its contents, then set the reference to the replacement string.
* The replacement string is generated before the original is freed; it may be a derivative of the original. */
* The replacement string is generated before the original is freed; so it can be a derivative of the original. */
#define mpw_replace_string(\
/* char* */string, /* char* */replacement) \
do { const char *replacement_ = replacement; mpw_free_string( &string ); string = replacement_; } while (0)
@ -174,25 +175,25 @@ void mpw_zero(
//// Cryptographic functions.
/** Derive a key from the given secret and salt using the scrypt KDF.
* @return A new keySize allocated buffer containing the key. */
* @return A new keySize allocated buffer containing the key or NULL if secret or salt is missing, key could not be allocated or the KDF failed. */
uint8_t const *mpw_kdf_scrypt(
const size_t keySize, const uint8_t *secret, const size_t secretSize, const uint8_t *salt, const size_t saltSize,
uint64_t N, uint32_t r, uint32_t p);
/** Derive a subkey from the given key using the blake2b KDF.
* @return A new keySize allocated buffer containing the key. */
* @return A new keySize allocated buffer containing the key or NULL if the key or subkeySize is missing, the key sizes are out of bounds, the subkey could not be allocated or derived. */
uint8_t const *mpw_kdf_blake2b(
const size_t subkeySize, const uint8_t *key, const size_t keySize,
const uint8_t *context, const size_t contextSize, const uint64_t id, const char *personal);
/** Calculate the MAC for the given message with the given key using SHA256-HMAC.
* @return A new 32-byte allocated buffer containing the MAC. */
* @return A new 32-byte allocated buffer containing the MAC or NULL if the key or message is missing, the MAC could not be allocated or generated. */
uint8_t const *mpw_hash_hmac_sha256(
const uint8_t *key, const size_t keySize, const uint8_t *salt, const size_t saltSize);
const uint8_t *key, const size_t keySize, const uint8_t *message, const size_t messageSize);
/** 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 or NULL if the key or buffer is missing, the key size is out of bounds or the result could not be allocated. */
uint8_t const *mpw_aes_encrypt(
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.
* @return A new bufSize allocated buffer containing the plainBuf. */
* @return A new bufSize allocated buffer containing the plainBuf or NULL if the key or buffer is missing, the key size is out of bounds or the result could not be allocated. */
uint8_t const *mpw_aes_decrypt(
const uint8_t *key, const size_t keySize, const uint8_t *cipherBuf, size_t *bufSize);
/** Calculate an OTP using RFC-4226.
@ -205,18 +206,18 @@ const char *mpw_hotp(
//// Visualizers.
/** Compose a formatted string.
* @return A C-string in a reused buffer, do not free or store it. */
* @return A C-string in a reused buffer, do not free or store it; or NULL if the format is missing or the result could not be allocated or formatted. */
const char *mpw_str(const char *format, ...);
const char *mpw_vstr(const char *format, va_list args);
/** Encode a buffer as a string of hexadecimal characters.
* @return A C-string in a reused buffer, do not free or store it. */
* @return A C-string in a reused buffer, do not free or store it; or NULL if the buffer is missing or the result could not be allocated. */
const char *mpw_hex(const void *buf, size_t length);
const char *mpw_hex_l(uint32_t number);
/** Encode a fingerprint for a buffer.
* @return A C-string in a reused buffer, do not free or store it. */
* @return A C-string in a reused buffer, do not free or store it; or NULL if the buffer is missing or the result could not be allocated. */
MPKeyID mpw_id_buf(const void *buf, size_t length);
/** Compare two fingerprints for equality.
* @return true if the buffers represent identical fingerprints. */
* @return true if the buffers represent identical fingerprints or are both NULL. */
bool mpw_id_buf_equals(const char *id1, const char *id2);
//// String utilities.