2
0

Support for persisting login/question type & stateful types, null checking, cleanup and rewrite of CLI state.

This commit is contained in:
Maarten Billemont 2017-08-22 18:18:24 -04:00
parent f83cdacab8
commit f2f8747126
6 changed files with 337 additions and 228 deletions

View File

@ -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;

View File

@ -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

View File

@ -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 );
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
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 );
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
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,10 +591,8 @@ 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 )) {
@ -578,12 +600,19 @@ static MPMarshalledUser *mpw_marshall_read_flat(
return NULL;
}
if (siteContent && strlen( siteContent ))
site->content = mpw_siteState( masterKey, site->name, site->counter,
MPKeyPurposeAuthentication, NULL, site->type, siteContent, site->algorithm );
if (siteLoginName && strlen( siteLoginName ))
site->loginContent = mpw_siteState( masterKey, site->name, MPCounterValueInitial,
MPKeyPurposeIdentification, NULL, site->loginType, siteLoginName, site->algorithm );
}
else
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,12 +779,10 @@ 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 )) {
@ -763,18 +790,40 @@ static MPMarshalledUser *mpw_marshall_read_json(
return NULL;
}
if (siteContent && strlen( siteContent ))
site->content = mpw_siteState( masterKey, site->name, site->counter,
MPKeyPurposeAuthentication, NULL, site->type, siteContent, site->algorithm );
if (siteLoginName && strlen( siteLoginName ))
site->loginContent = mpw_siteState( masterKey, site->name, MPCounterValueInitial,
MPKeyPurposeIdentification, NULL, site->loginType, siteLoginName, site->algorithm );
}
else
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 );

View File

@ -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;

View File

@ -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;

View File

@ -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.
}
if (!user)
user = mpw_marshall_user( fullName, masterPassword, MPAlgorithmVersionCurrent );
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" );
// 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;
}
resultType = site->type;
siteCounter = site->counter;
algorithmVersion = site->algorithm;
break;
}
}
}
if (!site)
site = mpw_marshall_site( user, siteName, MPResultTypeDefault, MPCounterValueDefault, user->algorithm );
mpw_free_string( siteName );
// 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;
}
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;
}
// 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: {
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;
}
if (keyContextArg) {
mpw_free_string( keyContext );
keyContext = strdup( keyContextArg );
site->algorithm = (MPAlgorithmVersion)algorithmVersionInt;
}
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 );
// 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;
}
else if (resultTypeArg)
// TODO: We're not persisting the resultType of the generated login
site->loginGenerated = true;
inf( "(state) %s => ", resultState );
fprintf( stdout, "%s\n", site->loginName );
}
else if (resultParam && site && resultType & MPResultTypeClassStateful) {
switch (keyPurpose) {
case MPKeyPurposeAuthentication: {
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" );
mpw_free( masterKey, MPMasterKeySize );
return EX_SOFTWARE;
}
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;
}
site->content = resultState;
break;
}
if (!question)
mpw_marshal_question( site, keyContext );
case MPKeyPurposeIdentification: {
mpw_free_string( site->loginContent );
site->loginContent = resultState;
break;
}
if (site) {
case MPKeyPurposeRecovery: {
mpw_free_string( question->content );
question->content = resultState;
break;
}
}
// 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;
}