Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Support for the HHIT and BRID RR types.
- Support for the "docpath", "pvd" and "oots" SVCB Service Parameters
- Recognize DNSSEC algorithms ED25519, ED448, SM2SM3 (RFC 9563) and
ECC-GOST12 (RFC 9558) by mnemonic. Based on a patch by Igor V. Ruzanov.

### Fixed

- Clarify DS digest algorithm 5 comment as GOST R 34.11-2012 (RFC 9558).

## [0.2.5] - 2026-07-07

Expand Down
176 changes: 140 additions & 36 deletions scripts/algorithm-hash.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* hash.c -- Calculate perfect hash for DNSSEC algorithms
* algorithm-hash.c -- Calculate perfect hash for DNSSEC algorithms
*
* Copyright (c) 2023, NLnet Labs. All rights reserved.
*
Expand All @@ -15,60 +15,164 @@ typedef struct tuple tuple_t;
struct tuple {
char name[24];
uint8_t code;
int array_index; // index in algorithms[] (0 = BAD placeholder)
};

// // https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml
// https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml
// Sparse algorithms[32]: index matches value where possible; 9 and 11 reserved;
// ED448/SM2SM3 at 16/17; ECC-GOST12 at 23; INDIRECT/PRIVATEDNS/PRIVATEOID after.
static const tuple_t algorithms[] = {
{ "RSAMD5", 1 },
{ "DH", 2 },
{ "DSA", 3 },
{ "ECC", 4 },
{ "RSASHA1", 5 },
{ "DSA-NSEC-SHA1", 6 },
{ "RSASHA1-NSEC3-SHA1", 7 },
{ "RSASHA256", 8 },
{ "RSASHA512", 10 },
{ "ECC-GOST", 12 },
{ "ECDSAP256SHA256", 13 },
{ "ECDSAP384SHA384", 14 },
{ "INDIRECT", 252 },
{ "PRIVATEDNS", 253 },
{ "PRIVATEOID", 254 }
{ "RSAMD5", 1, 1 },
{ "DH", 2, 2 },
{ "DSA", 3, 3 },
{ "ECC", 4, 4 },
{ "RSASHA1", 5, 5 },
{ "DSA-NSEC-SHA1", 6, 6 },
{ "RSASHA1-NSEC3-SHA1", 7, 7 },
{ "RSASHA256", 8, 8 },
{ "RSASHA512", 10, 10 },
{ "ECC-GOST", 12, 12 },
{ "ECDSAP256SHA256", 13, 13 },
{ "ECDSAP384SHA384", 14, 14 },
{ "ED25519", 15, 15 },
{ "ED448", 16, 16 },
{ "SM2SM3", 17, 17 },
{ "ECC-GOST12", 23, 23 },
{ "INDIRECT", 252, 24 },
{ "PRIVATEDNS", 253, 25 },
{ "PRIVATEOID", 254, 26 }
};

const uint64_t original_magic = 29874llu;
// Hash table size must be a power of two; 32 fits the current set.
#define HASH_BITS 5
#define HASH_SIZE (1u << HASH_BITS)
#define HASH_MASK (HASH_SIZE - 1)

static uint64_t
name_key(const char *name, size_t length)
{
char upper[8];
uint64_t value;
size_t i;

memset(upper, 0, sizeof(upper));
for (i = 0; i < length && i < 8; i++) {
unsigned char c = (unsigned char)name[i];
if (c >= 'a' && c <= 'z')
c = (unsigned char)(c - 32);
upper[i] = (char)c;
}
memcpy(&value, upper, 8);
// Include length so ECC-GOST and ECC-GOST12 do not collide.
value ^= ((uint64_t)length << 56);
return value;
}

static uint8_t hash(uint64_t magic, uint64_t value)
{
uint32_t value32 = ((value >> 32) ^ value);
return (value32 * magic) >> 32;
uint32_t value32 = (uint32_t)((value >> 32) ^ value);
return (uint8_t)((value32 * magic) >> 32) & HASH_MASK;
}

static void
print_mask(const char *name, size_t length)
{
size_t i;
printf(" { ");
for (i = 0; i < 24; i++) {
if (i && (i % 8) == 0)
printf("\n ");
if (i < length) {
unsigned char c = (unsigned char)name[i];
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))
printf("0xdf");
else
printf("0xff");
} else {
printf("0");
}
if (i != 23)
printf(", ");
}
printf(" }");
}

int main(int argc, char *argv[])
{
const size_t n = sizeof(algorithms)/sizeof(algorithms[0]);
for (uint64_t magic = original_magic; magic < UINT64_MAX; magic++) {
size_t i;
uint16_t keys[256] = { 0 };
for (i=0; i < n; i++) {
uint64_t value;
memcpy(&value, algorithms[i].name, 8);
uint64_t magic;
(void)argc;
(void)argv;

uint8_t key = hash(magic, value);
if (keys[key & 0xf])
for (magic = 1; magic < UINT64_MAX; magic++) {
size_t i;
uint8_t seen[HASH_SIZE];
memset(seen, 0, sizeof(seen));
for (i = 0; i < n; i++) {
size_t length = strlen(algorithms[i].name);
uint8_t key = hash(magic, name_key(algorithms[i].name, length));
if (seen[key])
break;
keys[key & 0xf] = 1;
seen[key] = 1;
}

if (i == n) {
printf("i: %zu, magic: %" PRIu64 "\n", i, magic);
for (i=0; i < n; i++) {
uint64_t value;
memcpy(&value, algorithms[i].name, 8);
uint8_t key = hash(magic, value);
printf("%s: %" PRIu8 " (%" PRIu16 ")\n", algorithms[i].name, key & 0xf, algorithms[i].code);
int slot_to_idx[HASH_SIZE];
size_t s;

printf("// magic: %" PRIu64 "\n", magic);
for (i = 0; i < n; i++) {
size_t length = strlen(algorithms[i].name);
uint8_t key = hash(magic, name_key(algorithms[i].name, length));
printf("// %s -> slot %u (code %u, index %d)\n",
algorithms[i].name, key, algorithms[i].code,
algorithms[i].array_index);
}

printf("\n--- algorithms array ---\n");
printf("static const algorithm_t algorithms[32] = {\n");
{
int filled[32] = {0};
for (i = 0; i < n; i++)
filled[algorithms[i].array_index] = (int)i + 1;
for (s = 0; s < 32; s++) {
if (filled[s]) {
int idx = filled[s] - 1;
printf(" ALGORITHM(\"%s\", %u),\n",
algorithms[idx].name, algorithms[idx].code);
} else {
printf(" BAD_ALGORITHM(%zu),\n", s);
}
}
}
printf("};\n");

for (s = 0; s < HASH_SIZE; s++)
slot_to_idx[s] = -1;
for (i = 0; i < n; i++) {
size_t length = strlen(algorithms[i].name);
uint8_t key = hash(magic, name_key(algorithms[i].name, length));
slot_to_idx[key] = (int)i;
}

printf("\n--- hash map ---\n");
printf("} algorithm_hash_map[%u] = {\n", HASH_SIZE);
for (s = 0; s < HASH_SIZE; s++) {
if (slot_to_idx[s] < 0) {
printf(" { &algorithms[0], // unknown (%zu)\n", s);
printf(" { 0 } }");
} else {
int idx = slot_to_idx[s];
printf(" { &algorithms[%d], // %s (%zu)\n",
algorithms[idx].array_index, algorithms[idx].name, s);
print_mask(algorithms[idx].name, strlen(algorithms[idx].name));
}
if (s + 1 < HASH_SIZE)
printf(",\n");
else
printf("\n");
}
//print_table(magic);
printf("};\n");
return 0;
}
}
Expand Down
120 changes: 84 additions & 36 deletions src/generic/algorithm.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,70 +41,117 @@ static const algorithm_t algorithms[32] = {
ALGORITHM("ECC-GOST", 12),
ALGORITHM("ECDSAP256SHA256", 13),
ALGORITHM("ECDSAP384SHA384", 14),
BAD_ALGORITHM(15),
ALGORITHM("ED25519", 15),
ALGORITHM("ED448", 16),
ALGORITHM("SM2SM3", 17),
BAD_ALGORITHM(18),
BAD_ALGORITHM(19),
BAD_ALGORITHM(20),
BAD_ALGORITHM(21),
BAD_ALGORITHM(22),
ALGORITHM("ECC-GOST12", 23),
ALGORITHM("INDIRECT", 252),
ALGORITHM("PRIVATEDNS", 253),
ALGORITHM("PRIVATEOID", 254),
BAD_ALGORITHM(27),
BAD_ALGORITHM(28),
BAD_ALGORITHM(29),
BAD_ALGORITHM(30),
BAD_ALGORITHM(31),
};

static const struct {
const algorithm_t *algorithm;
uint8_t mask[24];
} algorithm_hash_map[16] = {
{ &algorithms[2], // DH (0)
{ 0xdf, 0xdf, 0 } },
{ &algorithms[10], // RSASHA512 (1)
{ 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xff, 0xff,
0xff, 0 } },
{ &algorithms[7], // RSASHA1-NSEC3-SHA1 (2)
{ 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xff, 0xff,
0xdf, 0xdf, 0xdf, 0xdf, 0xff, 0xff, 0xdf, 0xdf,
0xdf, 0xff, 0 } },
{ &algorithms[8], // RSASHA256 (3)
} algorithm_hash_map[32] = {
{ &algorithms[0], // unknown (0)
{ 0 } },
{ &algorithms[15], // ED25519 (1)
{ 0xdf, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0 } },
{ &algorithms[0], // unknown (2)
{ 0 } },
{ &algorithms[0], // unknown (3)
{ 0 } },
{ &algorithms[8], // RSASHA256 (4)
{ 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xff, 0xff,
0xff, 0 } },
{ &algorithms[13], // ECDSAP256SHA256 (4)
{ 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xff, 0xff,
0xff, 0xdf, 0xdf, 0xdf, 0xff, 0xff, 0xff, 0 } },
{ &algorithms[0], // unknown
{ 0 } },
{ &algorithms[6], // DSA-NSEC-SHA1 (6)
{ 0xdf, 0xdf, 0xdf, 0xff, 0xdf, 0xdf, 0xdf, 0xdf,
0xff, 0xdf, 0xdf, 0xdf, 0xff, 0 } },
{ &algorithms[1], // RSAMD5 (7)
{ &algorithms[1], // RSAMD5 (5)
{ 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xff, 0 } },
{ &algorithms[5], // RSASHA1 (8)
{ &algorithms[0], // unknown (6)
{ 0 } },
{ &algorithms[5], // RSASHA1 (7)
{ 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xff, 0 } },
{ &algorithms[17], // PRIVATEDNS (9)
{ 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf,
0xdf, 0xdf, 0 } },
{ &algorithms[18], // PRIVATEOID (10)
{ 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf,
0xdf, 0xdf, 0 } },
{ &algorithms[16], // INDIRECT (11)
{ &algorithms[24], // INDIRECT (8)
{ 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf,
0 } },
{ &algorithms[14], // ECDSAP384SHA384 (12)
{ &algorithms[14], // ECDSAP384SHA384 (9)
{ 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xff, 0xff,
0xff, 0xdf, 0xdf, 0xdf, 0xff, 0xff, 0xff, 0 } },
{ &algorithms[0], // unknown (10)
{ 0 } },
{ &algorithms[10], // RSASHA512 (11)
{ 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xff, 0xff,
0xff, 0 } },
{ &algorithms[26], // PRIVATEOID (12)
{ 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf,
0xdf, 0xdf, 0 } },
{ &algorithms[3], // DSA (13)
{ 0xdf, 0xdf, 0xdf, 0 } },
{ &algorithms[4], // ECC (14)
{ 0xdf, 0xdf, 0xdf, 0 } },
{ &algorithms[12], // ECC-GHOST (15)
{ &algorithms[13], // ECDSAP256SHA256 (15)
{ 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xff, 0xff,
0xff, 0xdf, 0xdf, 0xdf, 0xff, 0xff, 0xff, 0 } },
{ &algorithms[0], // unknown (16)
{ 0 } },
{ &algorithms[17], // SM2SM3 (17)
{ 0xdf, 0xdf, 0xff, 0xdf, 0xdf, 0xff, 0 } },
{ &algorithms[0], // unknown (18)
{ 0 } },
{ &algorithms[25], // PRIVATEDNS (19)
{ 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf,
0xdf, 0xdf, 0 } },
{ &algorithms[6], // DSA-NSEC-SHA1 (20)
{ 0xdf, 0xdf, 0xdf, 0xff, 0xdf, 0xdf, 0xdf, 0xdf,
0xff, 0xdf, 0xdf, 0xdf, 0xff, 0 } },
{ &algorithms[0], // unknown (21)
{ 0 } },
{ &algorithms[16], // ED448 (22)
{ 0xdf, 0xdf, 0xff, 0xff, 0xff, 0 } },
{ &algorithms[23], // ECC-GOST12 (23)
{ 0xdf, 0xdf, 0xdf, 0xff, 0xdf, 0xdf, 0xdf, 0xdf,
0 } }
0xff, 0xff, 0 } },
{ &algorithms[0], // unknown (24)
{ 0 } },
{ &algorithms[7], // RSASHA1-NSEC3-SHA1 (25)
{ 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xff, 0xff,
0xdf, 0xdf, 0xdf, 0xdf, 0xff, 0xff, 0xdf, 0xdf,
0xdf, 0xff, 0 } },
{ &algorithms[12], // ECC-GOST (26)
{ 0xdf, 0xdf, 0xdf, 0xff, 0xdf, 0xdf, 0xdf, 0xdf,
0 } },
{ &algorithms[0], // unknown (27)
{ 0 } },
{ &algorithms[2], // DH (28)
{ 0xdf, 0xdf, 0 } },
{ &algorithms[0], // unknown (29)
{ 0 } },
{ &algorithms[0], // unknown (30)
{ 0 } },
{ &algorithms[0], // unknown (31)
{ 0 } }
};

#undef UNKNOWN_ALGORITHM
#undef ALGORITHM

// magic value generated using algorithm-hash.c
// magic value generated using algorithm-hash.c (includes name length so
// ECC-GOST and ECC-GOST12 do not collide on the first eight bytes)
static uint8_t algorithm_hash(uint64_t value)
{
value = le64toh(value);
uint32_t value32 = (uint32_t)((value >> 32) ^ value);
return (uint8_t)((value32 * 29874llu) >> 32) & 0xf;
return (uint8_t)((value32 * 3611llu) >> 32) & 0x1f;
}

nonnull_all
Expand All @@ -131,8 +178,9 @@ static really_inline int32_t scan_algorithm(
uint64_t zero_mask;
memcpy(&zero_mask, &zero_masks[32 - (length & 0x1f)], 8);
input &= zero_mask;
const uint8_t index = algorithm_hash(input);
assert(index < 16);
// include length so ECC-GOST and ECC-GOST12 hash differently
const uint8_t index = algorithm_hash(input ^ ((uint64_t)length << 56));
assert(index < 32);
const algorithm_t *algorithm = algorithm_hash_map[index].algorithm;
uint64_t matches, mask, name;
// compare bytes 0-7
Expand Down
Loading
Loading