From f2f87471269e166fd5fe3424d730e14268437271 Mon Sep 17 00:00:00 2001 From: Maarten Billemont Date: Tue, 22 Aug 2017 18:18:24 -0400 Subject: [PATCH] Support for persisting login/question type & stateful types, null checking, cleanup and rewrite of CLI state. --- core/c/mpw-algorithm.c | 40 ++- core/c/mpw-algorithm.h | 10 +- core/c/mpw-marshall.c | 161 ++++++--- core/c/mpw-marshall.h | 6 +- platform-darwin/Source/MPAppDelegate_Store.m | 4 +- platform-independent/cli-c/cli/mpw-cli.c | 344 ++++++++++--------- 6 files changed, 337 insertions(+), 228 deletions(-) diff --git a/core/c/mpw-algorithm.c b/core/c/mpw-algorithm.c index 2964a538..76f62a3a 100644 --- a/core/c/mpw-algorithm.c +++ b/core/c/mpw-algorithm.c @@ -24,9 +24,14 @@ MPMasterKey mpw_masterKey(const char *fullName, const char *masterPassword, const MPAlgorithmVersion algorithmVersion) { + if (fullName && !strlen( fullName )) + fullName = NULL; + if (masterPassword && !strlen( masterPassword )) + masterPassword = NULL; + trc( "-- mpw_masterKey (algorithm: %u)\n", algorithmVersion ); trc( "fullName: %s\n", fullName ); - trc( "masterPassword.id: %s\n", mpw_id_buf( masterPassword, strlen( masterPassword ) ) ); + trc( "masterPassword.id: %s\n", masterPassword? mpw_id_buf( masterPassword, strlen( masterPassword ) ): NULL ); if (!fullName || !masterPassword) return NULL; @@ -49,6 +54,11 @@ MPSiteKey mpw_siteKey( MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter, const MPKeyPurpose keyPurpose, const char *keyContext, const MPAlgorithmVersion algorithmVersion) { + if (siteName && !strlen( siteName )) + siteName = NULL; + if (keyContext && !strlen( keyContext )) + keyContext = NULL; + trc( "-- mpw_siteKey (algorithm: %u)\n", algorithmVersion ); trc( "siteName: %s\n", siteName ); trc( "siteCounter: %d\n", siteCounter ); @@ -78,6 +88,13 @@ const char *mpw_siteResult( const MPResultType resultType, const char *resultParam, const MPAlgorithmVersion algorithmVersion) { + if (siteName && !strlen( siteName )) + siteName = NULL; + if (keyContext && !strlen( keyContext )) + keyContext = NULL; + if (resultParam && !strlen( resultParam )) + resultParam = NULL; + MPSiteKey siteKey = mpw_siteKey( masterKey, siteName, siteCounter, keyPurpose, keyContext, algorithmVersion ); if (!siteKey) return NULL; @@ -142,28 +159,35 @@ const char *mpw_siteResult( const char *mpw_siteState( MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter, const MPKeyPurpose keyPurpose, const char *keyContext, - const MPResultType resultType, const char *state, + const MPResultType resultType, const char *resultParam, const MPAlgorithmVersion algorithmVersion) { + if (siteName && !strlen( siteName )) + siteName = NULL; + if (keyContext && !strlen( keyContext )) + keyContext = NULL; + if (resultParam && !strlen( resultParam )) + resultParam = NULL; + MPSiteKey siteKey = mpw_siteKey_v0( masterKey, siteName, siteCounter, keyPurpose, keyContext ); if (!siteKey) return NULL; trc( "-- mpw_siteState (algorithm: %u)\n", algorithmVersion ); trc( "resultType: %d (%s)\n", resultType, mpw_nameForType( resultType ) ); - trc( "state: %s\n", state ); - if (!masterKey || !state) + trc( "resultParam: %s\n", resultParam ); + if (!masterKey || !resultParam) return NULL; switch (algorithmVersion) { case MPAlgorithmVersion0: - return mpw_siteState_v0( masterKey, siteKey, resultType, state ); + return mpw_siteState_v0( masterKey, siteKey, resultType, resultParam ); case MPAlgorithmVersion1: - return mpw_siteState_v1( masterKey, siteKey, resultType, state ); + return mpw_siteState_v1( masterKey, siteKey, resultType, resultParam ); case MPAlgorithmVersion2: - return mpw_siteState_v2( masterKey, siteKey, resultType, state ); + return mpw_siteState_v2( masterKey, siteKey, resultType, resultParam ); case MPAlgorithmVersion3: - return mpw_siteState_v3( masterKey, siteKey, resultType, state ); + return mpw_siteState_v3( masterKey, siteKey, resultType, resultParam ); default: err( "Unsupported version: %d\n", algorithmVersion ); return NULL; diff --git a/core/c/mpw-algorithm.h b/core/c/mpw-algorithm.h index 59ecd48e..811edcf7 100644 --- a/core/c/mpw-algorithm.h +++ b/core/c/mpw-algorithm.h @@ -48,7 +48,8 @@ MPSiteKey mpw_siteKey( MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter, const MPKeyPurpose keyPurpose, const char *keyContext, const MPAlgorithmVersion algorithmVersion); -/** Encode a password for the site from the given site key. +/** Generate a site result token from the given parameters. + * @param resultParam A parameter for the resultType. For stateful result types, the output of mpw_siteState. * @return A newly allocated string or NULL if an error occurred. */ const char *mpw_siteResult( MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter, @@ -56,12 +57,13 @@ const char *mpw_siteResult( const MPResultType resultType, const char *resultParam, const MPAlgorithmVersion algorithmVersion); -/** Perform symmetric encryption on a secret token's plainText. - * @return The newly allocated cipherText of the secret token encrypted by the masterKey. */ +/** Encrypt a stateful site token for persistence. + * @param resultParam A parameter for the resultType. For stateful result types, the desired mpw_siteResult. + * @return A newly allocated string or NULL if an error occurred. */ const char *mpw_siteState( MPMasterKey masterKey, const char *siteName, const MPCounterValue siteCounter, const MPKeyPurpose keyPurpose, const char *keyContext, - const MPResultType resultType, const char *state, + const MPResultType resultType, const char *resultParam, const MPAlgorithmVersion algorithmVersion); #endif // _MPW_ALGORITHM_H diff --git a/core/c/mpw-marshall.c b/core/c/mpw-marshall.c index 57c9ee30..cef5bcef 100644 --- a/core/c/mpw-marshall.c +++ b/core/c/mpw-marshall.c @@ -63,8 +63,8 @@ MPMarshalledSite *mpw_marshall_site( .counter = siteCounter, .algorithm = algorithmVersion, - .loginName = NULL, - .loginGenerated = false, + .loginContent = NULL, + .loginType = MPResultTypeTemplateName, .url = NULL, .uses = 0, @@ -79,12 +79,16 @@ MPMarshalledSite *mpw_marshall_site( MPMarshalledQuestion *mpw_marshal_question( MPMarshalledSite *site, const char *keyword) { - if (!keyword || !mpw_realloc( &site->questions, NULL, sizeof( MPMarshalledQuestion ) * ++site->questions_count )) + if (!mpw_realloc( &site->questions, NULL, sizeof( MPMarshalledQuestion ) * ++site->questions_count )) return NULL; + if (!keyword) + keyword = ""; MPMarshalledQuestion *question = &site->questions[site->questions_count - 1]; *question = (MPMarshalledQuestion){ .keyword = strdup( keyword ), + .content = NULL, + .type = MPResultTypeTemplatePhrase, }; return question; } @@ -113,9 +117,13 @@ bool mpw_marshal_free( for (size_t s = 0; s < user->sites_count; ++s) { MPMarshalledSite *site = &user->sites[s]; success &= mpw_free_string( site->name ); + success &= mpw_free_string( site->content ); + success &= mpw_free_string( site->loginContent ); + success &= mpw_free_string( site->url ); for (size_t q = 0; q < site->questions_count; ++q) { MPMarshalledQuestion *question = &site->questions[q]; success &= mpw_free_string( question->keyword ); + success &= mpw_free_string( question->content ); } success &= mpw_free( site->questions, sizeof( MPMarshalledQuestion ) * site->questions_count ); } @@ -177,7 +185,7 @@ static bool mpw_marshall_write_flat( if (!site->name || !strlen( site->name )) continue; - const char *content = NULL; + const char *content = NULL, *loginContent = NULL; if (!user->redacted) { // Clear Text if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, user->fullName, user->masterPassword )) { @@ -185,19 +193,25 @@ static bool mpw_marshall_write_flat( return false; } - if (site->type & MPResultTypeClassTemplate) - content = mpw_siteResult( masterKey, site->name, site->counter, - MPKeyPurposeAuthentication, NULL, site->type, site->content, site->algorithm ); + content = mpw_siteResult( masterKey, site->name, site->counter, + MPKeyPurposeAuthentication, NULL, site->type, site->content, site->algorithm ); + loginContent = mpw_siteResult( masterKey, site->name, MPCounterValueInitial, + MPKeyPurposeIdentification, NULL, site->loginType, site->loginContent, site->algorithm ); } - else if (site->type & MPSiteFeatureExportContent && site->content && strlen( site->content )) + else { // Redacted - content = strdup( site->content ); + if (site->type & MPSiteFeatureExportContent && site->content && strlen( site->content )) + content = strdup( site->content ); + if (site->loginType & MPSiteFeatureExportContent && site->loginContent && strlen( site->loginContent )) + loginContent = strdup( site->loginContent ); + } if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &site->lastUsed ) )) mpw_string_pushf( out, "%s %8ld %lu:%lu:%lu %25s\t%25s\t%s\n", dateString, (long)site->uses, (long)site->type, (long)site->algorithm, (long)site->counter, - site->loginName?: "", site->name, content?: "" ); + loginContent?: "", site->name, content?: "" ); mpw_free_string( content ); + mpw_free_string( loginContent ); } mpw_free( masterKey, MPMasterKeySize ); @@ -239,15 +253,15 @@ static bool mpw_marshall_write_json( // Section: "user" json_object *json_user = json_object_new_object(); json_object_object_add( json_file, "user", json_user ); - json_object_object_add( json_user, "avatar", json_object_new_int( (int)user->avatar ) ); + json_object_object_add( json_user, "avatar", json_object_new_int( (int32_t)user->avatar ) ); json_object_object_add( json_user, "full_name", json_object_new_string( user->fullName ) ); if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &user->lastUsed ) )) json_object_object_add( json_user, "last_used", json_object_new_string( dateString ) ); json_object_object_add( json_user, "key_id", json_object_new_string( mpw_id_buf( masterKey, MPMasterKeySize ) ) ); - json_object_object_add( json_user, "algorithm", json_object_new_int( (int)user->algorithm ) ); - json_object_object_add( json_user, "default_type", json_object_new_int( (int)user->defaultType ) ); + json_object_object_add( json_user, "algorithm", json_object_new_int( (int32_t)user->algorithm ) ); + json_object_object_add( json_user, "default_type", json_object_new_int( (int32_t)user->defaultType ) ); // Section "sites" json_object *json_sites = json_object_new_object(); @@ -257,7 +271,7 @@ static bool mpw_marshall_write_json( if (!site->name || !strlen( site->name )) continue; - const char *content = NULL; + const char *content = NULL, *loginContent = NULL; if (!user->redacted) { // Clear Text if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, user->fullName, user->masterPassword )) { @@ -265,26 +279,31 @@ static bool mpw_marshall_write_json( return false; } - if (site->type & MPResultTypeClassTemplate) - content = mpw_siteResult( masterKey, site->name, site->counter, - MPKeyPurposeAuthentication, NULL, site->type, site->content, site->algorithm ); + content = mpw_siteResult( masterKey, site->name, site->counter, + MPKeyPurposeAuthentication, NULL, site->type, site->content, site->algorithm ); + loginContent = mpw_siteResult( masterKey, site->name, MPCounterValueInitial, + MPKeyPurposeIdentification, NULL, site->loginType, site->loginContent, site->algorithm ); } - else if (site->type & MPSiteFeatureExportContent && site->content && strlen( site->content )) + else { // Redacted - content = strdup( site->content ); + if (site->type & MPSiteFeatureExportContent && site->content && strlen( site->content )) + content = strdup( site->content ); + if (site->loginType & MPSiteFeatureExportContent && site->loginContent && strlen( site->loginContent )) + loginContent = strdup( site->loginContent ); + } json_object *json_site = json_object_new_object(); json_object_object_add( json_sites, site->name, json_site ); - json_object_object_add( json_site, "type", json_object_new_int( (int)site->type ) ); - json_object_object_add( json_site, "counter", json_object_new_int( (int)site->counter ) ); - json_object_object_add( json_site, "algorithm", json_object_new_int( (int)site->algorithm ) ); + json_object_object_add( json_site, "type", json_object_new_int( (int32_t)site->type ) ); + json_object_object_add( json_site, "counter", json_object_new_int( (int32_t)site->counter ) ); + json_object_object_add( json_site, "algorithm", json_object_new_int( (int32_t)site->algorithm ) ); if (content) json_object_object_add( json_site, "password", json_object_new_string( content ) ); - if (site->loginName) - json_object_object_add( json_site, "login_name", json_object_new_string( site->loginName ) ); - json_object_object_add( json_site, "login_generated", json_object_new_boolean( site->loginGenerated ) ); + if (loginContent) + json_object_object_add( json_site, "login_name", json_object_new_string( loginContent ) ); + json_object_object_add( json_site, "login_type", json_object_new_int( (int32_t)site->loginType ) ); - json_object_object_add( json_site, "uses", json_object_new_int( (int)site->uses ) ); + json_object_object_add( json_site, "uses", json_object_new_int( (int32_t)site->uses ) ); if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &site->lastUsed ) )) json_object_object_add( json_site, "last_used", json_object_new_string( dateString ) ); @@ -297,13 +316,18 @@ static bool mpw_marshall_write_json( json_object *json_site_question = json_object_new_object(); json_object_object_add( json_site_questions, question->keyword, json_site_question ); + json_object_object_add( json_site_question, "type", json_object_new_int( (int32_t)question->type ) ); if (!user->redacted) { // Clear Text - const char *answer = mpw_siteResult( masterKey, site->name, MPCounterValueInitial, - MPKeyPurposeRecovery, question->keyword, MPResultTypeTemplatePhrase, NULL, site->algorithm ); - if (answer) - json_object_object_add( json_site_question, "answer", json_object_new_string( answer ) ); + const char *answerContent = mpw_siteResult( masterKey, site->name, MPCounterValueInitial, + MPKeyPurposeRecovery, question->keyword, question->type, question->content, site->algorithm ); + json_object_object_add( json_site_question, "answer", json_object_new_string( answerContent ) ); + } + else { + // Redacted + if (site->type & MPSiteFeatureExportContent && question->content && strlen( question->content )) + json_object_object_add( json_site_question, "answer", json_object_new_string( question->content ) ); } } @@ -567,23 +591,28 @@ static MPMarshalledUser *mpw_marshall_read_flat( return NULL; } - site->loginName = siteLoginName? strdup( siteLoginName ): NULL; site->uses = (unsigned int)atoi( str_uses ); site->lastUsed = siteLastUsed; - if (siteContent && strlen( siteContent )) { - if (!user->redacted) { - // Clear Text - if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, fullName, masterPassword )) { - *error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." }; - return NULL; - } + if (!user->redacted) { + // Clear Text + if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, fullName, masterPassword )) { + *error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." }; + return NULL; + } + if (siteContent && strlen( siteContent )) site->content = mpw_siteState( masterKey, site->name, site->counter, MPKeyPurposeAuthentication, NULL, site->type, siteContent, site->algorithm ); - } - else - // Redacted + if (siteLoginName && strlen( siteLoginName )) + site->loginContent = mpw_siteState( masterKey, site->name, MPCounterValueInitial, + MPKeyPurposeIdentification, NULL, site->loginType, siteLoginName, site->algorithm ); + } + else { + // Redacted + if (siteContent && strlen( siteContent )) site->content = strdup( siteContent ); + if (siteLoginName && strlen( siteLoginName )) + site->loginContent = strdup( siteLoginName ); } } else { @@ -732,7 +761,7 @@ static MPMarshalledUser *mpw_marshall_read_json( MPCounterValue siteCounter = (MPCounterValue)value; const char *siteContent = mpw_get_json_string( json_site.val, "password", NULL ); const char *siteLoginName = mpw_get_json_string( json_site.val, "login_name", NULL ); - bool siteLoginGenerated = mpw_get_json_boolean( json_site.val, "login_generated", false ); + MPResultType siteLoginType = (MPResultType)mpw_get_json_int( json_site.val, "login_type", MPResultTypeTemplateName ); unsigned int siteUses = (unsigned int)mpw_get_json_int( json_site.val, "uses", 0 ); str_lastUsed = mpw_get_json_string( json_site.val, "last_used", NULL ); time_t siteLastUsed = mpw_mktime( str_lastUsed ); @@ -750,31 +779,51 @@ static MPMarshalledUser *mpw_marshall_read_json( return NULL; } - site->loginName = siteLoginName? strdup( siteLoginName ): NULL; - site->loginGenerated = siteLoginGenerated; + site->loginType = siteLoginType; site->url = siteURL? strdup( siteURL ): NULL; site->uses = siteUses; site->lastUsed = siteLastUsed; - if (siteContent && strlen( siteContent )) { - if (!user->redacted) { - // Clear Text - if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, fullName, masterPassword )) { - *error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." }; - return NULL; - } + if (!user->redacted) { + // Clear Text + if (!mpw_update_masterKey( &masterKey, &masterKeyAlgorithm, site->algorithm, fullName, masterPassword )) { + *error = (MPMarshallError){ MPMarshallErrorInternal, "Couldn't derive master key." }; + return NULL; + } + if (siteContent && strlen( siteContent )) site->content = mpw_siteState( masterKey, site->name, site->counter, MPKeyPurposeAuthentication, NULL, site->type, siteContent, site->algorithm ); - } - else - // Redacted + if (siteLoginName && strlen( siteLoginName )) + site->loginContent = mpw_siteState( masterKey, site->name, MPCounterValueInitial, + MPKeyPurposeIdentification, NULL, site->loginType, siteLoginName, site->algorithm ); + } + else { + // Redacted + if (siteContent && strlen( siteContent )) site->content = strdup( siteContent ); + if (siteLoginName && strlen( siteLoginName )) + site->loginContent = strdup( siteLoginName ); } json_object_iter json_site_question; json_object *json_site_questions = mpw_get_json_section( json_site.val, "questions" ); - json_object_object_foreachC( json_site_questions, json_site_question ) - mpw_marshal_question( site, json_site_question.key ); + json_object_object_foreachC( json_site_questions, json_site_question ) { + MPMarshalledQuestion *question = mpw_marshal_question( site, json_site_question.key ); + const char *answerContent = mpw_get_json_string( json_site_question.val, "answer", NULL ); + question->type = (MPResultType)mpw_get_json_int( json_site_question.val, "type", MPResultTypeTemplatePhrase ); + + if (!user->redacted) { + // Clear Text + if (answerContent && strlen( answerContent )) + question->content = mpw_siteState( masterKey, site->name, MPCounterValueInitial, + MPKeyPurposeRecovery, question->keyword, question->type, answerContent, site->algorithm ); + } + else { + // Redacted + if (answerContent && strlen( answerContent )) + question->content = strdup( answerContent ); + } + } } json_object_put( json_file ); diff --git a/core/c/mpw-marshall.h b/core/c/mpw-marshall.h index c6233c84..6de5369d 100644 --- a/core/c/mpw-marshall.h +++ b/core/c/mpw-marshall.h @@ -59,6 +59,8 @@ typedef struct MPMarshallError { typedef struct MPMarshalledQuestion { const char *keyword; + const char *content; + MPResultType type; } MPMarshalledQuestion; typedef struct MPMarshalledSite { @@ -68,8 +70,8 @@ typedef struct MPMarshalledSite { MPCounterValue counter; MPAlgorithmVersion algorithm; - const char *loginName; - bool loginGenerated; + const char *loginContent; + MPResultType loginType; const char *url; unsigned int uses; diff --git a/platform-darwin/Source/MPAppDelegate_Store.m b/platform-darwin/Source/MPAppDelegate_Store.m index d7a66c18..c15240cc 100644 --- a/platform-darwin/Source/MPAppDelegate_Store.m +++ b/platform-darwin/Source/MPAppDelegate_Store.m @@ -691,7 +691,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted ); if ([site isKindOfClass:[MPGeneratedSiteEntity class]]) ((MPGeneratedSiteEntity *)site).counter = importSite->counter; site.algorithm = MPAlgorithmForVersion( importSite->algorithm ); - site.loginName = importSite->loginName? @(importSite->loginName): nil; + site.loginName = importSite->loginContent? @(importSite->loginContent): nil; site.loginGenerated = importSite->loginGenerated; site.url = importSite->url? @(importSite->url): nil; site.uses = importSite->uses; @@ -724,7 +724,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted ); MPMarshalledSite *exportSite = mpw_marshall_site( exportUser, site.name.UTF8String, site.type, counter, site.algorithm.version ); exportSite->content = content.UTF8String; - exportSite->loginName = site.loginName.UTF8String; + exportSite->loginContent = site.loginName.UTF8String; exportSite->loginGenerated = site.loginGenerated; exportSite->url = site.url.UTF8String; exportSite->uses = (unsigned int)site.uses; diff --git a/platform-independent/cli-c/cli/mpw-cli.c b/platform-independent/cli-c/cli/mpw-cli.c index 0552445e..715795ce 100644 --- a/platform-independent/cli-c/cli/mpw-cli.c +++ b/platform-independent/cli-c/cli/mpw-cli.c @@ -159,14 +159,10 @@ static char *mpw_path(const char *prefix, const char *extension) { int main(int argc, char *const argv[]) { - // Master Password defaults. - const char *fullName = NULL, *masterPassword = NULL, *siteName = NULL, *resultParam = NULL, *keyContext = NULL; - MPCounterValue siteCounter = MPCounterValueDefault; - MPResultType resultType = MPResultTypeDefault; - MPKeyPurpose keyPurpose = MPKeyPurposeAuthentication; - MPAlgorithmVersion algorithmVersion = MPAlgorithmVersionCurrent; + // CLI defaults. MPMarshallFormat sitesFormat = MPMarshallFormatDefault; - bool allowPasswordUpdate = false, sitesFormatFixed = false, sitesRedacted = true; + const char *fullName = NULL, *masterPassword = NULL, *siteName = NULL; + bool allowPasswordUpdate = false, sitesFormatFixed = false; // Read the environment. const char *fullNameArg = NULL, *masterPasswordArg = NULL, *siteNameArg = NULL; @@ -257,14 +253,14 @@ int main(int argc, char *const argv[]) { ftl( "Missing full name.\n" ); return EX_DATAERR; } + if (!(masterPasswordArg && (masterPassword = strdup( masterPasswordArg )))) + while (!masterPassword || !strlen( masterPassword )) + masterPassword = mpw_getpass( "Your master password: " ); if (!(siteNameArg && (siteName = strdup( siteNameArg ))) && !(siteName = mpw_getline( "Site name:" ))) { ftl( "Missing site name.\n" ); return EX_DATAERR; } - if (!(masterPasswordArg && (masterPassword = strdup( masterPasswordArg )))) - while (!masterPassword || !strlen( masterPassword )) - masterPassword = mpw_getpass( "Your master password: " ); if (sitesFormatArg) { sitesFormat = mpw_formatWithName( sitesFormatArg ); if (ERR == (int)sitesFormat) { @@ -281,7 +277,7 @@ int main(int argc, char *const argv[]) { // Try to fall back to the flat format. if (!sitesFormatFixed) { - free( sitesPath ); + mpw_free_string( sitesPath ); sitesPath = mpw_path( fullName, mpw_marshall_format_extension( MPMarshallFormatFlat ) ); if (sitesPath && (sitesFile = fopen( sitesPath, "r" ))) sitesFormat = MPMarshallFormatFlat; @@ -290,20 +286,21 @@ int main(int argc, char *const argv[]) { } } - // Read the user's sites file. + // Load the user object from file. MPMarshalledUser *user = NULL; MPMarshalledSite *site = NULL; + MPMarshalledQuestion *question = NULL; if (!sitesFile) { - free( sitesPath ); + mpw_free_string( sitesPath ); sitesPath = NULL; } else { // Read file. - size_t readAmount = 4096, bufSize = 0, bufOffset = 0, readSize = 0; + size_t blockSize = 4096, bufSize = 0, bufOffset = 0, readSize = 0; char *sitesInputData = NULL; - while ((mpw_realloc( &sitesInputData, &bufSize, readAmount )) && - (bufOffset += (readSize = fread( sitesInputData + bufOffset, 1, readAmount, sitesFile ))) && - (readSize == readAmount)); + while ((mpw_realloc( &sitesInputData, &bufSize, blockSize )) && + (bufOffset += (readSize = fread( sitesInputData + bufOffset, 1, blockSize, sitesFile ))) && + (readSize == blockSize)); if (ferror( sitesFile )) wrn( "Error while reading configuration file:\n %s: %d\n", sitesPath, ferror( sitesFile ) ); fclose( sitesFile ); @@ -312,6 +309,7 @@ int main(int argc, char *const argv[]) { MPMarshallInfo *sitesInputInfo = mpw_marshall_read_info( sitesInputData ); MPMarshallFormat sitesInputFormat = sitesFormatArg? sitesFormat: sitesInputInfo->format; MPMarshallError marshallError = { .type = MPMarshallSuccess }; + mpw_marshal_info_free( sitesInputInfo ); user = mpw_marshall_read( sitesInputData, sitesInputFormat, masterPassword, &marshallError ); if (marshallError.type == MPMarshallErrorMasterPassword) { // Incorrect master password. @@ -319,7 +317,7 @@ int main(int argc, char *const argv[]) { ftl( "Incorrect master password according to configuration:\n %s: %s\n", sitesPath, marshallError.description ); mpw_marshal_free( user ); mpw_free( sitesInputData, bufSize ); - free( sitesPath ); + mpw_free_string( sitesPath ); return EX_DATAERR; } @@ -334,6 +332,7 @@ int main(int argc, char *const argv[]) { mpw_marshal_free( user ); user = mpw_marshall_read( sitesInputData, sitesInputFormat, importMasterPassword, &marshallError ); + mpw_free_string( importMasterPassword ); } if (user) { mpw_free_string( user->masterPassword ); @@ -345,57 +344,30 @@ int main(int argc, char *const argv[]) { err( "Couldn't parse configuration file:\n %s: %s\n", sitesPath, marshallError.description ); mpw_marshal_free( user ); user = NULL; - free( sitesPath ); + mpw_free_string( sitesPath ); sitesPath = NULL; } - - if (user) { - // Load defaults. - mpw_free_string( fullName ); - mpw_free_string( masterPassword ); - fullName = strdup( user->fullName ); - masterPassword = strdup( user->masterPassword ); - algorithmVersion = user->algorithm; - resultType = user->defaultType; - sitesRedacted = user->redacted; - - if (!sitesRedacted && !sitesRedactedArg) - wrn( "Sites configuration is not redacted. Use -R 1 to change this.\n" ); - - for (size_t s = 0; s < user->sites_count; ++s) { - site = &user->sites[s]; - if (strcmp( siteName, site->name ) != 0) { - site = NULL; - continue; - } - - resultType = site->type; - siteCounter = site->counter; - algorithmVersion = site->algorithm; - break; - } - } } + if (!user) + user = mpw_marshall_user( fullName, masterPassword, MPAlgorithmVersionCurrent ); + mpw_free_string( fullName ); + mpw_free_string( masterPassword ); - // Parse default/config-overriding command-line parameters. - if (sitesRedactedArg) - sitesRedacted = strcmp( sitesRedactedArg, "1" ) == 0; - if (siteCounterArg) { - long long int siteCounterInt = atoll( siteCounterArg ); - if (siteCounterInt < MPCounterValueFirst || siteCounterInt > MPCounterValueLast) { - ftl( "Invalid site counter: %s\n", siteCounterArg ); - return EX_USAGE; + // Load the site object. + for (size_t s = 0; s < user->sites_count; ++s) { + site = &user->sites[s]; + if (strcmp( siteName, site->name ) != 0) { + site = NULL; + continue; } - siteCounter = (MPCounterValue)siteCounterInt; - } - if (algorithmVersionArg) { - int algorithmVersionInt = atoi( algorithmVersionArg ); - if (algorithmVersionInt < MPAlgorithmVersionFirst || algorithmVersionInt > MPAlgorithmVersionLast) { - ftl( "Invalid algorithm version: %s\n", algorithmVersionArg ); - return EX_USAGE; - } - algorithmVersion = (MPAlgorithmVersion)algorithmVersionInt; + break; } + if (!site) + site = mpw_marshall_site( user, siteName, MPResultTypeDefault, MPCounterValueDefault, user->algorithm ); + mpw_free_string( siteName ); + + // Load the purpose and context / question object. + MPKeyPurpose keyPurpose = MPKeyPurposeAuthentication; if (keyPurposeArg) { keyPurpose = mpw_purposeWithName( keyPurposeArg ); if (ERR == (int)keyPurpose) { @@ -403,36 +375,116 @@ int main(int argc, char *const argv[]) { return EX_USAGE; } } - char *purposeResult = "password"; + const char *keyContext = NULL; + if (keyContextArg) { + keyContext = strdup( keyContextArg ); + + switch (keyPurpose) { + case MPKeyPurposeAuthentication: + // NOTE: keyContext is not persisted. + break; + case MPKeyPurposeIdentification: + // NOTE: keyContext is not persisted. + break; + case MPKeyPurposeRecovery: + for (size_t q = 0; q < site->questions_count; ++q) { + question = &site->questions[q]; + if (strcmp( keyContext, question->keyword ) != 0) { + question = NULL; + continue; + } + break; + } + break; + } + } + if (!question) + question = mpw_marshal_question( site, keyContext ); + + // Initialize purpose-specific operation parameters. + MPResultType resultType = MPResultTypeDefault; + MPCounterValue siteCounter = MPCounterValueDefault; + const char *purposeResult = NULL, *resultState = NULL; switch (keyPurpose) { - case MPKeyPurposeAuthentication: + case MPKeyPurposeAuthentication: { + purposeResult = "password"; + resultType = site->type; + resultState = strdup( site->content ); + siteCounter = site->counter; break; + } case MPKeyPurposeIdentification: { - resultType = MPResultTypeTemplateName; purposeResult = "login"; + resultType = site->loginType; + resultState = strdup( site->loginContent ); + siteCounter = MPCounterValueInitial; break; } case MPKeyPurposeRecovery: { - resultType = MPResultTypeTemplatePhrase; + mpw_free_string( keyContext ); purposeResult = "answer"; + keyContext = strdup( question->keyword ); + resultType = question->type; + resultState = strdup( question->content ); + siteCounter = MPCounterValueInitial; break; } } + + // Override operation parameters from command-line arguments. if (resultTypeArg) { resultType = mpw_typeWithName( resultTypeArg ); if (ERR == (int)resultType) { ftl( "Invalid type: %s\n", resultTypeArg ); return EX_USAGE; } + + if (!(resultType & MPSiteFeatureAlternative)) { + switch (keyPurpose) { + case MPKeyPurposeAuthentication: + site->type = resultType; + break; + case MPKeyPurposeIdentification: + site->loginType = resultType; + break; + case MPKeyPurposeRecovery: + question->type = resultType; + break; + } + } } - if (resultParamArg) { - mpw_free_string( resultParam ); + if (siteCounterArg) { + long long int siteCounterInt = atoll( siteCounterArg ); + if (siteCounterInt < MPCounterValueFirst || siteCounterInt > MPCounterValueLast) { + ftl( "Invalid site counter: %s\n", siteCounterArg ); + return EX_USAGE; + } + + switch (keyPurpose) { + case MPKeyPurposeAuthentication: + siteCounter = site->counter = (MPCounterValue)siteCounterInt; + break; + case MPKeyPurposeIdentification: + case MPKeyPurposeRecovery: + // NOTE: counter for login & question is not persisted. + break; + } + } + const char *resultParam = NULL; + if (resultParamArg) resultParam = strdup( resultParamArg ); + if (algorithmVersionArg) { + int algorithmVersionInt = atoi( algorithmVersionArg ); + if (algorithmVersionInt < MPAlgorithmVersionFirst || algorithmVersionInt > MPAlgorithmVersionLast) { + ftl( "Invalid algorithm version: %s\n", algorithmVersionArg ); + return EX_USAGE; + } + site->algorithm = (MPAlgorithmVersion)algorithmVersionInt; } - if (keyContextArg) { - mpw_free_string( keyContext ); - keyContext = strdup( keyContextArg ); - } + if (sitesRedactedArg) + user->redacted = strcmp( sitesRedactedArg, "1" ) == 0; + else if (!user->redacted) + wrn( "Sites configuration is not redacted. Use -R 1 to change this.\n" ); mpw_free_string( fullNameArg ); mpw_free_string( masterPasswordArg ); mpw_free_string( siteNameArg ); @@ -446,120 +498,100 @@ int main(int argc, char *const argv[]) { mpw_free_string( sitesRedactedArg ); // Operation summary. - const char *identicon = mpw_identicon( fullName, masterPassword ); + const char *identicon = mpw_identicon( user->fullName, user->masterPassword ); if (!identicon) wrn( "Couldn't determine identicon.\n" ); dbg( "-----------------\n" ); - dbg( "fullName : %s\n", fullName ); - trc( "masterPassword : %s\n", masterPassword ); + dbg( "fullName : %s\n", user->fullName ); + trc( "masterPassword : %s\n", user->masterPassword ); dbg( "identicon : %s\n", identicon ); dbg( "sitesFormat : %s%s\n", mpw_nameForFormat( sitesFormat ), sitesFormatFixed? " (fixed)": "" ); dbg( "sitesPath : %s\n", sitesPath ); - dbg( "siteName : %s\n", siteName ); + dbg( "siteName : %s\n", site->name ); dbg( "siteCounter : %u\n", siteCounter ); dbg( "resultType : %s (%u)\n", mpw_nameForType( resultType ), resultType ); dbg( "resultParam : %s\n", resultParam ); dbg( "keyPurpose : %s (%u)\n", mpw_nameForPurpose( keyPurpose ), keyPurpose ); dbg( "keyContext : %s\n", keyContext ); - dbg( "algorithmVersion : %u\n", algorithmVersion ); + dbg( "algorithmVersion : %u\n", site->algorithm ); dbg( "-----------------\n\n" ); - inf( "%s's %s for %s:\n[ %s ]: ", fullName, purposeResult, siteName, identicon ); + inf( "%s's %s for %s:\n[ %s ]: ", user->fullName, purposeResult, site->name, identicon ); mpw_free_string( identicon ); if (sitesPath) - free( sitesPath ); + mpw_free_string( sitesPath ); // Determine master key. MPMasterKey masterKey = mpw_masterKey( - fullName, masterPassword, algorithmVersion ); - mpw_free_string( masterPassword ); - mpw_free_string( fullName ); + user->fullName, user->masterPassword, site->algorithm ); if (!masterKey) { ftl( "Couldn't derive master key.\n" ); return EX_SOFTWARE; } - // Output the result. - if (keyPurpose == MPKeyPurposeIdentification && site && (resultParam || (!site->loginGenerated && site->loginName))) { - if (resultParam) { - mpw_free_string( site->loginName ); - site->loginGenerated = false; - site->loginName = strdup( resultParam ); - } - else if (resultTypeArg) - // TODO: We're not persisting the resultType of the generated login - site->loginGenerated = true; - - fprintf( stdout, "%s\n", site->loginName ); - } - - else if (resultParam && site && resultType & MPResultTypeClassStateful) { - mpw_free_string( site->content ); - if (!(site->content = mpw_siteState( masterKey, siteName, siteCounter, - keyPurpose, keyContext, resultType, resultParam, algorithmVersion ))) { - ftl( "Couldn't encrypt site content.\n" ); + // Update state. + if (resultParam && resultType & MPResultTypeClassStateful) { + if (!(resultState = mpw_siteState( masterKey, site->name, siteCounter, + keyPurpose, keyContext, resultType, resultParam, site->algorithm ))) { + ftl( "Couldn't encrypt site result.\n" ); mpw_free( masterKey, MPMasterKeySize ); return EX_SOFTWARE; } + inf( "(state) %s => ", resultState ); - fprintf( stdout, "%s\n", site->content ); - } - else { - if (!resultParam && site && site->content && resultType & MPResultTypeClassStateful) - resultParam = strdup( site->content ); - const char *siteResult = mpw_siteResult( masterKey, siteName, siteCounter, - keyPurpose, keyContext, resultType, resultParam, algorithmVersion ); - if (!siteResult) { - ftl( "Couldn't generate site result.\n" ); - mpw_free( masterKey, MPMasterKeySize ); - return EX_SOFTWARE; - } - - fprintf( stdout, "%s\n", siteResult ); - mpw_free_string( siteResult ); - } - if (site && site->url) - inf( "See: %s\n", site->url ); - mpw_free( masterKey, MPMasterKeySize ); - mpw_free_string( siteName ); - mpw_free_string( resultParam ); - mpw_free_string( keyContext ); - - // Update the mpsites file. - if (user) { - // TODO: Move this up above the summary and replace the mpw lvars by user/site accessors. - if (keyPurpose == MPKeyPurposeAuthentication && !(resultType & MPSiteFeatureAlternative)) { - if (!site) - site = mpw_marshall_site( user, siteName, resultType, siteCounter, algorithmVersion ); - else { - site->type = resultType; - site->counter = siteCounter; - site->algorithm = algorithmVersion; - } - } - else if (keyPurpose == MPKeyPurposeRecovery && site && keyContext) { - // TODO: We're not persisting the resultType of the recovery question - MPMarshalledQuestion *question = NULL; - for (size_t q = 0; q < site->questions_count; ++q) { - question = &site->questions[q]; - if (strcmp( keyContext, question->keyword ) != 0) { - question = NULL; - continue; - } + switch (keyPurpose) { + case MPKeyPurposeAuthentication: { + mpw_free_string( site->content ); + site->content = resultState; + break; + } + case MPKeyPurposeIdentification: { + mpw_free_string( site->loginContent ); + site->loginContent = resultState; + break; + } + + case MPKeyPurposeRecovery: { + mpw_free_string( question->content ); + question->content = resultState; break; } - if (!question) - mpw_marshal_question( site, keyContext ); - } - if (site) { - site->lastUsed = user->lastUsed = time( NULL ); - site->uses++; } + // resultParam is consumed. + mpw_free_string( resultParam ); + resultParam = NULL; + } + + // Second phase resultParam defaults to state. + if (!resultParam && resultState) + resultParam = strdup( resultState ); + mpw_free_string( resultState ); + + // Generate result. + const char *result = mpw_siteResult( masterKey, site->name, siteCounter, + keyPurpose, keyContext, resultType, resultParam, site->algorithm ); + if (!result) { + ftl( "Couldn't generate site result.\n" ); + return EX_SOFTWARE; + } + fprintf( stdout, "%s\n", result ); + if (site->url) + inf( "See: %s\n", site->url ); + mpw_free( masterKey, MPMasterKeySize ); + mpw_free_string( keyContext ); + mpw_free_string( resultParam ); + mpw_free_string( result ); + + // Update usage metadata. + site->lastUsed = user->lastUsed = time( NULL ); + site->uses++; + + // Update the mpsites file. + if (sitesFormat != MPMarshallFormatNone) { if (!sitesFormatFixed) sitesFormat = MPMarshallFormatDefault; - user->redacted = sitesRedacted; - sitesPath = mpw_path( user->fullName, mpw_marshall_format_extension( sitesFormat ) ); + dbg( "Updating: %s (%s)\n", sitesPath, mpw_nameForFormat( sitesFormat ) ); if (!sitesPath || !(sitesFile = fopen( sitesPath, "w" ))) wrn( "Couldn't create updated configuration file:\n %s: %s\n", sitesPath, strerror( errno ) ); @@ -576,9 +608,9 @@ int main(int argc, char *const argv[]) { mpw_free_string( buf ); fclose( sitesFile ); } - free( sitesPath ); - mpw_marshal_free( user ); + mpw_free_string( sitesPath ); } + mpw_marshal_free( user ); return 0; }