aboutsummaryrefslogtreecommitdiff
path: root/lpass.c
diff options
context:
space:
mode:
authorSamuel Fadel <samuel@nihil.ws>2023-04-08 20:12:35 +0200
committerSamuel Fadel <samuel@nihil.ws>2023-04-08 20:12:35 +0200
commitd314d0cf4b92e1dd9c1f761498b06160dcf5e104 (patch)
tree68dcb1f3d236a7ec174862b0caf6e2e90a29b6cc /lpass.c
Initial commit.
An initial working implementation of the password generating algorithm of lesspass. Lacks any sort of documentation or usage info and simply prints the generated password on stdout. * lpass.c: Initial implementation of lesspass * Makefile: A just works version
Diffstat (limited to 'lpass.c')
-rw-r--r--lpass.c163
1 files changed, 163 insertions, 0 deletions
diff --git a/lpass.c b/lpass.c
new file mode 100644
index 0000000..2193359
--- /dev/null
+++ b/lpass.c
@@ -0,0 +1,163 @@
+#include <openssl/bn.h>
+#include <openssl/evp.h>
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#define MAX_BUF 1024
+#define ENTROPY_ITERATIONS 100000
+#define ENTROPY_KEY_LENGTH 32
+
+#define CHAR_SUBSET_LOWER "abcdefghijklmnopqrstuvwxyz"
+#define CHAR_SUBSET_UPPER "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+#define CHAR_SUBSET_DIGITS "0123456789"
+#define CHAR_SUBSET_SYMBOLS "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
+
+const char *CHAR_SET = (CHAR_SUBSET_LOWER
+ CHAR_SUBSET_UPPER
+ CHAR_SUBSET_DIGITS
+ CHAR_SUBSET_SYMBOLS);
+
+static BIGNUM *
+calc_entropy(const char *site,
+ const char *login,
+ uint64_t counter,
+ const char *master_pass,
+ int passlen)
+{
+ char salt[MAX_BUF + 1];
+ memset(salt, 0, sizeof(salt));
+ int saltlen = snprintf(salt, MAX_BUF, "%s%s%lx", site, login, counter);
+ if (saltlen > MAX_BUF) {
+ return 0;
+ }
+
+ unsigned char key[ENTROPY_KEY_LENGTH];
+ memset(key, 0, sizeof(key));
+ int status = PKCS5_PBKDF2_HMAC(master_pass, passlen,
+ (const unsigned char *) salt, saltlen,
+ ENTROPY_ITERATIONS,
+ EVP_sha256(),
+ ENTROPY_KEY_LENGTH,
+ key);
+ if (status == 0) {
+ return 0;
+ }
+
+ /* NULL as last arg to allocate a new BIGNUM */
+ return BN_bin2bn(key, ENTROPY_KEY_LENGTH, NULL);
+}
+
+static int
+consume_entropy(char *generated_pass, BIGNUM *entropy, const char *charset, int maxlen)
+{
+ int retval = 1;
+ BN_CTX *ctx = BN_CTX_new();
+ BIGNUM *bn_charsetlen = BN_new();
+ BIGNUM *bn_remainder = BN_new();
+ int charsetlen = strlen(charset);
+ BN_set_word(bn_charsetlen, charsetlen);
+ for (int passlen = strlen(generated_pass);
+ passlen < maxlen;
+ passlen++) {
+ BN_div(entropy, bn_remainder, entropy, bn_charsetlen, ctx);
+ int remainder = BN_get_word(bn_remainder);
+ if (remainder >= charsetlen) {
+ retval = 0;
+ goto consume_entropy_cleanup;
+ }
+ generated_pass[passlen] = charset[remainder];
+ }
+consume_entropy_cleanup:
+ BN_free(bn_remainder);
+ BN_free(bn_charsetlen);
+ BN_CTX_free(ctx);
+ return retval;
+}
+
+static int
+insert_str_pseudo_randomly(char *generated_pass, BIGNUM *entropy, const char *s)
+{
+ int retval = 1;
+ char buf[MAX_BUF + 1];
+ uint64_t passlen = (uint64_t) strlen(generated_pass);
+ BN_CTX *ctx = BN_CTX_new();
+ BIGNUM *bn_passlen = BN_new();
+ BIGNUM *bn_remainder = BN_new();
+ for (char c = *s; *s != '\0'; c = *(++s)) {
+ if (BN_set_word(bn_passlen, passlen) == 0) {
+ retval = 0;
+ goto insert_str_pseudo_randomly_cleanup;
+ }
+ BN_div(entropy, bn_remainder, entropy, bn_passlen, ctx);
+ uint64_t remainder = BN_get_word(bn_remainder);
+ /*
+ * Idea here is to add the char `c` at position `remainder` in `generated_pass`.
+ * 1. Copy the part that would need to be shifted into `buf`.
+ */
+ memset(buf, 0, sizeof(buf));
+ strncpy(buf, &generated_pass[remainder], passlen - remainder);
+ /*
+ * 2. Add new character, then copy `buf` back into `generated_pass`.
+ */
+ generated_pass[remainder] = c;
+ strncpy(&generated_pass[remainder + 1], buf, passlen - remainder);
+ passlen++;
+ }
+insert_str_pseudo_randomly_cleanup:
+ BN_free(bn_remainder);
+ BN_free(bn_passlen);
+ BN_CTX_free(ctx);
+ return retval;
+}
+
+static void
+render_pass(BIGNUM *entropy, char *out, int length)
+{
+ /* XXX: FIXME: implement charset restrictions */
+ int num_charsets = 4;
+ consume_entropy(out, entropy, CHAR_SET, length - num_charsets);
+
+ /*
+ * After generating the initial password, add one character of
+ * each charset to ensure at least one is there.
+ */
+ char str_to_add[num_charsets + 1];
+ memset(str_to_add, 0, sizeof(str_to_add));
+ consume_entropy(&str_to_add[0], entropy, CHAR_SUBSET_LOWER, 1);
+ consume_entropy(&str_to_add[1], entropy, CHAR_SUBSET_UPPER, 1);
+ consume_entropy(&str_to_add[2], entropy, CHAR_SUBSET_DIGITS, 1);
+ consume_entropy(&str_to_add[3], entropy, CHAR_SUBSET_SYMBOLS, 1);
+ insert_str_pseudo_randomly(out, entropy, str_to_add);
+}
+
+int
+main(int argc, char *argv[])
+{
+ if (argc != 5) {
+ return 1;
+ }
+
+ const char *master_pass = argv[1];
+ int passlen = strlen(master_pass);
+ const char *site = argv[2];
+ const char *login = argv[3];
+ int length = 0;
+ if (sscanf(argv[4], "%d", &length) == 0) {
+ return 1;
+ }
+
+ uint64_t counter = 1;
+ BIGNUM *entropy = calc_entropy(site, login, counter, master_pass, passlen);
+ if (entropy == NULL) {
+ return 1;
+ }
+
+ char generated_pass[length + 1];
+ memset(generated_pass, 0, sizeof(generated_pass));
+ render_pass(entropy, generated_pass, length);
+ printf("%s\n", generated_pass);
+ BN_free(entropy);
+ return 0;
+}