c: Implement the passgeny module.

This is the C implementation of the passgeny library.
This commit is contained in:
2021-11-14 09:16:34 +01:00
parent 67ec1180dd
commit f927eea9aa
5 changed files with 408 additions and 8 deletions

56
c/passgeny/inc/passgeny.h Normal file
View File

@ -0,0 +1,56 @@
#ifndef PASSGENY_H_INCLUDED
#define PASSGENY_H_INCLUDED
#include <stdint.h>
#include <stdbool.h>
typedef struct passgeny passgeny_t;
struct passgeny
{
uint8_t pg_master_hash[32]; /* SHA256 of the master password */
char *pg_pattern; /* Password pattern */
double pg_last_bits_total; /* Total bits that were available for generating the last password */
double pg_last_bits_used; /* Bits consumed when calculating the last password */
};
/*
* Initialize a new passgeny instance
*
* Note: The master password is hashed and remembered. The raw password is never
* stored in memory.
*/
bool passgeny_init(passgeny_t *passgeny, const char *master_password);
/*
* Destroy a passgeny instance
*/
void passgeny_fini(passgeny_t *passgeny);
/*
* Set the current pattern
*/
void passgeny_set_pattern(passgeny_t *passgeny, const char *pattern);
/*
* Generate a new passgeny password using `domain`, `username` and optional
* tokens.
*
* The password is stored in the `out` string, where `out_sz` represents the
* maximum size of the string including '\0'
*/
bool passgeny_generate(
passgeny_t *passgeny,
char *out,
size_t out_sz,
const char *domain,
const char *username,
const char * const toknes[],
int ntokens);
/*
* Dump some statistics
*/
void passgeny_dump_verbose(const passgeny_t *passgeny);
#endif /* PASSGENY_H_INCLUDED */

10
c/passgeny/meson.build Normal file
View File

@ -0,0 +1,10 @@
passgeny_inc = include_directories('inc')
passgeny_lib = static_library(
'passgeny',
['src/passgeny.c'],
include_directories : passgeny_inc,
dependencies: [ dependency('libcrypto'), dependency('libargon2'), phogen_dep, bhash_dep])
passgeny_dep = declare_dependency(link_with : passgeny_lib, include_directories : passgeny_inc)

341
c/passgeny/src/passgeny.c Normal file
View File

@ -0,0 +1,341 @@
#include <ctype.h>
#include <regex.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/sha.h>
#include <argon2.h>
#include "bhash.h"
#include "phogen.h"
#include "passgeny.h"
/*
* =========================================================================
* Passgeny default parameters and constants
* =========================================================================
*/
/*
* The default pattern used for password generation
*/
#define PASSGENY_DEFAULT_PATTERN "^6p^6p^6ps2p100d"
/*
* List of special characters (order is important)
*/
#define PASSGENY_SPECIAL "-./=_ "
/*
* Argon2 parameters
*/
#define PASSGENY_ARGON2_SALT "Passgeny v1"
#define PASSGENY_ARGON2_MEMORY_COST 8192
#define PASSGENY_ARGON2_TIME_COST 6
#define PASSGENY_ARGON2_PARALLEL 4
#define PASSGENY_ARGON2_HASH_LEN 128
/* Ensure that pg_master_hash is big enough to hold a SHA256 hash */
_Static_assert(
sizeof(((passgeny_t *)NULL)->pg_master_hash) == SHA256_DIGEST_LENGTH,
"pg_master_hash doesn't match SHA256_DIGEST_LENGTH");
static bool passgeny_patmatch(
const char **pstr,
const char *pattern,
char *out,
size_t out_len);
static void passgeny_gen_str(char *out, size_t out_sz, const char *pool, bhash_t *bh);
/*
* =========================================================================
* Public API
* =========================================================================
*/
bool passgeny_init(passgeny_t *passgeny, const char *master_password)
{
SHA256_CTX sha256;
memset(passgeny, 0, sizeof(*passgeny));
SHA256_Init(&sha256);
SHA256_Update(&sha256, master_password, strlen(master_password));
SHA256_Final(passgeny->pg_master_hash, &sha256);
passgeny->pg_pattern = strdup(PASSGENY_DEFAULT_PATTERN);
return true;
}
void passgeny_fini(passgeny_t *passgeny)
{
/* Just nuke the master hash from memory */
explicit_bzero(passgeny->pg_master_hash, sizeof(passgeny->pg_master_hash));
free(passgeny->pg_pattern);
}
void passgeny_set_pattern(passgeny_t *passgeny, const char *pattern)
{
free(passgeny->pg_pattern);
passgeny->pg_pattern = strdup(pattern);
}
bool passgeny_generate(
passgeny_t *passgeny,
char *out,
size_t out_sz,
const char *domain,
const char *username,
const char * const tokens[],
int ntokens)
{
char ahash[PASSGENY_ARGON2_HASH_LEN];
char popt[32];
bhash_t bh;
int ti;
int rc;
const char *ppat = passgeny->pg_pattern;
/*
* Generate the Argon2 hash
*/
/* Caculate the input length */
size_t in_len = 0;
in_len += strlen(domain) + 1;
in_len += strlen(username) + 1;
in_len += sizeof(passgeny->pg_master_hash);
for (ti = 0; ti < ntokens; ti++)
{
in_len += strlen(tokens[ti]);
}
char in[in_len];
char *pin = in;
strcpy(pin, domain);
pin += strlen(domain) + 1;
strcpy(pin, username);
pin += strlen(username) + 1;
for (ti = 0; ti < ntokens; ti++)
{
strcpy(pin, tokens[ti]);
pin += strlen(tokens[ti]) + 1;
}
memcpy(pin, passgeny->pg_master_hash, sizeof(passgeny->pg_master_hash));
pin += sizeof(passgeny->pg_master_hash);
rc = argon2id_hash_raw(
PASSGENY_ARGON2_TIME_COST,
PASSGENY_ARGON2_MEMORY_COST,
PASSGENY_ARGON2_PARALLEL,
in,
pin - in,
PASSGENY_ARGON2_SALT,
strlen(PASSGENY_ARGON2_SALT),
ahash,
sizeof(ahash));
if (rc != 0)
{
fprintf(stderr, "Error generating argon2 hash.");
return false;
}
bhash_init(&bh, ahash, sizeof(ahash));
/*
* Generate the password according to the currently set pattern
*/
char cword[256];
char cflags = '\0';
while (*ppat != 0)
{
if (passgeny_patmatch(&ppat, "\\^", popt, sizeof(popt)))
{
cflags = '^';
continue;
}
else if (passgeny_patmatch(&ppat, "([0-9]*)p", popt, sizeof(popt)))
{
size_t clen = atoi(popt);
if (clen >= sizeof(cword))
{
fprintf(stderr, "Invalid phogen length: %zd\n", clen);
return false;
}
else if (clen == 0)
{
clen = 1;
}
phogen_encode(cword, clen + 1, &bh);
}
else if (passgeny_patmatch(&ppat, "([0-9]*)s", popt, sizeof(popt)))
{
size_t clen = atoi(popt);
if (clen >= sizeof(cword))
{
fprintf(stderr, "Invalid special string length: %zd\n", clen);
return false;
}
else if (clen == 0)
{
clen = 1;
}
passgeny_gen_str(cword, clen + 1, PASSGENY_SPECIAL, &bh);
}
else if (passgeny_patmatch(&ppat, "([0-9]*)d", popt, sizeof(popt)))
{
int id = atoi(popt);
if (id > INT_MAX)
{
fprintf(stderr, "Invalid decimal length: %d\n", id);
return false;
}
else if (id == 0)
{
id = 10;
}
snprintf(cword, sizeof(cword), "%d", bhash_mod32(&bh, id));
}
else if (passgeny_patmatch(&ppat, "([0-9]*)x", popt, sizeof(popt)))
{
size_t clen = atoi(popt);
if (clen >= sizeof(cword))
{
fprintf(stderr, "Invalid hex string length: %zd\n", clen);
return false;
}
else if (clen == 0)
{
clen = 1;
}
passgeny_gen_str(cword, clen + 1, "0123456789abcdef", &bh);
}
else
{
printf("Error parsing pattern at: %s\n", ppat);
return false;
}
if (cflags == '^')
{
cword[0] = toupper(cword[0]);
}
/* Reset flags */
cflags = '\0';
/* Store cword to out */
size_t clen = strlen(cword);
if (clen >= out_sz)
{
fprintf(stderr, "passgeny: Output string buffer is too short.\n");
return false;
}
memcpy(out, cword, clen);
out += clen;
out_sz -= clen;
}
*out = '\0';
passgeny->pg_last_bits_total = bhash_bits_avail(&bh);
passgeny->pg_last_bits_used = bhash_bits_used(&bh);
return true;
}
void passgeny_dump_verbose(const passgeny_t *pg)
{
fprintf(stderr, "Total hash size is %2.0f bits.\n", pg->pg_last_bits_total);
fprintf(stderr, "Used bits when generating the password: %0.2f.\n", pg->pg_last_bits_used);
}
/*
* =========================================================================
* Private functions
* =========================================================================
*/
/*
* Perform a pattern match on string `pstr`.
*
* If there's no match, this function returns false.
*
* If there's a match, pstr will be moved forward (past the pattern) and `opt`
* will be filled with the first group match in regex.
*
* The value stored in `opt` will be truncated if its too long.
*/
static bool passgeny_patmatch(
const char **pstr,
const char *pattern,
char *opt,
size_t opt_sz)
{
regmatch_t rm[2];
regex_t re;
bool retval = false;
if (regcomp(&re, pattern, REG_EXTENDED) != 0)
{
return false;
}
if (regexec(&re, *pstr, sizeof(rm) / sizeof(rm[0]), rm, 0) == REG_NOMATCH)
{
goto exit;
}
/* All matches must start at the beginning of the string */
if (rm[0].rm_so != 0) goto exit;
if (opt != NULL && rm[1].rm_so >= 0 && rm[1].rm_eo >= 0)
{
size_t ropt_sz = rm[1].rm_eo - rm[1].rm_so + 1;
if (ropt_sz > opt_sz) ropt_sz = opt_sz;
memcpy(opt, *pstr + rm[1].rm_so, ropt_sz - 1);
opt[ropt_sz - 1] = '\0';
}
*pstr += rm[0].rm_eo;
retval = true;
exit:
regfree(&re);
return retval;
}
/*
* Generate a string by using the bhash and a pool of characters
*/
static void passgeny_gen_str(char *out, size_t out_sz, const char *pool, bhash_t *bh)
{
int ii;
size_t psz;
if (out_sz == 0) return;
psz = strlen(pool);
for (ii = 0; ii < (out_sz - 1); ii++)
{
out[ii] = pool[bhash_mod32(bh, psz)];
}
out[ii] = '\0';
}