aboutsummaryrefslogtreecommitdiff
path: root/lpass.c
blob: 2193359324d7735da3d443c4f329ef3cda95021b (about) (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
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;
}