feat(a7): SCRAM-SHA-256 daemon auth to replace replayable digest
Replace the challenge-less static-SHA-256 daemon bearer credential with a SCRAM-SHA-256-style challenge/response and a salted PBKDF2 verifier store. PROTOCOL_VERSION 2.18.0 -> 2.19.0; legacy user:SHA256HEX stores hard-reject. - credentials: b64/rand/PBKDF2/HMAC primitives, verifier store parser, constant-time proof verify + ServerSignature, --hash-credentials helper - config: auth block is now [present][username]; client runs the challenge exchange; config_burn_auth wipes plaintext/derived secrets (A7-4) - server: gate drives the challenge, dummy verifier for unknown/off-list users - tests: independent Python KAT, replay + legacy integration tests, fuzz paths - docs: new store format, --hash-credentials, 2.19.0 bump TLS verification behavior (A7-3/S1) is intentionally unchanged.
This commit is contained in:
+416
-147
@@ -1,6 +1,7 @@
|
||||
#include "test_credentials.h"
|
||||
#include "credentials.h"
|
||||
#include "test_utils.h"
|
||||
#include "utils.h"
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -9,17 +10,42 @@
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* Known SHA-256 vectors pin the digest derivation to real SHA-256 so a change
|
||||
* in the hashing (or a wire/store format change) is observable. */
|
||||
#define SHA256_EMPTY "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
#define SHA256_SECRET "2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b"
|
||||
#define SHA256_ALICE_PASS "9b90e524e94995ee4aeae2ee3c428a53405d1e8db147f44facc46797d0caf4c3"
|
||||
/* Known-answer vector, independently recomputed with Python
|
||||
* (hashlib.pbkdf2_hmac / hmac / hashlib.sha256). */
|
||||
#define KAT_PASSWORD "alice-s3cret"
|
||||
#define KAT_USER "alice"
|
||||
#define KAT_ITERS 4096u
|
||||
#define KAT_CLIENT_KEY "80f0e0af43e34e8aeec1738609c5d1eac8646601b4f244cef5a04a9f13563cf8"
|
||||
#define KAT_STORED_KEY "5b3b489437085a11fe594ab99154da4cb4ebab4ae8c2edf51fbaa9277e9f9099"
|
||||
#define KAT_SERVER_KEY "38f668736210bd4dbcb5193b9a514c5b1047174eff5f5a80ee4c2b1e8b2c76a1"
|
||||
#define KAT_CLIENT_PROOF "c9b0d397b853176b842a751b9af327270ae0c5e286cb77d16e59fa42245270f6"
|
||||
#define KAT_SERVER_SIG "93c0d94b9ee29798ffd42734a9becb2168bad1f69e492d2ccb042a43db13bffc"
|
||||
#define KAT_SALT_B64 "AAECAwQFBgcICQoLDA0ODw=="
|
||||
#define KAT_NAME_PREFIX "$fastsync$1$pbkdf2-sha256$"
|
||||
|
||||
static int g_file_counter = 0;
|
||||
|
||||
/* Write `contents` to a uniquely-named temp file and return a malloc'd path
|
||||
* (the caller frees it; the file is removed at the end of the test process or
|
||||
* on request via rm_temp). */
|
||||
static int hex_nibble(char c) {
|
||||
if (c >= '0' && c <= '9')
|
||||
return c - '0';
|
||||
if (c >= 'a' && c <= 'f')
|
||||
return c - 'a' + 10;
|
||||
if (c >= 'A' && c <= 'F')
|
||||
return c - 'A' + 10;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void unhex(const char* hex, uint8_t* out, size_t out_len) {
|
||||
for (size_t i = 0; i < out_len; i++)
|
||||
out[i] = (uint8_t)((hex_nibble(hex[2 * i]) << 4) | hex_nibble(hex[2 * i + 1]));
|
||||
}
|
||||
|
||||
/* Fill deterministic nonces: out[i] = first + i. */
|
||||
static void ramp(uint8_t* out, size_t len, uint8_t first) {
|
||||
for (size_t i = 0; i < len; i++)
|
||||
out[i] = (uint8_t)(first + i);
|
||||
}
|
||||
|
||||
static char* make_tmp_file(const char* contents) {
|
||||
char path[256];
|
||||
snprintf(path, sizeof(path), "/tmp/fs_cred_test_%d_%d", (int)getpid(), g_file_counter++);
|
||||
@@ -36,7 +62,7 @@ static char* make_tmp_file(const char* contents) {
|
||||
/* Credential/password files are owner-only; the reader rejects group/other
|
||||
* permission bits, so create temp files 0600 like the real ones. */
|
||||
chmod(path, 0600);
|
||||
return strdup(path);
|
||||
return str_dup(path);
|
||||
}
|
||||
|
||||
static void rm_temp(const char* path) {
|
||||
@@ -44,33 +70,11 @@ static void rm_temp(const char* path) {
|
||||
unlink(path);
|
||||
}
|
||||
|
||||
static void test_credentials_hash_vectors() {
|
||||
char out[CREDENTIAL_HASH_HEX_LEN + 1];
|
||||
EXPECT_TRUE(credentials_hash_password("", out));
|
||||
EXPECT_EQ_STR(out, SHA256_EMPTY);
|
||||
EXPECT_TRUE(credentials_hash_password("secret", out));
|
||||
EXPECT_EQ_STR(out, SHA256_SECRET);
|
||||
EXPECT_TRUE(credentials_hash_password("alice-pass", out));
|
||||
EXPECT_EQ_STR(out, SHA256_ALICE_PASS);
|
||||
EXPECT_FALSE(credentials_hash_password(NULL, out));
|
||||
EXPECT_FALSE(credentials_hash_password("x", NULL));
|
||||
}
|
||||
|
||||
static void test_credentials_hash_valid() {
|
||||
EXPECT_TRUE(credentials_hash_valid(SHA256_SECRET));
|
||||
EXPECT_FALSE(credentials_hash_valid(NULL));
|
||||
EXPECT_FALSE(credentials_hash_valid(""));
|
||||
/* Wrong length. */
|
||||
EXPECT_FALSE(credentials_hash_valid("abc"));
|
||||
EXPECT_FALSE(
|
||||
credentials_hash_valid("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
|
||||
EXPECT_FALSE(
|
||||
credentials_hash_valid("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
|
||||
/* Uppercase hex and non-hex are rejected. */
|
||||
EXPECT_FALSE(
|
||||
credentials_hash_valid("2BB80D537B1DA3E38BD30361AA855686BDE0EACD7162FEF6A25FE97BF527A25B"));
|
||||
EXPECT_FALSE(
|
||||
credentials_hash_valid("gbb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b"));
|
||||
/* Build a valid new-format line for user/password at iters. */
|
||||
static bool make_store_line(const char* user, const char* password, uint32_t iters, char* out,
|
||||
size_t out_sz) {
|
||||
char err[256];
|
||||
return credentials_hash_store_line(user, password, iters, out, out_sz, err, sizeof(err));
|
||||
}
|
||||
|
||||
static void test_credentials_secure_equal() {
|
||||
@@ -78,57 +82,283 @@ static void test_credentials_secure_equal() {
|
||||
EXPECT_TRUE(credentials_secure_equal("", "", 0));
|
||||
EXPECT_FALSE(credentials_secure_equal("abc", "abd", 3));
|
||||
EXPECT_TRUE(credentials_secure_equal("abc", "ab", 2));
|
||||
/* Same prefix, difference at the very last byte must still be detected. */
|
||||
EXPECT_FALSE(credentials_secure_equal(SHA256_SECRET, SHA256_ALICE_PASS, CREDENTIAL_HASH_HEX_LEN));
|
||||
EXPECT_FALSE(credentials_secure_equal("ab", "ac", 2));
|
||||
}
|
||||
|
||||
static void test_credentials_store_parse_valid() {
|
||||
char* path = make_tmp_file(
|
||||
"# server credential store\n"
|
||||
"; another comment style\n"
|
||||
"\n"
|
||||
"alice:9b90e524e94995ee4aeae2ee3c428a53405d1e8db147f44facc46797d0caf4c3\n"
|
||||
" bob : 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b \n"
|
||||
"carol:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\r\n");
|
||||
static void test_credentials_b64() {
|
||||
uint8_t salt[CREDENTIAL_SALT_LEN];
|
||||
ramp(salt, sizeof(salt), 0x00);
|
||||
char encoded[25];
|
||||
EXPECT_TRUE(credentials_b64_encode(salt, sizeof(salt), encoded, sizeof(encoded)));
|
||||
EXPECT_EQ_STR(encoded, KAT_SALT_B64);
|
||||
|
||||
uint8_t decoded[CREDENTIAL_SALT_LEN];
|
||||
size_t decoded_len = 0;
|
||||
EXPECT_TRUE(credentials_b64_decode(encoded, decoded, sizeof(decoded), &decoded_len));
|
||||
EXPECT_EQ_INT((int)decoded_len, CREDENTIAL_SALT_LEN);
|
||||
EXPECT_TRUE(memcmp(decoded, salt, sizeof(salt)) == 0);
|
||||
|
||||
/* Malformed input is refused: bad length, bad alphabet, missing buffer. */
|
||||
EXPECT_FALSE(credentials_b64_decode("abc", decoded, sizeof(decoded), &decoded_len));
|
||||
EXPECT_FALSE(credentials_b64_decode("!!!!", decoded, sizeof(decoded), &decoded_len));
|
||||
EXPECT_FALSE(credentials_b64_decode("", decoded, sizeof(decoded), &decoded_len));
|
||||
EXPECT_FALSE(credentials_b64_decode(KAT_SALT_B64, decoded, 4, &decoded_len));
|
||||
EXPECT_FALSE(credentials_b64_decode(NULL, decoded, sizeof(decoded), &decoded_len));
|
||||
EXPECT_FALSE(credentials_b64_encode(NULL, 3, encoded, sizeof(encoded)));
|
||||
EXPECT_FALSE(credentials_b64_encode(salt, sizeof(salt), encoded, 3));
|
||||
}
|
||||
|
||||
static void test_credentials_random_bytes() {
|
||||
uint8_t a[CREDENTIAL_NONCE_LEN];
|
||||
uint8_t b[CREDENTIAL_NONCE_LEN];
|
||||
EXPECT_TRUE(credentials_random_bytes(a, sizeof(a)));
|
||||
EXPECT_TRUE(credentials_random_bytes(b, sizeof(b)));
|
||||
EXPECT_TRUE(memcmp(a, b, sizeof(a)) != 0);
|
||||
EXPECT_FALSE(credentials_random_bytes(NULL, 4));
|
||||
}
|
||||
|
||||
static void test_credentials_compute_keys_kat() {
|
||||
uint8_t salt[CREDENTIAL_SALT_LEN];
|
||||
ramp(salt, sizeof(salt), 0x00);
|
||||
uint8_t client_key[CREDENTIAL_KEY_LEN];
|
||||
uint8_t stored_key[CREDENTIAL_KEY_LEN];
|
||||
uint8_t server_key[CREDENTIAL_KEY_LEN];
|
||||
EXPECT_TRUE(
|
||||
credentials_compute_keys(KAT_PASSWORD, salt, KAT_ITERS, client_key, stored_key, server_key));
|
||||
uint8_t expect[CREDENTIAL_KEY_LEN];
|
||||
unhex(KAT_CLIENT_KEY, expect, sizeof(expect));
|
||||
EXPECT_TRUE(memcmp(client_key, expect, sizeof(expect)) == 0);
|
||||
unhex(KAT_STORED_KEY, expect, sizeof(expect));
|
||||
EXPECT_TRUE(memcmp(stored_key, expect, sizeof(expect)) == 0);
|
||||
unhex(KAT_SERVER_KEY, expect, sizeof(expect));
|
||||
EXPECT_TRUE(memcmp(server_key, expect, sizeof(expect)) == 0);
|
||||
EXPECT_FALSE(credentials_compute_keys(KAT_PASSWORD, salt, 0, client_key, stored_key, server_key));
|
||||
EXPECT_FALSE(credentials_compute_keys(NULL, salt, KAT_ITERS, client_key, stored_key, server_key));
|
||||
}
|
||||
|
||||
static void test_credentials_auth_message_and_proof_kat() {
|
||||
uint8_t snonce[CREDENTIAL_NONCE_LEN];
|
||||
uint8_t cnonce[CREDENTIAL_NONCE_LEN];
|
||||
ramp(snonce, sizeof(snonce), 0xa0);
|
||||
ramp(cnonce, sizeof(cnonce), 0x10);
|
||||
|
||||
uint8_t auth_msg[CREDENTIAL_AUTH_MESSAGE_MAX];
|
||||
size_t msg_len = 0;
|
||||
EXPECT_TRUE(credentials_build_auth_message(KAT_USER, snonce, cnonce, auth_msg, sizeof(auth_msg),
|
||||
&msg_len));
|
||||
const char* expect_msg =
|
||||
"4661737453796e632d417574682d763100000005616c69636500000020a0a1a2a3a4a5a6a7a8a9aaabac"
|
||||
"adaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf00000020101112131415161718191a1b1c1d1e1f2021"
|
||||
"22232425262728292a2b2c2d2e2f";
|
||||
uint8_t expect[CREDENTIAL_AUTH_MESSAGE_MAX];
|
||||
size_t expect_len = strlen(expect_msg) / 2;
|
||||
unhex(expect_msg, expect, expect_len);
|
||||
EXPECT_EQ_INT((int)msg_len, (int)expect_len);
|
||||
EXPECT_TRUE(memcmp(auth_msg, expect, expect_len) == 0);
|
||||
|
||||
uint8_t client_key[CREDENTIAL_KEY_LEN];
|
||||
uint8_t stored_key[CREDENTIAL_KEY_LEN];
|
||||
uint8_t server_key[CREDENTIAL_KEY_LEN];
|
||||
unhex(KAT_CLIENT_KEY, client_key, sizeof(client_key));
|
||||
unhex(KAT_STORED_KEY, stored_key, sizeof(stored_key));
|
||||
unhex(KAT_SERVER_KEY, server_key, sizeof(server_key));
|
||||
uint8_t proof[CREDENTIAL_KEY_LEN];
|
||||
uint8_t server_sig[CREDENTIAL_KEY_LEN];
|
||||
EXPECT_TRUE(credentials_client_proof(client_key, stored_key, server_key, auth_msg, msg_len, proof,
|
||||
server_sig));
|
||||
unhex(KAT_CLIENT_PROOF, expect, sizeof(expect));
|
||||
EXPECT_TRUE(memcmp(proof, expect, CREDENTIAL_KEY_LEN) == 0);
|
||||
unhex(KAT_SERVER_SIG, expect, sizeof(expect));
|
||||
EXPECT_TRUE(memcmp(server_sig, expect, CREDENTIAL_KEY_LEN) == 0);
|
||||
}
|
||||
|
||||
static CredentialVerifier kat_verifier(void) {
|
||||
CredentialVerifier v;
|
||||
memset(&v, 0, sizeof(v));
|
||||
ramp(v.salt, sizeof(v.salt), 0x00);
|
||||
v.iters = KAT_ITERS;
|
||||
unhex(KAT_STORED_KEY, v.stored_key, sizeof(v.stored_key));
|
||||
unhex(KAT_SERVER_KEY, v.server_key, sizeof(v.server_key));
|
||||
v.found = true;
|
||||
return v;
|
||||
}
|
||||
|
||||
static void test_credentials_verify_response_kat() {
|
||||
CredentialVerifier v = kat_verifier();
|
||||
uint8_t snonce[CREDENTIAL_NONCE_LEN];
|
||||
uint8_t cnonce[CREDENTIAL_NONCE_LEN];
|
||||
ramp(snonce, sizeof(snonce), 0xa0);
|
||||
ramp(cnonce, sizeof(cnonce), 0x10);
|
||||
uint8_t proof[CREDENTIAL_KEY_LEN];
|
||||
uint8_t expect_sig[CREDENTIAL_KEY_LEN];
|
||||
unhex(KAT_CLIENT_PROOF, proof, sizeof(proof));
|
||||
unhex(KAT_SERVER_SIG, expect_sig, sizeof(expect_sig));
|
||||
|
||||
uint8_t server_sig[CREDENTIAL_KEY_LEN];
|
||||
EXPECT_TRUE(credentials_verify_response(&v, KAT_USER, snonce, cnonce, proof, server_sig));
|
||||
EXPECT_TRUE(memcmp(server_sig, expect_sig, CREDENTIAL_KEY_LEN) == 0);
|
||||
|
||||
/* Tampered proof refused. */
|
||||
uint8_t bad[CREDENTIAL_KEY_LEN];
|
||||
memcpy(bad, proof, sizeof(bad));
|
||||
bad[0] ^= 0x01;
|
||||
EXPECT_FALSE(credentials_verify_response(&v, KAT_USER, snonce, cnonce, bad, server_sig));
|
||||
|
||||
/* Unit replay: the same proof bound to a different client nonce is refused. */
|
||||
uint8_t other[CREDENTIAL_NONCE_LEN];
|
||||
memcpy(other, cnonce, sizeof(other));
|
||||
other[0] ^= 0x01;
|
||||
EXPECT_FALSE(credentials_verify_response(&v, KAT_USER, snonce, other, proof, server_sig));
|
||||
/* Different server nonce too. */
|
||||
uint8_t other_server[CREDENTIAL_NONCE_LEN];
|
||||
memcpy(other_server, snonce, sizeof(other_server));
|
||||
other_server[0] ^= 0x01;
|
||||
EXPECT_FALSE(credentials_verify_response(&v, KAT_USER, other_server, cnonce, proof, server_sig));
|
||||
|
||||
/* Wrong user changes the AuthMessage and fails. */
|
||||
EXPECT_FALSE(credentials_verify_response(&v, "bob", snonce, cnonce, proof, server_sig));
|
||||
|
||||
/* found=false never accepts. */
|
||||
v.found = false;
|
||||
EXPECT_FALSE(credentials_verify_response(&v, KAT_USER, snonce, cnonce, proof, server_sig));
|
||||
|
||||
/* NULL arguments fail closed. */
|
||||
v.found = true;
|
||||
EXPECT_FALSE(credentials_verify_response(NULL, KAT_USER, snonce, cnonce, proof, server_sig));
|
||||
EXPECT_FALSE(credentials_verify_response(&v, NULL, snonce, cnonce, proof, server_sig));
|
||||
EXPECT_FALSE(credentials_verify_response(&v, KAT_USER, NULL, cnonce, proof, server_sig));
|
||||
EXPECT_FALSE(credentials_verify_response(&v, KAT_USER, snonce, cnonce, NULL, server_sig));
|
||||
}
|
||||
|
||||
static void test_credentials_username_valid() {
|
||||
EXPECT_TRUE(credentials_username_valid("alice"));
|
||||
EXPECT_TRUE(credentials_username_valid("a"));
|
||||
EXPECT_FALSE(credentials_username_valid(NULL));
|
||||
EXPECT_FALSE(credentials_username_valid(""));
|
||||
EXPECT_FALSE(credentials_username_valid("bad user"));
|
||||
EXPECT_FALSE(credentials_username_valid("tab\there"));
|
||||
EXPECT_FALSE(credentials_username_valid("nul\nhere"));
|
||||
}
|
||||
|
||||
/* A generated line round-trips through the store parser and verifies with the
|
||||
* same password. */
|
||||
static void test_credentials_hash_store_line_roundtrip() {
|
||||
char line[CREDENTIAL_MAX_LINE];
|
||||
EXPECT_TRUE(make_store_line("alice", KAT_PASSWORD, CREDENTIAL_MIN_ITERS, line, sizeof(line)));
|
||||
EXPECT_TRUE(strncmp(line, "alice:", 6) == 0);
|
||||
EXPECT_TRUE(strstr(line, KAT_NAME_PREFIX) != NULL);
|
||||
|
||||
char* path = make_tmp_file(line);
|
||||
EXPECT_NOT_NULL(path);
|
||||
char err[512];
|
||||
CredentialStore* store = credentials_load(path, NULL, err, sizeof(err));
|
||||
EXPECT_NOT_NULL(store);
|
||||
EXPECT_EQ_INT(credentials_store_size(store), 3);
|
||||
EXPECT_EQ_INT(credentials_store_size(store), 1);
|
||||
EXPECT_TRUE(credentials_store_has(store, "alice"));
|
||||
|
||||
CredentialVerifier v;
|
||||
const char* module_users[] = {"alice"};
|
||||
EXPECT_TRUE(credentials_get_verifier(store, "alice", module_users, 1, &v));
|
||||
EXPECT_TRUE(v.found);
|
||||
EXPECT_EQ_INT((int)v.iters, (int)CREDENTIAL_MIN_ITERS);
|
||||
uint8_t client_key[CREDENTIAL_KEY_LEN];
|
||||
uint8_t stored_key[CREDENTIAL_KEY_LEN];
|
||||
uint8_t server_key[CREDENTIAL_KEY_LEN];
|
||||
EXPECT_TRUE(
|
||||
credentials_compute_keys(KAT_PASSWORD, v.salt, v.iters, client_key, stored_key, server_key));
|
||||
EXPECT_TRUE(memcmp(stored_key, v.stored_key, CREDENTIAL_KEY_LEN) == 0);
|
||||
EXPECT_TRUE(memcmp(server_key, v.server_key, CREDENTIAL_KEY_LEN) == 0);
|
||||
|
||||
credentials_free(store);
|
||||
rm_temp(path);
|
||||
free(path);
|
||||
}
|
||||
|
||||
static void test_credentials_store_parse_valid() {
|
||||
char line_alice[CREDENTIAL_MAX_LINE];
|
||||
char line_bob[CREDENTIAL_MAX_LINE];
|
||||
EXPECT_TRUE(
|
||||
make_store_line("alice", KAT_PASSWORD, CREDENTIAL_MIN_ITERS, line_alice, sizeof(line_alice)));
|
||||
EXPECT_TRUE(
|
||||
make_store_line("bob", "bob-s3cret", CREDENTIAL_MIN_ITERS, line_bob, sizeof(line_bob)));
|
||||
char contents[2 * CREDENTIAL_MAX_LINE + 64];
|
||||
snprintf(contents, sizeof(contents), "# server credential store\n; comment\n\n%s\n%s\n",
|
||||
line_alice, line_bob);
|
||||
char* path = make_tmp_file(contents);
|
||||
EXPECT_NOT_NULL(path);
|
||||
char err[512];
|
||||
CredentialStore* store = credentials_load(path, NULL, err, sizeof(err));
|
||||
EXPECT_NOT_NULL(store);
|
||||
EXPECT_EQ_INT(credentials_store_size(store), 2);
|
||||
EXPECT_TRUE(credentials_store_has(store, "alice"));
|
||||
EXPECT_TRUE(credentials_store_has(store, "bob"));
|
||||
EXPECT_TRUE(credentials_store_has(store, "carol"));
|
||||
EXPECT_FALSE(credentials_store_has(store, "mallory"));
|
||||
EXPECT_FALSE(credentials_store_has(store, "ALICE"));
|
||||
EXPECT_TRUE(credentials_verify(store, "alice", SHA256_ALICE_PASS));
|
||||
EXPECT_TRUE(credentials_verify(store, "bob", SHA256_SECRET));
|
||||
EXPECT_TRUE(credentials_verify(store, "carol", SHA256_EMPTY));
|
||||
EXPECT_FALSE(credentials_verify(store, "alice", SHA256_SECRET));
|
||||
EXPECT_FALSE(credentials_verify(store, "mallory", SHA256_ALICE_PASS));
|
||||
|
||||
/* Unknown user and off-list user both yield a not-found dummy. */
|
||||
const char* module_users[] = {"alice"};
|
||||
CredentialVerifier v;
|
||||
EXPECT_TRUE(credentials_get_verifier(store, "mallory", module_users, 1, &v));
|
||||
EXPECT_FALSE(v.found);
|
||||
EXPECT_TRUE(credentials_get_verifier(store, "bob", module_users, 1, &v));
|
||||
EXPECT_FALSE(v.found);
|
||||
EXPECT_TRUE(credentials_get_verifier(store, "alice", module_users, 1, &v));
|
||||
EXPECT_TRUE(v.found);
|
||||
/* The dummy keys are fixed (all zero) so they can never authenticate. */
|
||||
const uint8_t zero[CREDENTIAL_KEY_LEN] = {0};
|
||||
EXPECT_TRUE(credentials_get_verifier(store, "mallory", module_users, 1, &v));
|
||||
EXPECT_TRUE(memcmp(v.stored_key, zero, CREDENTIAL_KEY_LEN) == 0);
|
||||
EXPECT_TRUE(memcmp(v.server_key, zero, CREDENTIAL_KEY_LEN) == 0);
|
||||
/* Miss salt is fresh random on each call. */
|
||||
uint8_t salt_a[CREDENTIAL_SALT_LEN];
|
||||
uint8_t salt_b[CREDENTIAL_SALT_LEN];
|
||||
EXPECT_TRUE(credentials_get_verifier(store, "mallory", module_users, 1, &v));
|
||||
memcpy(salt_a, v.salt, sizeof(salt_a));
|
||||
EXPECT_TRUE(credentials_get_verifier(store, "mallory", module_users, 1, &v));
|
||||
memcpy(salt_b, v.salt, sizeof(salt_b));
|
||||
EXPECT_TRUE(memcmp(salt_a, salt_b, sizeof(salt_a)) != 0);
|
||||
|
||||
credentials_free(store);
|
||||
rm_temp(path);
|
||||
free(path);
|
||||
}
|
||||
|
||||
static void test_credentials_store_parse_rejects_malformed() {
|
||||
/* A valid salt (16 bytes -> 24 b64 chars) / keys (32 bytes -> 44 chars). */
|
||||
uint8_t sixteen[CREDENTIAL_SALT_LEN] = {0};
|
||||
uint8_t thirtytwo[CREDENTIAL_KEY_LEN] = {0};
|
||||
char salt_b64[25];
|
||||
char key_b64[45];
|
||||
credentials_b64_encode(sixteen, sizeof(sixteen), salt_b64, sizeof(salt_b64));
|
||||
credentials_b64_encode(thirtytwo, sizeof(thirtytwo), key_b64, sizeof(key_b64));
|
||||
|
||||
char below_min[CREDENTIAL_MAX_LINE];
|
||||
char above_max[CREDENTIAL_MAX_LINE];
|
||||
char short_salt[CREDENTIAL_MAX_LINE];
|
||||
char short_key[CREDENTIAL_MAX_LINE];
|
||||
char empty_field[CREDENTIAL_MAX_LINE];
|
||||
snprintf(below_min, sizeof(below_min), "alice:$fastsync$1$pbkdf2-sha256$99$%s$%s$%s\n", salt_b64,
|
||||
key_b64, key_b64);
|
||||
snprintf(above_max, sizeof(above_max), "alice:$fastsync$1$pbkdf2-sha256$99999999$%s$%s$%s\n",
|
||||
salt_b64, key_b64, key_b64);
|
||||
snprintf(short_salt, sizeof(short_salt), "alice:$fastsync$1$pbkdf2-sha256$600000$AAAA$%s$%s\n",
|
||||
key_b64, key_b64);
|
||||
snprintf(short_key, sizeof(short_key), "alice:$fastsync$1$pbkdf2-sha256$600000$%s$AAAA$%s\n",
|
||||
salt_b64, key_b64);
|
||||
snprintf(empty_field, sizeof(empty_field), "alice:$fastsync$1$pbkdf2-sha256$600000$%s$%s$\n",
|
||||
salt_b64, key_b64);
|
||||
|
||||
const char* cases[] = {
|
||||
/* no colon */
|
||||
"alice\n",
|
||||
/* empty user */
|
||||
":9b90e524e94995ee4aeae2ee3c428a53405d1e8db147f44facc46797d0caf4c3\n",
|
||||
/* empty secret */
|
||||
"alice:\n",
|
||||
/* secret too short */
|
||||
"alice:8ce9c8b52c5\n",
|
||||
/* secret not hex */
|
||||
"alice:zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz\n",
|
||||
/* uppercase hex rejected (strict) */
|
||||
"alice:2BB80D537B1DA3E38BD30361AA855686BDE0EACD7162FEF6A25FE97BF527A25B\n",
|
||||
/* whitespace inside the username */
|
||||
"ali ce:2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b\n",
|
||||
/* duplicate user within one file */
|
||||
"alice:9b90e524e94995ee4aeae2ee3c428a53405d1e8db147f44facc46797d0caf4c3\n"
|
||||
"alice:2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b\n",
|
||||
":anything\n",
|
||||
"alice:not-a-verifier\n",
|
||||
below_min,
|
||||
above_max,
|
||||
short_salt,
|
||||
short_key,
|
||||
empty_field,
|
||||
"ali "
|
||||
"ce:$fastsync$1$pbkdf2-sha256$600000$AAECAwQFBgcICQoLDA0ODw==$WztIlDcIWhH+"
|
||||
"WUq5kVTaTLTrq0rowu31H7qpJ36fkJk=$OPZoc2IQvU28tRk7mlFMWxBHF07/X1qA7kwrHossdqE=\n",
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
|
||||
char* path = make_tmp_file(cases[i]);
|
||||
@@ -142,6 +372,35 @@ static void test_credentials_store_parse_rejects_malformed() {
|
||||
}
|
||||
}
|
||||
|
||||
static void test_credentials_store_rejects_legacy_hex() {
|
||||
const char* secret = "9b90e524e94995ee4aeae2ee3c428a53405d1e8db147f44facc46797d0caf4c3";
|
||||
char contents[CREDENTIAL_MAX_LINE];
|
||||
snprintf(contents, sizeof(contents), "alice:%s\n", secret);
|
||||
char* path = make_tmp_file(contents);
|
||||
EXPECT_NOT_NULL(path);
|
||||
char err[512];
|
||||
const CredentialStore* store = credentials_load(path, NULL, err, sizeof(err));
|
||||
EXPECT_NULL(store);
|
||||
EXPECT_TRUE(strstr(err, "legacy") != NULL);
|
||||
EXPECT_TRUE(strstr(err, "alice") != NULL);
|
||||
rm_temp(path);
|
||||
free(path);
|
||||
}
|
||||
|
||||
static void test_credentials_store_duplicate_rejected() {
|
||||
char line[CREDENTIAL_MAX_LINE];
|
||||
EXPECT_TRUE(make_store_line("alice", KAT_PASSWORD, CREDENTIAL_MIN_ITERS, line, sizeof(line)));
|
||||
char contents[2 * CREDENTIAL_MAX_LINE + 8];
|
||||
snprintf(contents, sizeof(contents), "%s\n%s\n", line, line);
|
||||
char* path = make_tmp_file(contents);
|
||||
EXPECT_NOT_NULL(path);
|
||||
char err[512];
|
||||
EXPECT_NULL(credentials_load(path, NULL, err, sizeof(err)));
|
||||
EXPECT_TRUE(strstr(err, "duplicate") != NULL);
|
||||
rm_temp(path);
|
||||
free(path);
|
||||
}
|
||||
|
||||
static void test_credentials_store_parse_missing_file() {
|
||||
char err[512];
|
||||
const CredentialStore* store =
|
||||
@@ -152,15 +411,11 @@ static void test_credentials_store_parse_missing_file() {
|
||||
|
||||
static void test_credentials_store_empty_and_null() {
|
||||
char err[512];
|
||||
/* A NULL path is a valid (empty) store: no module can authenticate, which is
|
||||
* the fail-closed state the startup check turns into a refusal to start. */
|
||||
CredentialStore* store = credentials_load(NULL, NULL, err, sizeof(err));
|
||||
EXPECT_NOT_NULL(store);
|
||||
EXPECT_EQ_INT(credentials_store_size(store), 0);
|
||||
EXPECT_FALSE(credentials_verify(store, "alice", SHA256_ALICE_PASS));
|
||||
credentials_free(store);
|
||||
|
||||
/* A blank/comment-only file is an empty store too (not an error). */
|
||||
char* path = make_tmp_file("# nothing here\n; nor here\n");
|
||||
EXPECT_NOT_NULL(path);
|
||||
store = credentials_load(path, NULL, err, sizeof(err));
|
||||
@@ -172,12 +427,8 @@ static void test_credentials_store_empty_and_null() {
|
||||
}
|
||||
|
||||
static void test_credentials_store_overlong_line_rejected() {
|
||||
/* A line longer than CREDENTIAL_MAX_LINE must be rejected. Fill the buffer
|
||||
* fully so the line really is overlong, but keep both a trailing newline and
|
||||
* a NUL terminator at known indices: make_tmp_file does strlen(contents), so
|
||||
* an unterminated stack buffer would be an out-of-bounds read (ASan). */
|
||||
char big[CREDENTIAL_MAX_LINE + 80];
|
||||
int n = snprintf(big, sizeof(big), "alice:%s", SHA256_SECRET);
|
||||
int n = snprintf(big, sizeof(big), "alice:%s", KAT_NAME_PREFIX);
|
||||
memset(big + n, 'a', sizeof(big) - (size_t)n - 1);
|
||||
big[sizeof(big) - 2] = '\n';
|
||||
big[sizeof(big) - 1] = '\0';
|
||||
@@ -191,35 +442,43 @@ static void test_credentials_store_overlong_line_rejected() {
|
||||
}
|
||||
|
||||
static void test_credentials_early_input_merge() {
|
||||
char* pw =
|
||||
make_tmp_file("alice:9b90e524e94995ee4aeae2ee3c428a53405d1e8db147f44facc46797d0caf4c3\n");
|
||||
char alice[CREDENTIAL_MAX_LINE];
|
||||
char bob[CREDENTIAL_MAX_LINE];
|
||||
EXPECT_TRUE(make_store_line("alice", KAT_PASSWORD, CREDENTIAL_MIN_ITERS, alice, sizeof(alice)));
|
||||
EXPECT_TRUE(make_store_line("bob", "bob-s3cret", CREDENTIAL_MIN_ITERS, bob, sizeof(bob)));
|
||||
char alice_file[CREDENTIAL_MAX_LINE + 2];
|
||||
char bob_file[CREDENTIAL_MAX_LINE + 2];
|
||||
char alice_other[CREDENTIAL_MAX_LINE];
|
||||
snprintf(alice_file, sizeof(alice_file), "%s\n", alice);
|
||||
snprintf(bob_file, sizeof(bob_file), "%s\n", bob);
|
||||
EXPECT_TRUE(make_store_line("alice", "different-s3cret", CREDENTIAL_MIN_ITERS, alice_other,
|
||||
sizeof(alice_other)));
|
||||
|
||||
char* pw = make_tmp_file(alice_file);
|
||||
char* early = make_tmp_file(bob_file);
|
||||
char* early_same = make_tmp_file(alice_file); /* byte-identical verifier dedupes */
|
||||
char* early_diff = make_tmp_file(alice_other);
|
||||
EXPECT_NOT_NULL(pw);
|
||||
EXPECT_NOT_NULL(early);
|
||||
EXPECT_NOT_NULL(early_same);
|
||||
EXPECT_NOT_NULL(early_diff);
|
||||
char err[512];
|
||||
|
||||
/* A second file adds a new user. */
|
||||
char* early =
|
||||
make_tmp_file("bob:2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b\n");
|
||||
EXPECT_NOT_NULL(early);
|
||||
CredentialStore* store = credentials_load(pw, early, err, sizeof(err));
|
||||
EXPECT_NOT_NULL(store);
|
||||
EXPECT_EQ_INT(credentials_store_size(store), 2);
|
||||
EXPECT_TRUE(credentials_verify(store, "alice", SHA256_ALICE_PASS));
|
||||
EXPECT_TRUE(credentials_verify(store, "bob", SHA256_SECRET));
|
||||
EXPECT_TRUE(credentials_store_has(store, "alice"));
|
||||
EXPECT_TRUE(credentials_store_has(store, "bob"));
|
||||
credentials_free(store);
|
||||
|
||||
/* The same user with the SAME secret dedupes. */
|
||||
char* early_same =
|
||||
make_tmp_file("alice:9b90e524e94995ee4aeae2ee3c428a53405d1e8db147f44facc46797d0caf4c3\n");
|
||||
EXPECT_NOT_NULL(early_same);
|
||||
/* The same user with the SAME verifier dedupes. */
|
||||
store = credentials_load(pw, early_same, err, sizeof(err));
|
||||
EXPECT_NOT_NULL(store);
|
||||
EXPECT_EQ_INT(credentials_store_size(store), 1);
|
||||
credentials_free(store);
|
||||
|
||||
/* The same user with a DIFFERENT secret fails closed (ambiguous). */
|
||||
char* early_diff =
|
||||
make_tmp_file("alice:2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b\n");
|
||||
EXPECT_NOT_NULL(early_diff);
|
||||
/* The same user with a DIFFERENT verifier fails closed (ambiguous). */
|
||||
store = credentials_load(pw, early_diff, err, sizeof(err));
|
||||
EXPECT_NULL(store);
|
||||
EXPECT_TRUE(err[0] != '\0');
|
||||
@@ -239,7 +498,6 @@ static void test_credentials_read_secret_file() {
|
||||
char* user = NULL;
|
||||
char* password = NULL;
|
||||
|
||||
/* Leading comments/blanks skipped; first real line wins. */
|
||||
char* path = make_tmp_file("# password file\n"
|
||||
"\n"
|
||||
"alice:correct horse battery staple\n"
|
||||
@@ -254,8 +512,6 @@ static void test_credentials_read_secret_file() {
|
||||
rm_temp(path);
|
||||
free(path);
|
||||
|
||||
/* CRLF is tolerated; the username is trimmed but the password's exact bytes
|
||||
* (edge spaces included) are preserved so a whitespace password stays usable. */
|
||||
path = make_tmp_file(" bob : s3cret \r\n");
|
||||
EXPECT_NOT_NULL(path);
|
||||
EXPECT_EQ_INT(credentials_read_secret_file(path, &user, &password, err, sizeof(err)), 0);
|
||||
@@ -267,8 +523,6 @@ static void test_credentials_read_secret_file() {
|
||||
rm_temp(path);
|
||||
free(path);
|
||||
|
||||
/* A whitespace-only password (no characters) is still a real password and is
|
||||
* preserved exactly, not mistaken for an empty line. */
|
||||
path = make_tmp_file("carol: \n");
|
||||
EXPECT_NOT_NULL(path);
|
||||
EXPECT_EQ_INT(credentials_read_secret_file(path, &user, &password, err, sizeof(err)), 0);
|
||||
@@ -280,7 +534,6 @@ static void test_credentials_read_secret_file() {
|
||||
rm_temp(path);
|
||||
free(path);
|
||||
|
||||
/* Empty file / comment-only file rejected. */
|
||||
path = make_tmp_file("");
|
||||
EXPECT_NOT_NULL(path);
|
||||
EXPECT_EQ_INT(credentials_read_secret_file(path, &user, &password, err, sizeof(err)), -1);
|
||||
@@ -294,13 +547,9 @@ static void test_credentials_read_secret_file() {
|
||||
static void test_credentials_read_secret_file_bad() {
|
||||
char err[512];
|
||||
const char* cases[] = {
|
||||
/* no colon */
|
||||
"alicepassword\n",
|
||||
/* empty user */
|
||||
":password\n",
|
||||
/* empty password */
|
||||
"alice:\n",
|
||||
/* empty password after CR-only line ending */
|
||||
"alice:\r\n",
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
|
||||
@@ -320,62 +569,77 @@ static void test_credentials_read_secret_file_bad() {
|
||||
EXPECT_EQ_INT(credentials_read_secret_file(missing, NULL, NULL, err, sizeof(err)), -1);
|
||||
}
|
||||
|
||||
static void test_credentials_gate_allows() {
|
||||
char* path =
|
||||
make_tmp_file("alice:9b90e524e94995ee4aeae2ee3c428a53405d1e8db147f44facc46797d0caf4c3\n"
|
||||
"bob:2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b\n");
|
||||
EXPECT_NOT_NULL(path);
|
||||
static void test_credentials_hash_file() {
|
||||
char* plaintext = make_tmp_file("# comment\n\n alice :" KAT_PASSWORD "\nbob:bob-s3cret\n");
|
||||
EXPECT_NOT_NULL(plaintext);
|
||||
FILE* out = tmpfile();
|
||||
EXPECT_NOT_NULL(out);
|
||||
char err[512];
|
||||
CredentialStore* store = credentials_load(path, NULL, err, sizeof(err));
|
||||
EXPECT_EQ_INT(credentials_hash_file(plaintext, CREDENTIAL_MIN_ITERS, out, err, sizeof(err)), 0);
|
||||
rewind(out);
|
||||
|
||||
char line1[CREDENTIAL_MAX_LINE];
|
||||
char line2[CREDENTIAL_MAX_LINE];
|
||||
EXPECT_NOT_NULL(fgets(line1, sizeof(line1), out));
|
||||
EXPECT_NOT_NULL(fgets(line2, sizeof(line2), out));
|
||||
EXPECT_NULL(fgets(err, sizeof(err), out)); /* exactly two entries */
|
||||
size_t n1 = strlen(line1);
|
||||
if (n1 > 0 && line1[n1 - 1] == '\n')
|
||||
line1[--n1] = '\0';
|
||||
size_t n2 = strlen(line2);
|
||||
if (n2 > 0 && line2[n2 - 1] == '\n')
|
||||
line2[--n2] = '\0';
|
||||
EXPECT_TRUE(strncmp(line1, "alice:", 6) == 0);
|
||||
EXPECT_TRUE(strncmp(line2, "bob:", 4) == 0);
|
||||
EXPECT_TRUE(strstr(line1, KAT_NAME_PREFIX) != NULL);
|
||||
fclose(out);
|
||||
|
||||
/* The generated lines load as a valid store. */
|
||||
char contents[2 * CREDENTIAL_MAX_LINE + 8];
|
||||
snprintf(contents, sizeof(contents), "%s\n%s\n", line1, line2);
|
||||
char* store_path = make_tmp_file(contents);
|
||||
EXPECT_NOT_NULL(store_path);
|
||||
CredentialStore* store = credentials_load(store_path, NULL, err, sizeof(err));
|
||||
EXPECT_NOT_NULL(store);
|
||||
|
||||
const char* module_users[] = {"alice", "bob"};
|
||||
|
||||
/* Matching user + digest passes. */
|
||||
EXPECT_TRUE(credentials_gate_allows(store, module_users, 2, "alice", SHA256_ALICE_PASS));
|
||||
EXPECT_TRUE(credentials_gate_allows(store, module_users, 2, "bob", SHA256_SECRET));
|
||||
/* Wrong digest for a listed user fails. */
|
||||
EXPECT_FALSE(credentials_gate_allows(store, module_users, 2, "alice", SHA256_SECRET));
|
||||
/* A store user that is not on the module's list fails. */
|
||||
EXPECT_FALSE(credentials_gate_allows(store, module_users, 2, "alice", SHA256_ALICE_PASS) &&
|
||||
credentials_gate_allows(store, module_users, 1, "bob", SHA256_SECRET));
|
||||
EXPECT_TRUE(credentials_gate_allows(store, module_users, 1, "alice", SHA256_ALICE_PASS));
|
||||
EXPECT_FALSE(credentials_gate_allows(store, module_users, 1, "bob", SHA256_SECRET));
|
||||
/* No credentials presented fails. */
|
||||
EXPECT_FALSE(credentials_gate_allows(store, module_users, 2, NULL, NULL));
|
||||
EXPECT_FALSE(credentials_gate_allows(store, module_users, 2, "alice", NULL));
|
||||
/* Unknown user fails. */
|
||||
EXPECT_FALSE(credentials_gate_allows(store, module_users, 2, "mallory", SHA256_ALICE_PASS));
|
||||
/* Fail closed: a NULL store refuses even with correct credentials. */
|
||||
EXPECT_FALSE(credentials_gate_allows(NULL, module_users, 2, "alice", SHA256_ALICE_PASS));
|
||||
/* An empty module list refuses everyone. */
|
||||
EXPECT_FALSE(credentials_gate_allows(store, NULL, 0, "alice", SHA256_ALICE_PASS));
|
||||
|
||||
EXPECT_EQ_INT(credentials_store_size(store), 2);
|
||||
credentials_free(store);
|
||||
rm_temp(path);
|
||||
free(path);
|
||||
rm_temp(store_path);
|
||||
free(store_path);
|
||||
rm_temp(plaintext);
|
||||
free(plaintext);
|
||||
|
||||
/* An invalid iteration count is refused up front. */
|
||||
char* p2 = make_tmp_file("alice:pw\n");
|
||||
EXPECT_NOT_NULL(p2);
|
||||
FILE* out2 = tmpfile();
|
||||
EXPECT_NOT_NULL(out2);
|
||||
EXPECT_EQ_INT(credentials_hash_file(p2, 10, out2, err, sizeof(err)), -1);
|
||||
EXPECT_TRUE(err[0] != '\0');
|
||||
fclose(out2);
|
||||
rm_temp(p2);
|
||||
free(p2);
|
||||
}
|
||||
|
||||
static void test_credentials_rejects_group_or_other_accessible() {
|
||||
char err[512];
|
||||
char* path =
|
||||
make_tmp_file("alice:9b90e524e94995ee4aeae2ee3c428a53405d1e8db147f44facc46797d0caf4c3\n");
|
||||
char line[CREDENTIAL_MAX_LINE];
|
||||
EXPECT_TRUE(make_store_line("alice", KAT_PASSWORD, CREDENTIAL_MIN_ITERS, line, sizeof(line)));
|
||||
char contents[CREDENTIAL_MAX_LINE + 2];
|
||||
snprintf(contents, sizeof(contents), "%s\n", line);
|
||||
char* path = make_tmp_file(contents);
|
||||
EXPECT_NOT_NULL(path);
|
||||
|
||||
/* 0600 is accepted by the server store loader. */
|
||||
EXPECT_EQ_INT(chmod(path, 0600), 0);
|
||||
CredentialStore* store = credentials_load(path, NULL, err, sizeof(err));
|
||||
EXPECT_NOT_NULL(store);
|
||||
credentials_free(store);
|
||||
|
||||
/* Group-readable and world-readable are both refused, with a clear error. */
|
||||
EXPECT_EQ_INT(chmod(path, 0640), 0);
|
||||
EXPECT_NULL(credentials_load(path, NULL, err, sizeof(err)));
|
||||
EXPECT_TRUE(strstr(err, "owner-only") != NULL);
|
||||
EXPECT_EQ_INT(chmod(path, 0604), 0);
|
||||
EXPECT_NULL(credentials_load(path, NULL, err, sizeof(err)));
|
||||
|
||||
/* The client --password-file reader enforces the same rule. */
|
||||
EXPECT_EQ_INT(chmod(path, 0644), 0);
|
||||
char* user = NULL;
|
||||
char* password = NULL;
|
||||
@@ -384,9 +648,7 @@ static void test_credentials_rejects_group_or_other_accessible() {
|
||||
EXPECT_NULL(password);
|
||||
EXPECT_TRUE(strstr(err, "owner-only") != NULL);
|
||||
|
||||
/* An --early-input file is checked too. */
|
||||
char* pw =
|
||||
make_tmp_file("bob:2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b\n");
|
||||
char* pw = make_tmp_file("bob:bob-s3cret\n");
|
||||
EXPECT_NOT_NULL(pw);
|
||||
EXPECT_EQ_INT(chmod(path, 0644), 0);
|
||||
EXPECT_NULL(credentials_load(pw, path, err, sizeof(err)));
|
||||
@@ -403,22 +665,29 @@ static void test_credentials_burn() {
|
||||
credentials_burn(secret, 16);
|
||||
for (int i = 0; i < 16; i++)
|
||||
EXPECT_EQ_INT(secret[i], 0);
|
||||
credentials_burn(NULL, 0); /* must not crash */
|
||||
credentials_burn(NULL, 0);
|
||||
}
|
||||
|
||||
void test_credentials(void) {
|
||||
test_credentials_hash_vectors();
|
||||
test_credentials_hash_valid();
|
||||
test_credentials_secure_equal();
|
||||
test_credentials_b64();
|
||||
test_credentials_random_bytes();
|
||||
test_credentials_compute_keys_kat();
|
||||
test_credentials_auth_message_and_proof_kat();
|
||||
test_credentials_verify_response_kat();
|
||||
test_credentials_username_valid();
|
||||
test_credentials_hash_store_line_roundtrip();
|
||||
test_credentials_store_parse_valid();
|
||||
test_credentials_store_parse_rejects_malformed();
|
||||
test_credentials_store_rejects_legacy_hex();
|
||||
test_credentials_store_duplicate_rejected();
|
||||
test_credentials_store_parse_missing_file();
|
||||
test_credentials_store_empty_and_null();
|
||||
test_credentials_store_overlong_line_rejected();
|
||||
test_credentials_early_input_merge();
|
||||
test_credentials_read_secret_file();
|
||||
test_credentials_read_secret_file_bad();
|
||||
test_credentials_hash_file();
|
||||
test_credentials_rejects_group_or_other_accessible();
|
||||
test_credentials_gate_allows();
|
||||
test_credentials_burn();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user