c: Implement the passgeny module.
This is the C implementation of the passgeny library.
This commit is contained in:
@ -1,8 +0,0 @@
|
|||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
#include "phogen.h"
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
return EXIT_SUCCESS;
|
|
||||||
}
|
|
||||||
@ -1,3 +1,4 @@
|
|||||||
subdir('bhash')
|
subdir('bhash')
|
||||||
subdir('phogen')
|
subdir('phogen')
|
||||||
|
subdir('passgeny')
|
||||||
subdir('cli')
|
subdir('cli')
|
||||||
|
|||||||
56
c/passgeny/inc/passgeny.h
Normal file
56
c/passgeny/inc/passgeny.h
Normal 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
10
c/passgeny/meson.build
Normal 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
341
c/passgeny/src/passgeny.c
Normal 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';
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user