From 4d0ec137dd444eab724b4f857e5ce40ec5d71019 Mon Sep 17 00:00:00 2001 From: Jared Mauch Date: Sun, 26 Jul 2026 20:23:07 -0400 Subject: [PATCH] Recognize SM2SM3 and ECC-GOST12 DNSSEC algorithms Add ED25519, ED448, SM2SM3 (RFC 9563) and ECC-GOST12 (RFC 9558) mnemonics to the zone parser. Regenerate the algorithm perfect hash (with name length) so ECC-GOST and ECC-GOST12 do not collide. Keep the algorithms[32] sparse layout with BAD_ALGORITHM placeholders for reserved values 9 and 11. Based on a patch by Igor V. Ruzanov for NLnetLabs/nsd#473. --- CHANGELOG.md | 6 ++ scripts/algorithm-hash.c | 176 +++++++++++++++++++++++++++++++-------- src/generic/algorithm.h | 120 ++++++++++++++++++-------- src/generic/types.h | 4 +- tests/semantics.c | 2 +- 5 files changed, 233 insertions(+), 75 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c093f8de..4bc1309b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/scripts/algorithm-hash.c b/scripts/algorithm-hash.c index aac10637..4ff07660 100644 --- a/scripts/algorithm-hash.c +++ b/scripts/algorithm-hash.c @@ -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. * @@ -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; } } diff --git a/src/generic/algorithm.h b/src/generic/algorithm.h index b3a1ba85..e935a285 100644 --- a/src/generic/algorithm.h +++ b/src/generic/algorithm.h @@ -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 @@ -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 diff --git a/src/generic/types.h b/src/generic/types.h index 18c8e8b0..61e3e7a3 100644 --- a/src/generic/types.h +++ b/src/generic/types.h @@ -1385,7 +1385,7 @@ static int32_t check_ds_rr( 32, // 2: SHA-256 32, // 3: GOST R 34.11-94 48, // 4: SHA-384 - 32, // 5: GOST R 34.10-2012 + 32, // 5: GOST R 34.11-2012 32, // 6: SM3 0 // 7: Unassigned }; @@ -1435,7 +1435,7 @@ static int32_t parse_ds_rdata( 32, // 2: SHA-256 32, // 3: GOST R 34.11-94 48, // 4: SHA-384 - 32, // 5: GOST R 34.10-2012 + 32, // 5: GOST R 34.11-2012 32, // 6: SM3 0 // 7: Unassigned }; diff --git a/tests/semantics.c b/tests/semantics.c index df85a35b..410b5274 100644 --- a/tests/semantics.c +++ b/tests/semantics.c @@ -99,7 +99,7 @@ void ds_digest_lengths(void **state) { 4, 48, ZONE_SUCCESS }, { 4, 47, ZONE_SEMANTIC_ERROR }, { 4, 49, ZONE_SEMANTIC_ERROR }, - // 5: GOST R 34.10-2012 + // 5: GOST R 34.11-2012 { 5, 32, ZONE_SUCCESS }, { 5, 31, ZONE_SEMANTIC_ERROR }, { 5, 33, ZONE_SEMANTIC_ERROR },