]> begriffs open source - libpw/blob - src/pw.c
Switch to Meson build system
[libpw] / src / pw.c
1 #include "pw.h"
2
3 #include <stdio.h>
4 #include <stdlib.h>
5
6 #include <openssl/evp.h>
7 #include <pwd.h>
8 #include <unistd.h>
9
10 // Using bcrypt for now, but alternatives are emerging
11 // https://isopenbsdsecu.re/mitigations/password_hashing/
12 #define CRYPT_FLAVOR "bcrypt,a"
13
14 struct pw_context
15 {
16         sqlite3 *conn;
17         const char *dummy_hash;
18         sqlite3_stmt *upsert_stmt;
19         sqlite3_stmt *find_stmt;
20         sqlite3_stmt *token_insert_stmt;
21 };
22
23 pw_status
24 pw_init_context(sqlite3 *db, pw_context **out)
25 {
26         int rc, fk_enabled;
27         static char shared_dummy_hash[_PASSWORD_LEN];
28
29         pw_context *ctx = malloc(sizeof *ctx);
30         if(!ctx)
31                 return PW_NO_MEM;
32         *out = ctx;
33
34         rc = sqlite3_db_config(db,
35                 SQLITE_DBCONFIG_ENABLE_FKEY, 1, &fk_enabled
36         );
37         if (rc != SQLITE_OK || !fk_enabled)
38                 return PW_SQLITE_ERROR;
39
40         ctx->conn = db;
41
42         if (shared_dummy_hash[0] == '\0')
43         {
44                 rc = crypt_newhash(
45                         "bogus", CRYPT_FLAVOR,
46                         shared_dummy_hash, _PASSWORD_LEN);
47                 if (rc < 0)
48                         return PW_CRYPT_ERROR;
49         }
50         ctx->dummy_hash = shared_dummy_hash;
51
52         rc = sqlite3_prepare_v2(db,
53                 "INSERT INTO login (user, pass) VALUES (?1, ?2) "
54                 "ON CONFLICT (user) DO UPDATE SET pass = ?2",
55                 -1, &ctx->upsert_stmt, NULL);
56         if (rc != SQLITE_OK)
57                 return PW_SQLITE_ERROR;
58
59         rc = sqlite3_prepare_v2(db,
60                 "SELECT pass FROM login WHERE user = ?",
61                 -1, &ctx->find_stmt, NULL);
62         if (rc != SQLITE_OK)
63                 return PW_SQLITE_ERROR;
64
65         rc = sqlite3_prepare_v2(db,
66                 "INSERT INTO token (login_id, hash) "
67                 "SELECT id, ?2 "
68                 "FROM login "
69                 "WHERE user = ?1",
70                 -1, &ctx->token_insert_stmt, NULL);
71         if (rc != SQLITE_OK)
72                 return PW_SQLITE_ERROR;
73
74         return PW_OK;
75 }
76
77 pw_status
78 pw_set(pw_context *ctx, char *user, char *pass)
79 {
80         int rc;
81         rc = sqlite3_bind_text(ctx->upsert_stmt, 1, user, -1, SQLITE_TRANSIENT);
82         if (rc != SQLITE_OK)
83                 return PW_SQLITE_ERROR;
84
85         char hash[_PASSWORD_LEN];
86         if (crypt_newhash(pass, CRYPT_FLAVOR, hash, sizeof hash) < 0)
87                 return PW_CRYPT_ERROR;
88
89         rc = sqlite3_bind_text(ctx->upsert_stmt, 2, hash, -1, SQLITE_TRANSIENT);
90         if (rc != SQLITE_OK)
91                 return PW_SQLITE_ERROR;
92
93         rc = sqlite3_step(ctx->upsert_stmt);
94         sqlite3_reset(ctx->upsert_stmt);
95         return (rc == SQLITE_DONE && sqlite3_changes(ctx->conn) > 0)
96                 ? PW_OK : PW_SQLITE_ERROR;
97 }
98
99 pw_status
100 pw_check(pw_context *ctx, char *user, char *pass)
101 {
102         int rc;
103         rc = sqlite3_bind_text(ctx->find_stmt, 1, user, -1, SQLITE_TRANSIENT);
104         if (rc != SQLITE_OK)
105                 return PW_SQLITE_ERROR;
106
107         bool user_exists = sqlite3_step(ctx->find_stmt) == SQLITE_ROW;
108
109         // Unconditionally run crypt_checkpass() so pw_check() takes a
110         // similar amount of time to complete whether or not the user is
111         // in the db
112         const char *pass_hash = user_exists
113                 // bcrypt uses characters in portable range of char
114                 ? (const char *)sqlite3_column_text(ctx->find_stmt, 0)
115                 // using a custom dummy hash rather than NULL since my
116                 // measurements show that crypt_checkpass runs 66% faster
117                 // for NULL than for an actual hash
118                 : ctx->dummy_hash;
119
120         rc = crypt_checkpass(pass, pass_hash);
121         sqlite3_reset(ctx->find_stmt);
122
123         // apps shouldn't expose PW_NO_USER vs PW_BAD_PASS externally,
124         // but it is returned here for debugging purposes
125         if (!user_exists)
126                 return PW_NO_USER;
127         return (rc == 0) ? PW_OK : PW_BAD_PASS;
128 }
129
130 pw_status
131 pw_token_create(
132         pw_context *ctx, char *user,
133         unsigned char **token, unsigned int *token_len
134 )
135 {
136         #define TOK_LEN 16
137
138         unsigned char *tok = malloc(TOK_LEN);
139         if (!tok)
140                 return PW_NO_MEM;
141         arc4random_buf(tok, TOK_LEN);
142         *token = tok;
143         *token_len = TOK_LEN;
144
145         unsigned char hash[EVP_MD_size(EVP_sha256())];
146         unsigned int hash_len;
147
148         if (EVP_Digest(tok, TOK_LEN, hash, &hash_len, EVP_sha256(), NULL) != 1)
149         {
150                 free(tok);
151                 *token = NULL;
152                 return PW_CRYPT_ERROR;
153         }
154
155         int rc = sqlite3_bind_text(ctx->token_insert_stmt, 1,
156                 user, -1, SQLITE_TRANSIENT);
157         rc = sqlite3_bind_blob(ctx->token_insert_stmt, 2,
158                 hash, hash_len, SQLITE_TRANSIENT);
159         if (rc != SQLITE_OK)
160         {
161                 token = NULL;
162                 free(tok);
163                 return PW_SQLITE_ERROR;
164         }
165         rc = sqlite3_step(ctx->token_insert_stmt);
166         sqlite3_reset(ctx->token_insert_stmt);
167         return (rc == SQLITE_DONE && sqlite3_changes(ctx->conn) > 0)
168                 ? PW_OK : PW_SQLITE_ERROR;
169 }