6 #include <openssl/evp.h>
10 // Using bcrypt for now, but alternatives are emerging
11 // https://isopenbsdsecu.re/mitigations/password_hashing/
12 #define CRYPT_FLAVOR "bcrypt,a"
17 const char *dummy_hash;
18 sqlite3_stmt *upsert_stmt;
19 sqlite3_stmt *find_stmt;
20 sqlite3_stmt *token_insert_stmt;
24 pw_init_context(sqlite3 *db, pw_context **out)
27 static char shared_dummy_hash[_PASSWORD_LEN];
29 pw_context *ctx = malloc(sizeof *ctx);
34 rc = sqlite3_db_config(db,
35 SQLITE_DBCONFIG_ENABLE_FKEY, 1, &fk_enabled
37 if (rc != SQLITE_OK || !fk_enabled)
38 return PW_SQLITE_ERROR;
42 if (shared_dummy_hash[0] == '\0')
45 "bogus", CRYPT_FLAVOR,
46 shared_dummy_hash, _PASSWORD_LEN);
48 return PW_CRYPT_ERROR;
50 ctx->dummy_hash = shared_dummy_hash;
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);
57 return PW_SQLITE_ERROR;
59 rc = sqlite3_prepare_v2(db,
60 "SELECT pass FROM login WHERE user = ?",
61 -1, &ctx->find_stmt, NULL);
63 return PW_SQLITE_ERROR;
65 rc = sqlite3_prepare_v2(db,
66 "INSERT INTO token (login_id, hash) "
70 -1, &ctx->token_insert_stmt, NULL);
72 return PW_SQLITE_ERROR;
78 pw_set(pw_context *ctx, char *user, char *pass)
81 rc = sqlite3_bind_text(ctx->upsert_stmt, 1, user, -1, SQLITE_TRANSIENT);
83 return PW_SQLITE_ERROR;
85 char hash[_PASSWORD_LEN];
86 if (crypt_newhash(pass, CRYPT_FLAVOR, hash, sizeof hash) < 0)
87 return PW_CRYPT_ERROR;
89 rc = sqlite3_bind_text(ctx->upsert_stmt, 2, hash, -1, SQLITE_TRANSIENT);
91 return PW_SQLITE_ERROR;
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;
100 pw_check(pw_context *ctx, char *user, char *pass)
103 rc = sqlite3_bind_text(ctx->find_stmt, 1, user, -1, SQLITE_TRANSIENT);
105 return PW_SQLITE_ERROR;
107 bool user_exists = sqlite3_step(ctx->find_stmt) == SQLITE_ROW;
109 // Unconditionally run crypt_checkpass() so pw_check() takes a
110 // similar amount of time to complete whether or not the user is
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
120 rc = crypt_checkpass(pass, pass_hash);
121 sqlite3_reset(ctx->find_stmt);
123 // apps shouldn't expose PW_NO_USER vs PW_BAD_PASS externally,
124 // but it is returned here for debugging purposes
127 return (rc == 0) ? PW_OK : PW_BAD_PASS;
132 pw_context *ctx, char *user,
133 unsigned char **token, unsigned int *token_len
138 unsigned char *tok = malloc(TOK_LEN);
141 arc4random_buf(tok, TOK_LEN);
143 *token_len = TOK_LEN;
145 unsigned char hash[EVP_MD_size(EVP_sha256())];
146 unsigned int hash_len;
148 if (EVP_Digest(tok, TOK_LEN, hash, &hash_len, EVP_sha256(), NULL) != 1)
152 return PW_CRYPT_ERROR;
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);
163 return PW_SQLITE_ERROR;
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;