aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSamuel Fadel <samuel@nihil.ws>2023-04-11 10:36:09 +0200
committerSamuel Fadel <samuel@nihil.ws>2023-04-11 10:36:09 +0200
commit33936a05ac14136491fd461e1c6beea0e5269161 (patch)
tree7eb96259473c582f79523f6bb09db9602195ad75
parent3eb0ae9b58683cc59468e3f5b2e28163a722e11b (diff)
Added LICENSE and implemented charset restrictions.0.1
As per my usage, lpass is now complete. * LICENSE: Added. * README.md: Updated with more information. * lpass.c: Implemented charset restrictions.
-rw-r--r--README.md18
-rw-r--r--lpass.c187
2 files changed, 169 insertions, 36 deletions
diff --git a/README.md b/README.md
index e385979..a17b169 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,19 @@
# lpass
+A simple implementation of
+[lesspass](https://github.com/lesspass/lesspass) in C.
-A simple implementation of [lesspass](https://github.com/lesspass/lesspass) in C.
+# Memory safety disclaimers
+This implementation only uses dynamic memory allocation as implemented
+within OpenSSL to handle big integers (`BIGNUM` in OpenSSL lingo). All
+allocations should be checked for success (non-`NULL`), please send a
+patch if you find that this is not the case. The same is true for the
+status of OpenSSL functions that return a status code.
+
+String manipulation is done only on statically-allocated strings,
+while also using the versions whose limits can be specified (e.g.,
+`snprintf` versus `sprintf` and `strncpy` versus `strcpy`).
+
+This code uses variable-length array declarations for simplicity.
+
+# Dependencies
+A recent version of OpenSSL.
diff --git a/lpass.c b/lpass.c
index 44f88a2..ef94412 100644
--- a/lpass.c
+++ b/lpass.c
@@ -1,3 +1,23 @@
+/*
+ * lpass.c
+ * lpass is a LessPass clone implemented in C.
+ *
+ * This file is part of lpass.
+ *
+ * lpass is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * lpass is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with lpass. If not, see <http://www.gnu.org/licenses/>.
+ */
+
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
@@ -17,19 +37,14 @@
#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);
-
enum CharSet {
- CHAR_SET_LOWER = 1 << 0,
- CHAR_SET_UPPER = 1 << 1,
- CHAR_SET_DIGITS = 1 << 2,
- CHAR_SET_SYMBOLS = 1 << 3,
+ CHARSET_LOWER = 1 << 0,
+ CHARSET_UPPER = 1 << 1,
+ CHARSET_DIGITS = 1 << 2,
+ CHARSET_SYMBOLS = 1 << 3,
};
-static BIGNUM *
+BIGNUM *
calc_entropy(const char *site,
const char *login,
uint64_t counter,
@@ -66,13 +81,28 @@ consume_entropy(char *generated_pass, BIGNUM *entropy, const char *charset, int
BN_CTX *ctx = BN_CTX_new();
BIGNUM *bn_charsetlen = BN_new();
BIGNUM *bn_remainder = BN_new();
+ if (ctx == NULL || bn_charsetlen == NULL || bn_remainder == NULL) {
+ retval = 0;
+ goto consume_entropy_cleanup;
+ }
+
int charsetlen = strlen(charset);
- BN_set_word(bn_charsetlen, charsetlen);
+ if (BN_set_word(bn_charsetlen, charsetlen) == 0) {
+ retval = 0;
+ goto consume_entropy_cleanup;
+ }
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` cannot store the value in `bn_remainder`, it
+ * will contain a very large number. Abort by checking if the
+ * remainder is too larger according to `passlen`, since that
+ * is also a failure case anyway.
+ */
+ uint64_t remainder = BN_get_word(bn_remainder);
if (remainder >= charsetlen) {
retval = 0;
goto consume_entropy_cleanup;
@@ -95,13 +125,29 @@ insert_str_pseudo_randomly(char *generated_pass, BIGNUM *entropy, const char *s)
BN_CTX *ctx = BN_CTX_new();
BIGNUM *bn_passlen = BN_new();
BIGNUM *bn_remainder = BN_new();
+ if (ctx == NULL || bn_passlen == NULL || bn_remainder == NULL) {
+ retval = 0;
+ goto insert_str_pseudo_randomly_cleanup;
+ }
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);
+
+ /*
+ * If `remainder` cannot store the value in `bn_remainder`, it
+ * will contain a very large number. Abort by checking if the
+ * remainder is too larger according to `passlen`, since that
+ * is also a failure case anyway.
+ */
uint64_t remainder = BN_get_word(bn_remainder);
+ if (remainder >= passlen) {
+ retval = 0;
+ goto insert_str_pseudo_randomly_cleanup;
+ }
+
/*
* 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`.
@@ -122,24 +168,69 @@ insert_str_pseudo_randomly_cleanup:
return retval;
}
-static void
-render_pass(BIGNUM *entropy, char *out, int length)
+static int
+charsets_has_set(uint8_t charsets, enum CharSet set)
+{
+ return (charsets & set) != 0;
+}
+
+static int
+build_charset(char *charset, uint8_t allowed_charsets)
+{
+ int c = 0;
+ charset[0] = '\0';
+ if (charsets_has_set(allowed_charsets, CHARSET_LOWER)) {
+ strcat(charset, CHAR_SUBSET_LOWER);
+ c++;
+ }
+ if (charsets_has_set(allowed_charsets, CHARSET_UPPER)) {
+ strcat(charset, CHAR_SUBSET_UPPER);
+ c++;
+ }
+ if (charsets_has_set(allowed_charsets, CHARSET_DIGITS)) {
+ strcat(charset, CHAR_SUBSET_DIGITS);
+ c++;
+ }
+ if (charsets_has_set(allowed_charsets, CHARSET_SYMBOLS)) {
+ strcat(charset, CHAR_SUBSET_SYMBOLS);
+ c++;
+ }
+ return c;
+}
+
+int
+render_pass(BIGNUM *entropy, uint8_t allowed_charsets, char *out, int length)
{
- /* XXX: FIXME: implement charset restrictions */
- int num_charsets = 4;
- consume_entropy(out, entropy, CHAR_SET, length - num_charsets);
+ char charset[MAX_BUF + 1];
+ int num_charsets = build_charset(charset, allowed_charsets);
+ if (consume_entropy(out, entropy, charset, length - num_charsets) == 0) {
+ return 0;
+ }
/*
* After generating the initial password, add one character of
- * each charset to ensure at least one is there.
+ * each charset to ensure at least one from that charset 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 c = 0;
+ if (charsets_has_set(allowed_charsets, CHARSET_LOWER)
+ && consume_entropy(&str_to_add[c++], entropy, CHAR_SUBSET_LOWER, 1) == 0) {
+ return 0;
+ }
+ if (charsets_has_set(allowed_charsets, CHARSET_UPPER)
+ && consume_entropy(&str_to_add[c++], entropy, CHAR_SUBSET_UPPER, 1) == 0) {
+ return 0;
+ }
+ if (charsets_has_set(allowed_charsets, CHARSET_DIGITS)
+ && consume_entropy(&str_to_add[c++], entropy, CHAR_SUBSET_DIGITS, 1) == 0) {
+ return 0;
+ }
+ if (charsets_has_set(allowed_charsets, CHARSET_SYMBOLS)
+ && consume_entropy(&str_to_add[c++], entropy, CHAR_SUBSET_SYMBOLS, 1) == 0) {
+ return 0;
+ }
+ return insert_str_pseudo_randomly(out, entropy, str_to_add);
}
static void
@@ -160,7 +251,11 @@ usage()
fputs("\nOptions:\n", stderr);
fputs(" -L LENGTH, --length LENGTH\n", stderr);
fputs(" password length (default: 16)\n", stderr);
- fputs(" --no-symbols no symbols in password\n", stderr);
+ fputs(" --no-lower no lowercase letters in password\n", stderr);
+ fputs(" --no-upper no uppercase letters in password\n", stderr);
+ fputs(" --no-digits no digits in password\n", stderr);
+ fputs(" --no-symbols no special symbols (" CHAR_SUBSET_SYMBOLS ")\n", stderr);
+ fputs(" -h, --help output this message and exit\n", stderr);
fputs("\nThe environment variable LESSPASS_MASTER_PASSWORD must be set with the master\n", stderr);
fputs("password for producing the desired password.\n", stderr);
}
@@ -168,35 +263,49 @@ usage()
int
main(int argc, char *argv[])
{
- const char *master_pass = getenv("LESSPASS_MASTER_PASSWORD");
- if (master_pass == NULL) {
- err("environment variable LESSPASS_MASTER_PASSWORD is not set");
- }
-
int c;
int length = DEFAULT_LENGTH;
- uint8_t charsets = 0;
+ uint8_t allowed_charsets = CHARSET_LOWER
+ | CHARSET_UPPER
+ | CHARSET_DIGITS
+ | CHARSET_SYMBOLS;
for (;;) {
int option_index = 0;
static struct option long_options[] = {
+ {"help", no_argument, 0, 'h'},
{"length", required_argument, 0, 'L'},
+ {"no-lower", no_argument, 0, 0 },
+ {"no-upper", no_argument, 0, 0 },
+ {"no-digits", no_argument, 0, 0 },
{"no-symbols", no_argument, 0, 0 },
{0, 0, 0, 0 }
};
- c = getopt_long(argc, argv, "L:", long_options, &option_index);
+ c = getopt_long(argc, argv, "hL:", long_options, &option_index);
if (c == -1) {
break;
}
switch (c) {
case 0:
- if (option_index == 1) {
- /* TODO: no-symbols */
+ if (strcmp("no-lower", long_options[option_index].name) == 0) {
+ allowed_charsets &= ~CHARSET_LOWER;
+ } else if (strcmp("no-upper", long_options[option_index].name) == 0) {
+ allowed_charsets &= ~CHARSET_UPPER;
+ } else if (strcmp("no-digits", long_options[option_index].name) == 0) {
+ allowed_charsets &= ~CHARSET_DIGITS;
+ } else if (strcmp("no-symbols", long_options[option_index].name) == 0) {
+ allowed_charsets &= ~CHARSET_SYMBOLS;
}
break;
+ case 'h':
+ usage();
+ exit(EXIT_SUCCESS);
case 'L':
- if (!optarg || sscanf(optarg, "%d", &length) == 0 || length < 1) {
+ if (!optarg
+ || sscanf(optarg, "%d", &length) == 0
+ || length < 1
+ || length >= MAX_BUF) {
usage();
exit(EXIT_FAILURE);
}
@@ -207,6 +316,11 @@ main(int argc, char *argv[])
}
}
+ const char *master_pass = getenv("LESSPASS_MASTER_PASSWORD");
+ if (master_pass == NULL) {
+ err("environment variable LESSPASS_MASTER_PASSWORD is not set");
+ }
+
/*
* We are still looking for 2 positional arguments, abort if they
* are not there.
@@ -227,7 +341,10 @@ main(int argc, char *argv[])
char generated_pass[length + 1];
memset(generated_pass, 0, sizeof(generated_pass));
- render_pass(entropy, generated_pass, length);
+ if (render_pass(entropy, allowed_charsets, generated_pass, length) == 0) {
+ BN_free(entropy);
+ err("Failed to generate password");
+ }
printf("%s\n", generated_pass);
BN_free(entropy);
return EXIT_SUCCESS;