Support for persisting login/question type & stateful types, null checking, cleanup and rewrite of CLI state.
This commit is contained in:
parent
f83cdacab8
commit
f2f8747126
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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 );
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user