fix(credentials): length-check before legacy-hex scan (C9)

secret_is_legacy_hex indexed s[0..63] without first checking the string
length, reading out of bounds for a shorter secret.  Require
strlen(s) == 64 before scanning, and add a unit test that short and
63-hex-digit secrets are rejected as ordinary malformed verifiers (never
misreported as legacy).
This commit is contained in:
2026-09-14 16:09:25 +02:00
parent 551c187005
commit 80c1ff321c
2 changed files with 31 additions and 2 deletions
+2 -2
View File
@@ -158,13 +158,13 @@ static int hex_value(char c) {
* digits. Such a line is refused loudly (and never accepted) so an operator
* cannot keep a replayable bearer digest in place after the protocol bump. */
static bool secret_is_legacy_hex(const char* s) {
if (!s)
if (!s || strlen(s) != 64)
return false;
for (int i = 0; i < 64; i++) {
if (hex_value(s[i]) < 0)
return false;
}
return s[64] == '\0';
return true;
}
bool credentials_b64_encode(const uint8_t* in, size_t n, char* out, size_t out_sz) {
+29
View File
@@ -473,6 +473,34 @@ static void test_credentials_store_rejects_legacy_hex() {
free(path);
}
/* C9: the legacy-hex detector must check the length before indexing 64 bytes, so
* a short secret is never read out of bounds. Such a line is rejected for the
* ordinary "expected verifier" reason, never as legacy. */
static void test_credentials_store_rejects_short_secret() {
char contents[CREDENTIAL_MAX_LINE];
snprintf(contents, sizeof(contents), "alice:%s\n", "abc");
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 unsalted") == NULL);
rm_temp(path);
free(path);
char short_hex[64];
memset(short_hex, 'a', 63);
short_hex[63] = '\0';
snprintf(contents, sizeof(contents), "alice:%s\n", short_hex);
path = make_tmp_file(contents);
EXPECT_NOT_NULL(path);
store = credentials_load(path, NULL, err, sizeof(err));
EXPECT_NULL(store);
EXPECT_TRUE(strstr(err, "legacy unsalted") == 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)));
@@ -1066,6 +1094,7 @@ void test_credentials(void) {
test_credentials_store_parse_valid();
test_credentials_store_parse_rejects_malformed();
test_credentials_store_rejects_legacy_hex();
test_credentials_store_rejects_short_secret();
test_credentials_store_duplicate_rejected();
test_credentials_store_rejects_nonuniform_iters();
test_credentials_store_parse_missing_file();