Persist optional TOTP authentication securely

This commit is contained in:
Mario Fetka
2026-07-17 02:12:08 +02:00
parent eb3c6c9e77
commit 7a1ef7c1ef
6 changed files with 856 additions and 10 deletions
@@ -1,4 +1,5 @@
add_library(authsqlite3 MODULE
auth-security-store.c
sqlite.c
password.c)
@@ -16,4 +17,14 @@ if(BUILD_TESTING)
bongoxpl
LibGcrypt::LibGcrypt)
add_test(NAME auth-password COMMAND auth-password-test)
add_executable(auth-security-store-test
auth-security-store-test.c
auth-security-store.c)
target_link_libraries(auth-security-store-test PRIVATE
bongoauthsecurity
LibGcrypt::LibGcrypt
OATH::OATH
SQLite3::SQLite3)
add_test(NAME auth-security-store COMMAND auth-security-store-test)
endif()
@@ -0,0 +1,173 @@
/****************************************************************************
* <Novell-copyright>
* Copyright (c) 2001 Novell, Inc. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public License
* as published by the Free Software Foundation.
*
* This program 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 this program; if not, contact Novell, Inc.
*
* To contact Novell about this file by physical or electronic mail, you
* may find current contact information at www.novell.com.
* </Novell-copyright>
****************************************************************************/
#include <liboath/oath.h>
#include <sqlite3.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "auth-security-store.h"
static int
PrepareDatabase(const char *path)
{
sqlite3 *database = NULL;
int result = -1;
if (sqlite3_open(path, &database) == SQLITE_OK &&
sqlite3_exec(database,
"CREATE TABLE users(username TEXT PRIMARY KEY,password TEXT);"
"INSERT INTO users(username) VALUES('admin');",
NULL, NULL, NULL) == SQLITE_OK)
result = 0;
if (database)
sqlite3_close(database);
return result;
}
int
main(void)
{
char directory[] = "/tmp/bongo-auth-store-XXXXXX";
char database_path[256] = "";
char key_path[256] = "";
char secret[BONGO_AUTH_TOTP_SECRET_SIZE];
char recovery[AUTH_RECOVERY_CODE_COUNT][BONGO_AUTH_CREDENTIAL_SIZE];
char otp[7];
char wrong_otp[7];
char *decoded = NULL;
size_t decoded_size = 0;
time_t now = 1234567890;
AuthSecurityStore store;
struct stat status;
int store_open = 0;
int oath_open = 0;
int enabled;
int required;
int result = 1;
memset(&store, 0, sizeof(store));
if (!mkdtemp(directory) ||
snprintf(database_path, sizeof(database_path), "%s/userdb.sqlite",
directory) >= (int)sizeof(database_path) ||
snprintf(key_path, sizeof(key_path), "%s/auth.key", directory) >=
(int)sizeof(key_path))
goto cleanup;
if (BongoAuthSecurityInit() != 0)
goto cleanup;
oath_open = 1;
if (PrepareDatabase(database_path) != 0 ||
AuthSecurityStoreOpen(&store, database_path, key_path) != 0)
goto cleanup;
store_open = 1;
if (AuthSecurityGetPolicy(&store, "admin", &enabled, &required) != 0 ||
enabled || required)
goto cleanup;
if (BongoAuthGenerateTotpSecret(secret, sizeof(secret)) != 0 ||
oath_base32_decode(secret, strlen(secret), &decoded, &decoded_size) !=
OATH_OK ||
oath_totp_generate(decoded, decoded_size, now, 30, 0, 6, otp) != OATH_OK)
goto cleanup;
memcpy(wrong_otp, otp, sizeof(wrong_otp));
wrong_otp[0] = wrong_otp[0] == '9' ? '0' : (char)(wrong_otp[0] + 1);
if (AuthSecurityEnableTotp(&store, "admin", secret, wrong_otp, now,
recovery) == 0 ||
AuthSecurityGetPolicy(&store, "admin", &enabled, &required) != 0 ||
enabled)
goto cleanup;
if (AuthSecurityEnableTotp(&store, "admin", secret, otp, now, recovery) != 0 ||
recovery[0][0] == '\0' || strcmp(recovery[0], recovery[1]) == 0 ||
AuthSecurityGetPolicy(&store, "admin", &enabled, &required) != 0 ||
!enabled || required)
goto cleanup;
if (stat(key_path, &status) != 0 ||
(status.st_mode & (S_IRWXG | S_IRWXO)) != 0)
goto cleanup;
{
sqlite3_stmt *statement = NULL;
const void *encrypted;
int encrypted_size;
const char *hash;
if (sqlite3_prepare_v2(store.database,
"SELECT s.totp_secret,r.code_hash FROM auth_security s "
"JOIN auth_recovery_codes r ON r.username=s.username "
"WHERE s.username='admin' LIMIT 1",
-1, &statement, NULL) != SQLITE_OK ||
sqlite3_step(statement) != SQLITE_ROW) {
sqlite3_finalize(statement);
goto cleanup;
}
encrypted = sqlite3_column_blob(statement, 0);
encrypted_size = sqlite3_column_bytes(statement, 0);
hash = (const char *)sqlite3_column_text(statement, 1);
if (!encrypted || encrypted_size <= (int)strlen(secret) ||
(encrypted_size == (int)strlen(secret) &&
memcmp(encrypted, secret, strlen(secret)) == 0) ||
!hash || strncmp(hash, "$argon2id$", 10) != 0) {
sqlite3_finalize(statement);
goto cleanup;
}
sqlite3_finalize(statement);
}
if (AuthSecurityVerifySecondFactor(&store, "admin", otp, now) !=
AUTH_SECOND_FACTOR_VALID ||
AuthSecurityVerifySecondFactor(&store, "admin", wrong_otp, now) !=
AUTH_SECOND_FACTOR_INVALID ||
AuthSecurityVerifySecondFactor(&store, "admin", recovery[0], now) !=
AUTH_SECOND_FACTOR_VALID ||
AuthSecurityVerifySecondFactor(&store, "admin", recovery[0], now) !=
AUTH_SECOND_FACTOR_INVALID)
goto cleanup;
if (AuthSecuritySetRequireAppPasswords(&store, "admin", 1) != 0 ||
AuthSecurityGetPolicy(&store, "admin", &enabled, &required) != 0 ||
!enabled || !required)
goto cleanup;
if (AuthSecurityDisableTotp(&store, "admin") != 0 ||
AuthSecurityGetPolicy(&store, "admin", &enabled, &required) != 0 ||
enabled || required ||
AuthSecurityVerifySecondFactor(&store, "admin", otp, now) !=
AUTH_SECOND_FACTOR_NOT_REQUIRED ||
AuthSecuritySetRequireAppPasswords(&store, "admin", 1) == 0)
goto cleanup;
result = 0;
cleanup:
free(decoded);
if (store_open)
AuthSecurityStoreClose(&store);
if (oath_open)
BongoAuthSecurityDone();
unlink(key_path);
unlink(database_path);
{
char path[300];
snprintf(path, sizeof(path), "%s-wal", database_path);
unlink(path);
snprintf(path, sizeof(path), "%s-shm", database_path);
unlink(path);
}
rmdir(directory);
return result;
}
@@ -0,0 +1,576 @@
/****************************************************************************
* <Novell-copyright>
* Copyright (c) 2001 Novell, Inc. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public License
* as published by the Free Software Foundation.
*
* This program 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 this program; if not, contact Novell, Inc.
*
* To contact Novell about this file by physical or electronic mail, you
* may find current contact information at www.novell.com.
* </Novell-copyright>
****************************************************************************/
#include <errno.h>
#include <fcntl.h>
#include <gcrypt.h>
#include <sqlite3.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "auth-security-store.h"
#define AUTH_KEY_SIZE 32U
#define AUTH_NONCE_SIZE 12U
#define AUTH_TAG_SIZE 16U
#define AUTH_SECRET_MAGIC "BTOTP1"
#define AUTH_SECRET_MAGIC_SIZE 6U
static void
SecureClear(void *buffer, size_t length)
{
volatile unsigned char *cursor = buffer;
while (length-- > 0)
*cursor++ = 0;
}
static int
ReadAll(int file, unsigned char *data, size_t length)
{
while (length > 0) {
ssize_t count = read(file, data, length);
if (count <= 0)
return -1;
data += count;
length -= (size_t)count;
}
return 0;
}
static int
WriteAll(int file, const unsigned char *data, size_t length)
{
while (length > 0) {
ssize_t count = write(file, data, length);
if (count <= 0)
return -1;
data += count;
length -= (size_t)count;
}
return 0;
}
static int
LoadKey(const char *path, unsigned char key[AUTH_KEY_SIZE])
{
int file;
int flags = O_RDONLY;
#ifdef O_NOFOLLOW
flags |= O_NOFOLLOW;
#endif
retry:
file = open(path, flags);
if (file >= 0) {
struct stat status;
unsigned char extra;
int valid = fstat(file, &status) == 0 && S_ISREG(status.st_mode) &&
(status.st_mode & (S_IRWXG | S_IRWXO)) == 0;
if (valid)
valid = ReadAll(file, key, AUTH_KEY_SIZE) == 0;
if (valid)
valid = read(file, &extra, 1) == 0;
close(file);
if (!valid)
SecureClear(key, AUTH_KEY_SIZE);
return valid ? 0 : -1;
}
if (errno != ENOENT)
return -1;
gcry_randomize(key, AUTH_KEY_SIZE, GCRY_STRONG_RANDOM);
file = open(path, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
if (file < 0) {
if (errno == EEXIST)
goto retry;
SecureClear(key, AUTH_KEY_SIZE);
return -1;
}
{
int failed = WriteAll(file, key, AUTH_KEY_SIZE) != 0;
if (!failed && fsync(file) != 0)
failed = 1;
if (close(file) != 0)
failed = 1;
if (!failed)
return 0;
unlink(path);
SecureClear(key, AUTH_KEY_SIZE);
return -1;
}
}
static int
EncryptSecret(AuthSecurityStore *store, const char *username,
const char *secret, unsigned char **encrypted,
size_t *encrypted_size)
{
unsigned char key[AUTH_KEY_SIZE];
gcry_cipher_hd_t cipher = NULL;
unsigned char *output = NULL;
size_t secret_size;
size_t total;
int result = -1;
if (!store || !username || !secret || !encrypted || !encrypted_size)
return -1;
secret_size = strlen(secret);
if (secret_size == 0 || secret_size >= BONGO_AUTH_TOTP_SECRET_SIZE)
return -1;
if (LoadKey(store->key_path, key) != 0) {
SecureClear(key, sizeof(key));
return -1;
}
total = AUTH_SECRET_MAGIC_SIZE + AUTH_NONCE_SIZE + AUTH_TAG_SIZE + secret_size;
output = malloc(total);
if (!output)
goto done;
memcpy(output, AUTH_SECRET_MAGIC, AUTH_SECRET_MAGIC_SIZE);
gcry_randomize(output + AUTH_SECRET_MAGIC_SIZE, AUTH_NONCE_SIZE,
GCRY_STRONG_RANDOM);
if (gcry_cipher_open(&cipher, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_GCM, 0) ||
gcry_cipher_setkey(cipher, key, sizeof(key)) ||
gcry_cipher_setiv(cipher, output + AUTH_SECRET_MAGIC_SIZE,
AUTH_NONCE_SIZE) ||
gcry_cipher_authenticate(cipher, username, strlen(username)) ||
gcry_cipher_encrypt(cipher,
output + AUTH_SECRET_MAGIC_SIZE + AUTH_NONCE_SIZE +
AUTH_TAG_SIZE,
secret_size, secret, secret_size) ||
gcry_cipher_gettag(cipher,
output + AUTH_SECRET_MAGIC_SIZE + AUTH_NONCE_SIZE,
AUTH_TAG_SIZE))
goto done;
*encrypted = output;
*encrypted_size = total;
output = NULL;
result = 0;
done:
if (cipher)
gcry_cipher_close(cipher);
SecureClear(key, sizeof(key));
free(output);
return result;
}
static int
DecryptSecret(AuthSecurityStore *store, const char *username,
const void *encrypted, size_t encrypted_size,
char secret[BONGO_AUTH_TOTP_SECRET_SIZE])
{
const unsigned char *input = encrypted;
unsigned char key[AUTH_KEY_SIZE];
gcry_cipher_hd_t cipher = NULL;
size_t plain_size;
int result = -1;
secret[0] = '\0';
if (!store || !username || !encrypted ||
encrypted_size <= AUTH_SECRET_MAGIC_SIZE + AUTH_NONCE_SIZE +
AUTH_TAG_SIZE ||
memcmp(input, AUTH_SECRET_MAGIC, AUTH_SECRET_MAGIC_SIZE) != 0)
return -1;
if (LoadKey(store->key_path, key) != 0) {
SecureClear(key, sizeof(key));
return -1;
}
plain_size = encrypted_size - AUTH_SECRET_MAGIC_SIZE - AUTH_NONCE_SIZE -
AUTH_TAG_SIZE;
if (plain_size >= BONGO_AUTH_TOTP_SECRET_SIZE)
goto done;
if (gcry_cipher_open(&cipher, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_GCM, 0) ||
gcry_cipher_setkey(cipher, key, sizeof(key)) ||
gcry_cipher_setiv(cipher, input + AUTH_SECRET_MAGIC_SIZE,
AUTH_NONCE_SIZE) ||
gcry_cipher_authenticate(cipher, username, strlen(username)) ||
gcry_cipher_decrypt(cipher, secret, plain_size,
input + AUTH_SECRET_MAGIC_SIZE + AUTH_NONCE_SIZE +
AUTH_TAG_SIZE,
plain_size) ||
gcry_cipher_checktag(cipher,
input + AUTH_SECRET_MAGIC_SIZE + AUTH_NONCE_SIZE,
AUTH_TAG_SIZE))
goto done;
secret[plain_size] = '\0';
result = 0;
done:
if (cipher)
gcry_cipher_close(cipher);
SecureClear(key, sizeof(key));
if (result != 0)
SecureClear(secret, BONGO_AUTH_TOTP_SECRET_SIZE);
return result;
}
int
AuthSecurityEnsureSchema(sqlite3 *database)
{
const char schema[] =
"PRAGMA foreign_keys=ON;"
"CREATE TABLE IF NOT EXISTS auth_security ("
" username TEXT PRIMARY KEY REFERENCES users(username) ON DELETE CASCADE,"
" totp_enabled INTEGER NOT NULL DEFAULT 0 CHECK(totp_enabled IN (0,1)),"
" require_app_passwords INTEGER NOT NULL DEFAULT 0 "
" CHECK(require_app_passwords IN (0,1)),"
" totp_secret BLOB,"
" updated_at INTEGER NOT NULL DEFAULT 0,"
" CHECK(totp_enabled=0 OR totp_secret IS NOT NULL),"
" CHECK(require_app_passwords=0 OR totp_enabled=1)"
");"
"CREATE TABLE IF NOT EXISTS auth_recovery_codes ("
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
" username TEXT NOT NULL REFERENCES users(username) ON DELETE CASCADE,"
" code_hash TEXT NOT NULL,"
" created_at INTEGER NOT NULL,"
" used_at INTEGER"
");"
"CREATE INDEX IF NOT EXISTS auth_recovery_active "
" ON auth_recovery_codes(username,used_at);"
"PRAGMA user_version=1;";
return database && sqlite3_exec(database, schema, NULL, NULL, NULL) == SQLITE_OK
? 0
: -1;
}
int
AuthSecurityStoreOpen(AuthSecurityStore *store, const char *database_path,
const char *key_path)
{
if (!store || !database_path || !key_path)
return -1;
memset(store, 0, sizeof(*store));
if (sqlite3_open_v2(database_path, &store->database,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_FULLMUTEX,
NULL) != SQLITE_OK)
goto fail;
store->key_path = strdup(key_path);
if (!store->key_path)
goto fail;
sqlite3_busy_timeout(store->database, 5000);
if (sqlite3_exec(store->database,
"PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;",
NULL, NULL, NULL) != SQLITE_OK ||
AuthSecurityEnsureSchema(store->database) != 0)
goto fail;
if (chmod(database_path, S_IRUSR | S_IWUSR) != 0)
goto fail;
return 0;
fail:
AuthSecurityStoreClose(store);
return -1;
}
void
AuthSecurityStoreClose(AuthSecurityStore *store)
{
if (!store)
return;
if (store->database)
sqlite3_close(store->database);
free(store->key_path);
memset(store, 0, sizeof(*store));
}
int
AuthSecurityGetPolicy(AuthSecurityStore *store, const char *username,
int *totp_enabled, int *require_app_passwords)
{
sqlite3_stmt *statement = NULL;
int result = -1;
if (!store || !store->database || !username || !totp_enabled ||
!require_app_passwords)
return -1;
*totp_enabled = 0;
*require_app_passwords = 0;
if (sqlite3_prepare_v2(store->database,
"SELECT totp_enabled,require_app_passwords FROM auth_security "
"WHERE username=?",
-1, &statement, NULL) != SQLITE_OK)
return -1;
sqlite3_bind_text(statement, 1, username, -1, SQLITE_TRANSIENT);
result = sqlite3_step(statement);
if (result == SQLITE_ROW) {
*totp_enabled = sqlite3_column_int(statement, 0) != 0;
*require_app_passwords = sqlite3_column_int(statement, 1) != 0;
result = 0;
} else if (result == SQLITE_DONE) {
result = 0;
} else {
result = -1;
}
sqlite3_finalize(statement);
return result;
}
int
AuthSecurityEnableTotp(AuthSecurityStore *store, const char *username,
const char *secret, const char *otp, time_t now,
char recovery_codes[AUTH_RECOVERY_CODE_COUNT]
[BONGO_AUTH_CREDENTIAL_SIZE])
{
char hashes[AUTH_RECOVERY_CODE_COUNT][BONGO_AUTH_HASH_SIZE];
unsigned char *encrypted = NULL;
size_t encrypted_size = 0;
sqlite3_stmt *statement = NULL;
int transaction = 0;
int result = -1;
int i;
if (!store || !store->database || !username || !secret || !otp ||
!recovery_codes || !BongoAuthVerifyTotp(secret, otp, now))
return -1;
memset(hashes, 0, sizeof(hashes));
for (i = 0; i < AUTH_RECOVERY_CODE_COUNT; i++) {
if (BongoAuthGenerateCredential(recovery_codes[i],
BONGO_AUTH_CREDENTIAL_SIZE) != 0 ||
BongoAuthHashCredential(recovery_codes[i], hashes[i],
BONGO_AUTH_HASH_SIZE) != 0)
goto done;
}
if (EncryptSecret(store, username, secret, &encrypted, &encrypted_size) != 0)
goto done;
if (sqlite3_exec(store->database, "BEGIN IMMEDIATE", NULL, NULL, NULL) !=
SQLITE_OK)
goto done;
transaction = 1;
if (sqlite3_prepare_v2(store->database,
"INSERT INTO auth_security(username,totp_enabled,"
"require_app_passwords,totp_secret,updated_at) VALUES(?,1,0,?,?) "
"ON CONFLICT(username) DO UPDATE SET totp_enabled=1,"
"require_app_passwords=0,totp_secret=excluded.totp_secret,"
"updated_at=excluded.updated_at",
-1, &statement, NULL) != SQLITE_OK)
goto done;
sqlite3_bind_text(statement, 1, username, -1, SQLITE_TRANSIENT);
sqlite3_bind_blob(statement, 2, encrypted, (int)encrypted_size,
SQLITE_TRANSIENT);
sqlite3_bind_int64(statement, 3, (sqlite3_int64)now);
if (sqlite3_step(statement) != SQLITE_DONE)
goto done;
sqlite3_finalize(statement);
statement = NULL;
if (sqlite3_prepare_v2(store->database,
"DELETE FROM auth_recovery_codes WHERE username=?", -1,
&statement, NULL) != SQLITE_OK)
goto done;
sqlite3_bind_text(statement, 1, username, -1, SQLITE_TRANSIENT);
if (sqlite3_step(statement) != SQLITE_DONE)
goto done;
sqlite3_finalize(statement);
statement = NULL;
if (sqlite3_prepare_v2(store->database,
"INSERT INTO auth_recovery_codes(username,code_hash,created_at) "
"VALUES(?,?,?)",
-1, &statement, NULL) != SQLITE_OK)
goto done;
for (i = 0; i < AUTH_RECOVERY_CODE_COUNT; i++) {
sqlite3_bind_text(statement, 1, username, -1, SQLITE_TRANSIENT);
sqlite3_bind_text(statement, 2, hashes[i], -1, SQLITE_TRANSIENT);
sqlite3_bind_int64(statement, 3, (sqlite3_int64)now);
if (sqlite3_step(statement) != SQLITE_DONE)
goto done;
sqlite3_reset(statement);
sqlite3_clear_bindings(statement);
}
if (sqlite3_exec(store->database, "COMMIT", NULL, NULL, NULL) != SQLITE_OK)
goto done;
transaction = 0;
result = 0;
done:
if (statement)
sqlite3_finalize(statement);
if (transaction)
sqlite3_exec(store->database, "ROLLBACK", NULL, NULL, NULL);
if (result != 0)
SecureClear(recovery_codes, AUTH_RECOVERY_CODE_COUNT *
BONGO_AUTH_CREDENTIAL_SIZE);
SecureClear(hashes, sizeof(hashes));
if (encrypted) {
SecureClear(encrypted, encrypted_size);
free(encrypted);
}
return result;
}
static int
VerifyRecoveryCode(AuthSecurityStore *store, const char *username,
const char *response, time_t now)
{
sqlite3_stmt *statement = NULL;
sqlite3_int64 match = 0;
int status;
if (sqlite3_prepare_v2(store->database,
"SELECT id,code_hash FROM auth_recovery_codes "
"WHERE username=? AND used_at IS NULL ORDER BY id",
-1, &statement, NULL) != SQLITE_OK)
return -1;
sqlite3_bind_text(statement, 1, username, -1, SQLITE_TRANSIENT);
while ((status = sqlite3_step(statement)) == SQLITE_ROW) {
const char *hash = (const char *)sqlite3_column_text(statement, 1);
if (hash && BongoAuthVerifyCredential(hash, response)) {
match = sqlite3_column_int64(statement, 0);
break;
}
}
sqlite3_finalize(statement);
if (!match)
return status == SQLITE_DONE ? 0 : -1;
if (sqlite3_prepare_v2(store->database,
"UPDATE auth_recovery_codes SET used_at=? "
"WHERE id=? AND used_at IS NULL",
-1, &statement, NULL) != SQLITE_OK)
return -1;
sqlite3_bind_int64(statement, 1, (sqlite3_int64)now);
sqlite3_bind_int64(statement, 2, match);
status = sqlite3_step(statement) == SQLITE_DONE &&
sqlite3_changes(store->database) == 1;
sqlite3_finalize(statement);
return status ? 1 : -1;
}
int
AuthSecurityVerifySecondFactor(AuthSecurityStore *store,
const char *username, const char *response,
time_t now)
{
sqlite3_stmt *statement = NULL;
char secret[BONGO_AUTH_TOTP_SECRET_SIZE];
const void *encrypted;
int encrypted_size;
int status;
if (!store || !store->database || !username || !response)
return AUTH_SECOND_FACTOR_ERROR;
if (sqlite3_prepare_v2(store->database,
"SELECT totp_enabled,totp_secret FROM auth_security WHERE username=?",
-1, &statement, NULL) != SQLITE_OK)
return AUTH_SECOND_FACTOR_ERROR;
sqlite3_bind_text(statement, 1, username, -1, SQLITE_TRANSIENT);
status = sqlite3_step(statement);
if (status == SQLITE_DONE ||
(status == SQLITE_ROW && sqlite3_column_int(statement, 0) == 0)) {
sqlite3_finalize(statement);
return AUTH_SECOND_FACTOR_NOT_REQUIRED;
}
if (status != SQLITE_ROW || sqlite3_column_type(statement, 1) != SQLITE_BLOB) {
sqlite3_finalize(statement);
return AUTH_SECOND_FACTOR_ERROR;
}
encrypted = sqlite3_column_blob(statement, 1);
encrypted_size = sqlite3_column_bytes(statement, 1);
status = DecryptSecret(store, username, encrypted, (size_t)encrypted_size,
secret);
sqlite3_finalize(statement);
if (status != 0)
return AUTH_SECOND_FACTOR_ERROR;
status = BongoAuthVerifyTotp(secret, response, now);
SecureClear(secret, sizeof(secret));
if (status)
return AUTH_SECOND_FACTOR_VALID;
status = VerifyRecoveryCode(store, username, response, now);
if (status < 0)
return AUTH_SECOND_FACTOR_ERROR;
return status ? AUTH_SECOND_FACTOR_VALID : AUTH_SECOND_FACTOR_INVALID;
}
int
AuthSecurityDisableTotp(AuthSecurityStore *store, const char *username)
{
sqlite3_stmt *statement = NULL;
int transaction = 0;
int result = -1;
if (!store || !store->database || !username)
return -1;
if (sqlite3_exec(store->database, "BEGIN IMMEDIATE", NULL, NULL, NULL) !=
SQLITE_OK)
return -1;
transaction = 1;
if (sqlite3_prepare_v2(store->database,
"INSERT INTO auth_security(username,totp_enabled,"
"require_app_passwords,totp_secret,updated_at) VALUES(?,0,0,NULL,?) "
"ON CONFLICT(username) DO UPDATE SET totp_enabled=0,"
"require_app_passwords=0,totp_secret=NULL,updated_at=excluded.updated_at",
-1, &statement, NULL) != SQLITE_OK)
goto done;
sqlite3_bind_text(statement, 1, username, -1, SQLITE_TRANSIENT);
sqlite3_bind_int64(statement, 2, (sqlite3_int64)time(NULL));
if (sqlite3_step(statement) != SQLITE_DONE)
goto done;
sqlite3_finalize(statement);
statement = NULL;
if (sqlite3_prepare_v2(store->database,
"DELETE FROM auth_recovery_codes WHERE username=?", -1,
&statement, NULL) != SQLITE_OK)
goto done;
sqlite3_bind_text(statement, 1, username, -1, SQLITE_TRANSIENT);
if (sqlite3_step(statement) != SQLITE_DONE)
goto done;
if (sqlite3_exec(store->database, "COMMIT", NULL, NULL, NULL) != SQLITE_OK)
goto done;
transaction = 0;
result = 0;
done:
if (statement)
sqlite3_finalize(statement);
if (transaction)
sqlite3_exec(store->database, "ROLLBACK", NULL, NULL, NULL);
return result;
}
int
AuthSecuritySetRequireAppPasswords(AuthSecurityStore *store,
const char *username, int required)
{
sqlite3_stmt *statement = NULL;
int result;
if (!store || !store->database || !username)
return -1;
if (sqlite3_prepare_v2(store->database,
"UPDATE auth_security SET require_app_passwords=?,updated_at=? "
"WHERE username=? AND (?=0 OR totp_enabled=1)",
-1, &statement, NULL) != SQLITE_OK)
return -1;
sqlite3_bind_int(statement, 1, required != 0);
sqlite3_bind_int64(statement, 2, (sqlite3_int64)time(NULL));
sqlite3_bind_text(statement, 3, username, -1, SQLITE_TRANSIENT);
sqlite3_bind_int(statement, 4, required != 0);
result = sqlite3_step(statement) == SQLITE_DONE &&
sqlite3_changes(store->database) == 1;
sqlite3_finalize(statement);
return result ? 0 : -1;
}
@@ -0,0 +1,63 @@
/****************************************************************************
* <Novell-copyright>
* Copyright (c) 2001 Novell, Inc. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public License
* as published by the Free Software Foundation.
*
* This program 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 this program; if not, contact Novell, Inc.
*
* To contact Novell about this file by physical or electronic mail, you
* may find current contact information at www.novell.com.
* </Novell-copyright>
****************************************************************************/
#ifndef BONGO_AUTH_SECURITY_STORE_H
#define BONGO_AUTH_SECURITY_STORE_H
#include <sqlite3.h>
#include <stddef.h>
#include <time.h>
#include <bongoauthsecurity.h>
#define AUTH_RECOVERY_CODE_COUNT 10
typedef struct {
sqlite3 *database;
char *key_path;
} AuthSecurityStore;
enum {
AUTH_SECOND_FACTOR_ERROR = -1,
AUTH_SECOND_FACTOR_NOT_REQUIRED = 0,
AUTH_SECOND_FACTOR_VALID = 1,
AUTH_SECOND_FACTOR_INVALID = 2
};
int AuthSecurityEnsureSchema(sqlite3 *database);
int AuthSecurityStoreOpen(AuthSecurityStore *store, const char *database_path,
const char *key_path);
void AuthSecurityStoreClose(AuthSecurityStore *store);
int AuthSecurityGetPolicy(AuthSecurityStore *store, const char *username,
int *totp_enabled, int *require_app_passwords);
int AuthSecurityEnableTotp(AuthSecurityStore *store, const char *username,
const char *secret, const char *otp, time_t now,
char recovery_codes[AUTH_RECOVERY_CODE_COUNT]
[BONGO_AUTH_CREDENTIAL_SIZE]);
int AuthSecurityVerifySecondFactor(AuthSecurityStore *store,
const char *username, const char *response,
time_t now);
int AuthSecurityDisableTotp(AuthSecurityStore *store, const char *username);
int AuthSecuritySetRequireAppPasswords(AuthSecurityStore *store,
const char *username, int required);
#endif
+32 -10
View File
@@ -7,6 +7,7 @@
#define LOGGERNAME "msgauth"
#include <logger.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
@@ -14,6 +15,7 @@
#include "sqlite.h"
#include "password.h"
#include "auth-security-store.h"
typedef struct {
MsgSQLStatement find_user;
@@ -44,14 +46,7 @@ AuthSqlite_Install(void)
int dcode;
char path[XPL_MAX_PATH + 1];
MsgSQLHandle *handle;
struct stat buf;
AuthSqlite_GetDbPath(path, XPL_MAX_PATH);
if (stat(path, &buf) == 0) {
// FIXME: db already exists - for now, remove, but could/should be more gentle
unlink(path);
}
handle = MsgSQLOpen(path, NULL, 1000);
if (NULL == handle) return -2;
@@ -59,14 +54,14 @@ AuthSqlite_Install(void)
if (MsgSQLBeginTransaction(handle)) goto fail;
dcode = sqlite3_exec (handle->db,
"PRAGMA user_version=0;"
"CREATE TABLE users (username TEXT DEFAULT NULL UNIQUE,"
"CREATE TABLE IF NOT EXISTS users (username TEXT DEFAULT NULL UNIQUE,"
" password TEXT DEFAULT NULL"
");"
"INSERT INTO users (username, password)"
"INSERT OR IGNORE INTO users (username, password)"
" VALUES ('admin', NULL); ",
NULL, NULL, NULL);
if (SQLITE_OK != dcode) goto fail;
if (AuthSecurityEnsureSchema(handle->db) != 0) goto fail;
if (MsgSQLCommitTransaction(handle)) goto fail;
@@ -81,6 +76,33 @@ fail:
return -1;
}
int
AuthSqlite_Init(void)
{
char path[XPL_MAX_PATH + 1];
sqlite3 *database = NULL;
int result = -1;
AuthSqlite_GetDbPath(path, sizeof(path));
if (access(path, F_OK) != 0 && errno == ENOENT)
return 0;
if (sqlite3_open_v2(path, &database,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_FULLMUTEX,
NULL) != SQLITE_OK)
goto done;
sqlite3_busy_timeout(database, 5000);
if (sqlite3_exec(database,
"PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;",
NULL, NULL, NULL) == SQLITE_OK &&
AuthSecurityEnsureSchema(database) == 0)
result = 0;
done:
if (database)
sqlite3_close(database);
return result;
}
int
AuthSqlite_FindUser(const char *user)
{
+1
View File
@@ -8,4 +8,5 @@ int AuthSqlite_SetPassword(const char *user, const char *password);
int AuthSqlite_GetUserStore(const char *user, struct sockaddr_in *store);
int AuthSqlite_UserList(char **list[]);
int AuthSqlite_InterfaceVersion(void);
int AuthSqlite_Init(void);
int AuthODBC_Init(void);