9 // Using bcrypt for now, but alternatives are emerging
10 // https://isopenbsdsecu.re/mitigations/password_hashing/
11 #define CRYPT_FLAVOR "bcrypt,a"
16 const char *dummy_hash;
17 sqlite3_stmt *upsert_stmt;
18 sqlite3_stmt *find_stmt;
22 pw_init_context(sqlite3 *db, pw_context **out)
25 static char shared_dummy_hash[_PASSWORD_LEN];
27 pw_context *ctx = malloc(sizeof *ctx);
32 rc = sqlite3_db_config(db,
33 SQLITE_DBCONFIG_ENABLE_FKEY, 1, &fk_enabled
35 if (rc != SQLITE_OK || !fk_enabled)
36 return PW_SQLITE_ERROR;
40 if (shared_dummy_hash[0] == '\0')
43 "bogus", CRYPT_FLAVOR,
44 shared_dummy_hash, _PASSWORD_LEN);
46 return PW_CRYPT_ERROR;
48 ctx->dummy_hash = shared_dummy_hash;
50 rc = sqlite3_prepare_v2(db,
51 "INSERT INTO login (user, pass) VALUES (?1, ?2) "
52 "ON CONFLICT (user) DO UPDATE SET pass = ?2",
53 -1, &ctx->upsert_stmt, NULL);
55 return PW_SQLITE_ERROR;
56 rc = sqlite3_prepare_v2(db,
57 "SELECT pass FROM login WHERE user = ?",
58 -1, &ctx->find_stmt, NULL);
60 return PW_SQLITE_ERROR;
66 pw_set(pw_context *ctx, char *user, char *pass)
69 rc = sqlite3_bind_text(ctx->upsert_stmt, 1, user, -1, SQLITE_TRANSIENT);
71 return PW_SQLITE_ERROR;
73 char hash[_PASSWORD_LEN];
74 if (crypt_newhash(pass, CRYPT_FLAVOR, hash, sizeof hash) < 0)
75 return PW_CRYPT_ERROR;
77 rc = sqlite3_bind_text(ctx->upsert_stmt, 2, hash, -1, SQLITE_TRANSIENT);
79 return PW_SQLITE_ERROR;
81 rc = sqlite3_step(ctx->upsert_stmt);
82 sqlite3_reset(ctx->upsert_stmt);
83 return (rc == SQLITE_DONE && sqlite3_changes(ctx->conn) > 0)
84 ? PW_OK : PW_SQLITE_ERROR;
88 pw_check(pw_context *ctx, char *user, char *pass)
91 rc = sqlite3_bind_text(ctx->find_stmt, 1, user, -1, SQLITE_TRANSIENT);
93 return PW_SQLITE_ERROR;
95 bool user_exists = sqlite3_step(ctx->find_stmt) == SQLITE_ROW;
97 // Unconditionally run crypt_checkpass() so pw_check() takes a
98 // similar amount of time to complete whether or not the user is
100 const char *pass_hash = user_exists
101 // bcrypt uses characters in portable range of char
102 ? (const char *)sqlite3_column_text(ctx->find_stmt, 0)
103 // magic hash doesn't matter, just make crypt_checkpass sweat
106 rc = crypt_checkpass(pass, pass_hash);
107 sqlite3_reset(ctx->find_stmt);
111 return (rc == 0) ? PW_OK : PW_BAD_PASS;