From e6573fc33797517c2f6fab5bb8e730c73b7f4aef Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 14 Apr 2023 14:05:15 +0200 Subject: [PATCH 01/43] - xfr-tsig, create util/tsig.c and util/tsig.h. --- Makefile.in | 5 ++- util/tsig.c | 43 +++++++++++++++++++ util/tsig.h | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 util/tsig.c create mode 100644 util/tsig.h diff --git a/Makefile.in b/Makefile.in index bc021aa1e..1b5bc12ee 100644 --- a/Makefile.in +++ b/Makefile.in @@ -130,7 +130,7 @@ util/fptr_wlist.c util/locks.c util/log.c util/mini_event.c util/module.c \ util/netevent.c util/net_help.c util/random.c util/rbtree.c util/regional.c \ util/rtt.c util/edns.c util/storage/dnstree.c util/storage/lookup3.c \ util/storage/lruhash.c util/storage/slabhash.c util/tcp_conn_limit.c \ -util/timehist.c util/tube.c util/proxy_protocol.c \ +util/timehist.c util/tsig.c util/tube.c util/proxy_protocol.c \ util/ub_event.c util/ub_event_pluggable.c util/winsock_event.c \ validator/autotrust.c validator/val_anchor.c validator/validator.c \ validator/val_kcache.c validator/val_kentry.c validator/val_neg.c \ @@ -147,7 +147,7 @@ iter_scrub.lo iter_utils.lo localzone.lo mesh.lo modstack.lo view.lo \ outbound_list.lo alloc.lo config_file.lo configlexer.lo configparser.lo \ fptr_wlist.lo edns.lo locks.lo log.lo mini_event.lo module.lo net_help.lo \ random.lo rbtree.lo regional.lo rtt.lo dnstree.lo lookup3.lo lruhash.lo \ -slabhash.lo tcp_conn_limit.lo timehist.lo tube.lo winsock_event.lo \ +slabhash.lo tcp_conn_limit.lo timehist.lo tsig.lo tube.lo winsock_event.lo \ autotrust.lo val_anchor.lo rpz.lo proxy_protocol.lo \ validator.lo val_kcache.lo val_kentry.lo val_neg.lo val_nsec3.lo val_nsec.lo \ val_secalgo.lo val_sigcrypt.lo val_utils.lo dns64.lo $(CACHEDB_OBJ) authzone.lo \ @@ -700,6 +700,7 @@ depend: # build rules ipset.lo ipset.o: $(srcdir)/ipset/ipset.c +tsig.lo tsig.o: $(srcdir)/util/tsig.c config.h $(srcdir)/util/tsig.h # Dependencies dns.lo dns.o: $(srcdir)/services/cache/dns.c config.h $(srcdir)/iterator/iter_delegpt.h $(srcdir)/util/log.h \ diff --git a/util/tsig.c b/util/tsig.c new file mode 100644 index 000000000..d8cafacba --- /dev/null +++ b/util/tsig.c @@ -0,0 +1,43 @@ +/* + * util/tsig.c - handle TSIG signatures. + * + * Copyright (c) 2023, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This file contains functions for dealing with TSIG records and signatures. + */ + +#include "config.h" +#include "util/tsig.h" diff --git a/util/tsig.h b/util/tsig.h new file mode 100644 index 000000000..e4613badd --- /dev/null +++ b/util/tsig.h @@ -0,0 +1,118 @@ +/* + * util/tsig.h - handle TSIG signatures. + * + * Copyright (c) 2023, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * This file contains functions for dealing with TSIG records and signatures. + */ + +#ifndef UTIL_TSIG_H +#define UTIL_TSIG_H + +/** + * TSIG record, the RR that is in the packet. + * The RR Type is TSIG and the RR class is CLASS_ANY. The TTL is 0. + */ +struct tsig_record { + /** domain name of the RR, the key name. */ + uint8_t* key_name; + /** length of the key_name */ + size_t key_name_len; + /** the algorithm name, as a domain name. */ + uint8_t* algorithm_name; + /** length of the algorithm_name */ + size_t algorithm_name_len; + /** the signed time, high part */ + uint16_t signed_time_high; + /** the signed time, low part */ + uint32_t signed_time_low; + /** the fudge time */ + uint16_t fudge_time; + /** the mac size, uint16_t on the wire */ + size_t mac_size; + /** the mac data */ + uint8_t* mac_data; + /** the original query id */ + uint16_t original_query_id; + /** the tsig error code */ + uint16_t error_code; + /** length of the other data, uint16_t on the wire */ + size_t other_size; + /** the other data */ + uint8_t* other_data; +}; + +/** + * TSIG algorithm. This is the HMAC algorithm used for the TSIG mac. + */ +struct tsig_algorithm { + /** Short name of the algorithm, like "hmac-md5" */ + char* short_name; + /** + * Full wireformat name of the algorith, such as + * "hmac-md5.sig-alg.reg.int." + */ + uint8_t* wireformat_name; + /** length of the wireformat_name */ + size_t wireformat_name_len; +}; + +/** + * TSIG key. This is used to sign and verify packets. + */ +struct tsig_key { + /** name of the key as string */ + char* name_str; + /** algorithm string */ + char* algo_str; + /** the algorithm structure */ + struct tsig_algorithm* algo; + /** + * Name of the key, in wireformat. + * The key name has to be transferred as a domain name, of the TSIG + * RR and thus the key name has to be a wireformat domain name. + */ + uint8_t* name; + /** length of name */ + size_t name_len; + /** the data, with the secret portion of the key. decoded from the + * base64 string with the secret. */ + uint8_t* data; + /** the size of the data */ + size_t data_len; +}; + +#endif /* UTIL_TSIG_H */ From 7edc1e0fc40613c940d92fd668bb21e3f2a44624 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 12 Jun 2025 09:25:54 +0200 Subject: [PATCH 02/43] - xfr-tsig, import the tsig verify code from hackathon/poisonlicious branch. --- sldns/sbuffer.h | 38 +++++++++++++++++ util/tsig.c | 108 ++++++++++++++++++++++++++++++++++++++++++++++++ util/tsig.h | 9 ++++ 3 files changed, 155 insertions(+) diff --git a/sldns/sbuffer.h b/sldns/sbuffer.h index 1b7fe370c..ce995a8f7 100644 --- a/sldns/sbuffer.h +++ b/sldns/sbuffer.h @@ -56,6 +56,18 @@ sldns_read_uint32(const void *src) #endif } +INLINE uint64_t +sldns_read_uint48(const void *src) +{ + const uint8_t *p = (const uint8_t *) src; + return ( ((uint64_t) p[0] << 40) + | ((uint64_t) p[1] << 32) + | ((uint64_t) p[2] << 24) + | ((uint64_t) p[3] << 16) + | ((uint64_t) p[4] << 8) + | (uint64_t) p[5]); +} + /* * Copy data allowing for unaligned accesses in network byte order * (big endian). @@ -693,6 +705,32 @@ sldns_buffer_read_u32(sldns_buffer *buffer) return result; } +/** + * returns the 6-byte integer value at the given position in the buffer + * \param[in] buffer the buffer + * \param[in] at position in the buffer + * \return 6 byte integer + */ +INLINE uint64_t +sldns_buffer_read_u48_at(sldns_buffer *buffer, size_t at) +{ + assert(sldns_buffer_available_at(buffer, at, 6)); + return sldns_read_uint48(buffer->_data + at); +} + +/** + * returns the 6-byte integer value at the current position in the buffer + * \param[in] buffer the buffer + * \return 6 byte integer + */ +INLINE uint64_t +sldns_buffer_read_u48(sldns_buffer *buffer) +{ + uint64_t result = sldns_buffer_read_u48_at(buffer, buffer->_position); + buffer->_position += 6; + return result; +} + /** * returns the status of the buffer * \param[in] buffer diff --git a/util/tsig.c b/util/tsig.c index d8cafacba..053d6ff3e 100644 --- a/util/tsig.c +++ b/util/tsig.c @@ -41,3 +41,111 @@ #include "config.h" #include "util/tsig.h" +#include "util/log.h" +#include "sldns/pkthdr.h" +#include "sldns/rrdef.h" +#include "sldns/sbuffer.h" +#include "util/data/msgparse.h" +#include "util/data/dname.h" +#include +#include + + +/** + * Verify pkt with the name (domain name), algorithm and key in Base64. + * out 0 on success, an error code otherwise. + */ +int +tsig_verify(sldns_buffer* pkt, const uint8_t* name, const uint8_t* alg, + const uint8_t* secret, size_t secret_len, uint64_t now) +{ + size_t n_rrs; + size_t end_of_message; + uint16_t rdlength; + uint8_t mac[1024]; + uint16_t mac_sz; + uint8_t hmac_result[1024]; + unsigned int hmac_result_len; + size_t pos; + uint16_t other_len; + size_t algname_size; + uint64_t time_signed; + uint16_t fudge; + const EVP_MD* digester; + + assert(LDNS_QDCOUNT(sldns_buffer_begin(pkt)) == 1); + + if(LDNS_ARCOUNT(sldns_buffer_begin(pkt)) < 1) { + log_err("No TSIG found (ARCOUNT == 0)"); + return -1; + } + LDNS_ARCOUNT_SET( sldns_buffer_begin(pkt) + , LDNS_ARCOUNT(sldns_buffer_begin(pkt)) - 1); + n_rrs = LDNS_ANCOUNT(sldns_buffer_begin(pkt)) + + LDNS_NSCOUNT(sldns_buffer_begin(pkt)) + + LDNS_ARCOUNT(sldns_buffer_begin(pkt)); + + sldns_buffer_rewind(pkt); + sldns_buffer_skip(pkt, LDNS_HEADER_SIZE); + pkt_dname_len(pkt); /* skip qname */ + sldns_buffer_skip(pkt, 2 * sizeof(uint16_t)); /* skip type and class */ + if(!skip_pkt_rrs(pkt, n_rrs)) /* skip all rrs */ + return -1; + end_of_message = sldns_buffer_position(pkt); + if(query_dname_compare(name, sldns_buffer_current(pkt))) + return LDNS_TSIG_ERROR_BADKEY; + pkt_dname_len(pkt); /* skip TSIG name */ + pos = sldns_buffer_position(pkt); /* Append pos */ + if(sldns_buffer_read_u16(pkt) != LDNS_RR_TYPE_TSIG) { + log_err("No TSIG found!"); + return -1; + } + sldns_buffer_skip(pkt, sizeof(uint16_t) /* skip class */ + + sizeof(uint32_t) /* skip TTLS */ + + sizeof(uint16_t)); /* skip rdlength */ + if(query_dname_compare(alg, sldns_buffer_current(pkt))) + return LDNS_TSIG_ERROR_BADKEY; + algname_size = pkt_dname_len(pkt); /* skip alg name */ + time_signed = sldns_buffer_read_u48(pkt); /* time signed */ + fudge = sldns_buffer_read_u16(pkt); /* fudge */ + mac_sz = sldns_buffer_read_u16(pkt); /* mac size */ + memcpy(mac,sldns_buffer_current(pkt),mac_sz); /* copy mac */ + sldns_buffer_skip(pkt, mac_sz); /* skip mac */ + /* Set ID to original ID */ + sldns_buffer_write_u16_at(pkt, 0, sldns_buffer_read_u16(pkt)); + sldns_buffer_skip(pkt, sizeof(uint16_t)); /* skip error */ + other_len = sldns_buffer_read_u16(pkt); /* other len */ + + /* move CLASS (uint16_t) and TTL (uint32_t) -> TYPE position */ + memmove( sldns_buffer_at(pkt, pos) + , sldns_buffer_at(pkt, pos + sizeof(uint16_t) /* type */) + , sizeof(uint16_t) + sizeof(uint32_t)); + pos += sizeof(uint16_t) + sizeof(uint32_t); + + /* Append Algorithm Name, Time signed (uint48_t), Fudge (uint16_t) */ + memmove( sldns_buffer_at(pkt, pos) + , sldns_buffer_at(pkt, pos + sizeof(uint16_t) /* type */ + + sizeof(uint16_t) /* rdlength */) + , algname_size + 6 /* sizeof(uint48_t) */ + sizeof(uint16_t)); + pos += algname_size + 6 /* sizeof(uint48_t) */ + sizeof(uint16_t); + + /* Append Error (uint16_t), Other Len (uint16_t), Other Data */ + memmove( sldns_buffer_at(pkt, pos) + , sldns_buffer_at(pkt, sldns_buffer_position(pkt) + - 2 * sizeof(uint16_t)) + , 2 * sizeof(uint16_t) + other_len); + pos += 2 * sizeof(uint16_t) + other_len; + + digester = EVP_sha256(); + hmac_result_len = sizeof(hmac_result); + HMAC( digester, secret, secret_len, sldns_buffer_begin(pkt), pos + , hmac_result, &hmac_result_len); + if(memcmp(mac, hmac_result, hmac_result_len) == 0) { + return now > time_signed ? + ( time_signed - now > fudge ? LDNS_TSIG_ERROR_BADTIME : 0 ) + : now - time_signed > fudge ? LDNS_TSIG_ERROR_BADTIME : 0 ; + sldns_buffer_set_position(pkt, end_of_message); + return 0; + } + return LDNS_TSIG_ERROR_BADSIG; +} diff --git a/util/tsig.h b/util/tsig.h index e4613badd..f0f10f99b 100644 --- a/util/tsig.h +++ b/util/tsig.h @@ -41,6 +41,7 @@ #ifndef UTIL_TSIG_H #define UTIL_TSIG_H +struct sldns_buffer; /** * TSIG record, the RR that is in the packet. @@ -115,4 +116,12 @@ struct tsig_key { size_t data_len; }; +/** + * Verify pkt with the name (domain name), algorithm and key. + * out 0 on success, an error code otherwise. + */ +int tsig_verify(struct sldns_buffer* pkt, const uint8_t* name, + const uint8_t* alg, const uint8_t* secret, size_t secret_len, + uint64_t now); + #endif /* UTIL_TSIG_H */ From ea0973002f7e1b8e6c9b44a996c65742154ece6b Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 12 Jun 2025 09:34:07 +0200 Subject: [PATCH 03/43] - xfr-tsig, constant time memcmp is used. --- util/tsig.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/tsig.c b/util/tsig.c index 053d6ff3e..4e774d891 100644 --- a/util/tsig.c +++ b/util/tsig.c @@ -140,7 +140,7 @@ tsig_verify(sldns_buffer* pkt, const uint8_t* name, const uint8_t* alg, hmac_result_len = sizeof(hmac_result); HMAC( digester, secret, secret_len, sldns_buffer_begin(pkt), pos , hmac_result, &hmac_result_len); - if(memcmp(mac, hmac_result, hmac_result_len) == 0) { + if(CRYPTO_memcmp(mac, hmac_result, hmac_result_len) == 0) { return now > time_signed ? ( time_signed - now > fudge ? LDNS_TSIG_ERROR_BADTIME : 0 ) : now - time_signed > fudge ? LDNS_TSIG_ERROR_BADTIME : 0 ; From 4fd0d84e66f05e86eb69df46ee5153a257aa2bb8 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 12 Jun 2025 09:49:20 +0200 Subject: [PATCH 04/43] - xfr-tsig, update header comment. --- util/tsig.c | 2 +- util/tsig.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/util/tsig.c b/util/tsig.c index 4e774d891..f8a40fdee 100644 --- a/util/tsig.c +++ b/util/tsig.c @@ -36,7 +36,7 @@ /** * \file * - * This file contains functions for dealing with TSIG records and signatures. + * This file provides functions to create and verify TSIG RRs. */ #include "config.h" diff --git a/util/tsig.h b/util/tsig.h index f0f10f99b..21bb18878 100644 --- a/util/tsig.h +++ b/util/tsig.h @@ -36,7 +36,7 @@ /** * \file * - * This file contains functions for dealing with TSIG records and signatures. + * This file provides functions to create and verify TSIG RRs. */ #ifndef UTIL_TSIG_H From eefb417c09505a6bb04a15934ff7381f1b904dec Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 12 Jun 2025 09:53:56 +0200 Subject: [PATCH 05/43] - xfr-tsig, const for dname compare and fix warnings in compile. --- util/data/dname.c | 2 +- util/data/dname.h | 2 +- util/tsig.c | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/util/data/dname.c b/util/data/dname.c index d29a8e034..750f16cff 100644 --- a/util/data/dname.c +++ b/util/data/dname.c @@ -97,7 +97,7 @@ dname_valid(uint8_t* dname, size_t maxlen) /** compare uncompressed, noncanonical, registers are hints for speed */ int -query_dname_compare(register uint8_t* d1, register uint8_t* d2) +query_dname_compare(const register uint8_t* d1, const register uint8_t* d2) { register uint8_t lab1, lab2; log_assert(d1 && d2); diff --git a/util/data/dname.h b/util/data/dname.h index f68c64a03..9bb4ba363 100644 --- a/util/data/dname.h +++ b/util/data/dname.h @@ -96,7 +96,7 @@ void pkt_dname_tolower(struct sldns_buffer* pkt, uint8_t* dname); * @return: -1, 0, or +1 depending on comparison results. * Sort order is first difference found. not the canonical ordering. */ -int query_dname_compare(uint8_t* d1, uint8_t* d2); +int query_dname_compare(const uint8_t* d1, const uint8_t* d2); /** * Determine correct, compressed, dname present in packet. diff --git a/util/tsig.c b/util/tsig.c index f8a40fdee..a1253eda1 100644 --- a/util/tsig.c +++ b/util/tsig.c @@ -61,7 +61,6 @@ tsig_verify(sldns_buffer* pkt, const uint8_t* name, const uint8_t* alg, { size_t n_rrs; size_t end_of_message; - uint16_t rdlength; uint8_t mac[1024]; uint16_t mac_sz; uint8_t hmac_result[1024]; From 182e580fe24f2bec4e6493e61b0ee1d76cdafacc Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 12 Jun 2025 09:57:23 +0200 Subject: [PATCH 06/43] - xfr-tsig, fix warning in compile of declaration. --- util/data/dname.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/data/dname.c b/util/data/dname.c index 750f16cff..7d368377c 100644 --- a/util/data/dname.c +++ b/util/data/dname.c @@ -97,7 +97,7 @@ dname_valid(uint8_t* dname, size_t maxlen) /** compare uncompressed, noncanonical, registers are hints for speed */ int -query_dname_compare(const register uint8_t* d1, const register uint8_t* d2) +query_dname_compare(register const uint8_t* d1, register const uint8_t* d2) { register uint8_t lab1, lab2; log_assert(d1 && d2); From 19492da15498f0905d02102b73972b3ea52ac4c0 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 12 Jun 2025 11:50:11 +0200 Subject: [PATCH 07/43] - xfr-tsig, check buffer remaining in tsig verify. --- util/tsig.c | 80 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 69 insertions(+), 11 deletions(-) diff --git a/util/tsig.c b/util/tsig.c index a1253eda1..b0796f402 100644 --- a/util/tsig.c +++ b/util/tsig.c @@ -50,6 +50,43 @@ #include #include +/** + * Skip packet query rr. + * @param pkt: the packet, position before the rr, ends after the rr. + * @return 0 on failure. + */ +static int +skip_pkt_query_rr(struct sldns_buffer* pkt) +{ + /* skip qname */ + if(sldns_buffer_remaining(pkt) < 1) + return 0; + if(!pkt_dname_len(pkt)) + return 0; /* malformed qname */ + if(sldns_buffer_remaining(pkt) < 4) + return 0; + /* skip type and class */ + sldns_buffer_skip(pkt, 2 * sizeof(uint16_t)); + return 1; +} + +/** + * Skip the packet query rrs. The position must be after the header. + * @param pkt: the packet. The end position is after the number of query + * section records. + * @param num: Limit of the number of records we want to parse. + * @return 1 on success, 0 on failure. + */ +static int +skip_pkt_query_rrs(struct sldns_buffer* pkt, int num) +{ + int i; + for(i=0; i fudge ? LDNS_TSIG_ERROR_BADTIME : 0 ) : now - time_signed > fudge ? LDNS_TSIG_ERROR_BADTIME : 0 ; sldns_buffer_set_position(pkt, end_of_message); + /* The TSIG has verified. */ return 0; } + sldns_buffer_set_position(pkt, end_of_message); return LDNS_TSIG_ERROR_BADSIG; } From 3f378c962fbf9b50ab8b0441d45a3e71be65d574 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 12 Jun 2025 14:34:56 +0200 Subject: [PATCH 08/43] - xfr-tsig, check rdata length in tsig verify. --- util/tsig.c | 120 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 106 insertions(+), 14 deletions(-) diff --git a/util/tsig.c b/util/tsig.c index b0796f402..59ea4b671 100644 --- a/util/tsig.c +++ b/util/tsig.c @@ -108,11 +108,14 @@ tsig_verify(sldns_buffer* pkt, const uint8_t* name, const uint8_t* alg, uint64_t time_signed; uint16_t fudge; const EVP_MD* digester; - uint8_t* tsig_name; + uint8_t* tsig_name, *tsig_algo; uint16_t rdlength; + uint16_t current_query_id; - if(sldns_buffer_limit(pkt) < LDNS_HEADER_SIZE) + if(sldns_buffer_limit(pkt) < LDNS_HEADER_SIZE) { + verbose(VERB_ALGO, "No TSIG, packet too short"); return -1; + } if(LDNS_ARCOUNT(sldns_buffer_begin(pkt)) < 1) { verbose(VERB_ALGO, "No TSIG found, ARCOUNT == 0"); return -1; @@ -127,50 +130,136 @@ tsig_verify(sldns_buffer* pkt, const uint8_t* name, const uint8_t* alg, sldns_buffer_skip(pkt, LDNS_HEADER_SIZE); /* Skip qnames. */ - if(!skip_pkt_query_rrs(pkt, LDNS_QDCOUNT(sldns_buffer_begin(pkt)))) + if(!skip_pkt_query_rrs(pkt, LDNS_QDCOUNT(sldns_buffer_begin(pkt)))) { + verbose(VERB_ALGO, "No TSIG, query section RRs malformed"); return -1; + } /* Skip all rrs. */ - if(!skip_pkt_rrs(pkt, n_rrs)) + if(!skip_pkt_rrs(pkt, n_rrs)) { + verbose(VERB_ALGO, "No TSIG, packet RRs are malformed"); return -1; + } end_of_message = sldns_buffer_position(pkt); /* Skip TSIG name. */ - if(sldns_buffer_remaining(pkt) < 1) + if(sldns_buffer_remaining(pkt) < 1) { + verbose(VERB_ALGO, "No TSIG, no tsig name"); + sldns_buffer_set_position(pkt, end_of_message); return -1; + } tsig_name = sldns_buffer_current(pkt); - if(!pkt_dname_len(pkt)) + if(!pkt_dname_len(pkt)) { + verbose(VERB_ALGO, "No TSIG, tsig name malformed"); + sldns_buffer_set_position(pkt, end_of_message); return -1; - if(dname_pkt_compare(pkt, tsig_name, (uint8_t*)name) != 0) + } + if(dname_pkt_compare(pkt, tsig_name, (uint8_t*)name) != 0) { + sldns_buffer_set_position(pkt, end_of_message); return LDNS_TSIG_ERROR_BADKEY; + } pos = sldns_buffer_position(pkt); /* Append pos */ /* Skip type, class, TTL and rdlength */ - if(sldns_buffer_remaining(pkt) < 2+2+4+2 /* type class TTL rdlen */) + if(sldns_buffer_remaining(pkt) < 2 /* type */ + 2 /* class */ + + 4 /* TTL */ + 2 /* rdlength */) { + verbose(VERB_ALGO, "No TSIG, TSIG RR malformed, packet too short"); + sldns_buffer_set_position(pkt, end_of_message); return -1; + } if(sldns_buffer_read_u16(pkt) != LDNS_RR_TYPE_TSIG) { verbose(VERB_ALGO, "No TSIG found, wrong RR type"); + sldns_buffer_set_position(pkt, end_of_message); return -1; } sldns_buffer_skip(pkt, sizeof(uint16_t) /* skip class */ + sizeof(uint32_t)); /* skip TTL */ rdlength = sldns_buffer_read_u16(pkt); /* read rdlength */ - if(sldns_buffer_remaining(pkt) < rdlength) + if(sldns_buffer_remaining(pkt) < rdlength) { + verbose(VERB_ALGO, "TSIG rdlength wrong, packet too short"); + sldns_buffer_set_position(pkt, end_of_message); return -1; + } /* Read the TSIG rdata. */ - if(query_dname_compare(alg, sldns_buffer_current(pkt))) + if(rdlength < 18 /* Rdata length with '.' names, zero lengths. */) { + verbose(VERB_ALGO, "TSIG rdata too short"); + sldns_buffer_set_position(pkt, end_of_message); + return -1; + } + tsig_algo = sldns_buffer_current(pkt); + /* The algorithm name MUST be uncompressed wireformat (RFC8945), + * check that it is. Fails on malformed and skips the name. */ + if(!(algname_size = query_dname_len(pkt))) { + verbose(VERB_ALGO, "TSIG algorithm name malformed"); + sldns_buffer_set_position(pkt, end_of_message); + return -1; + } + if(query_dname_compare(alg, tsig_algo) != 0) { + sldns_buffer_set_position(pkt, end_of_message); return LDNS_TSIG_ERROR_BADKEY; - algname_size = pkt_dname_len(pkt); /* skip alg name */ + } + if(rdlength < algname_size + 6 /* time */ + 2 /* fudge */ + + 2 /* mac size */) { + verbose(VERB_ALGO, "TSIG rdata too short for timestamp"); + sldns_buffer_set_position(pkt, end_of_message); + return -1; + } time_signed = sldns_buffer_read_u48(pkt); /* time signed */ fudge = sldns_buffer_read_u16(pkt); /* fudge */ mac_sz = sldns_buffer_read_u16(pkt); /* mac size */ + + if(rdlength < algname_size + 6 /* time */ + 2 /* fudge */ + + 2 /* mac size */ + mac_sz + 2 /* origid */ + + 2 /* error_code */ + 2 /* otherlen */) { + verbose(VERB_ALGO, "TSIG rdata too short for mac"); + sldns_buffer_set_position(pkt, end_of_message); + return -1; + } + if(mac_sz > 16384) { + /* the hash should not be too big, really 512/8=64 bytes */ + verbose(VERB_ALGO, "TSIG mac too large"); + sldns_buffer_set_position(pkt, end_of_message); + return -1; + } + if(mac_sz > sizeof(mac)) { + verbose(VERB_ALGO, "TSIG mac too large for buffer"); + sldns_buffer_set_position(pkt, end_of_message); + return -1; + } memcpy(mac,sldns_buffer_current(pkt),mac_sz); /* copy mac */ sldns_buffer_skip(pkt, mac_sz); /* skip mac */ + /* Set ID to original ID */ + current_query_id = sldns_buffer_read_u16_at(pkt, 0); sldns_buffer_write_u16_at(pkt, 0, sldns_buffer_read_u16(pkt)); sldns_buffer_skip(pkt, sizeof(uint16_t)); /* skip error */ other_len = sldns_buffer_read_u16(pkt); /* other len */ + if(rdlength < algname_size + 6 /* time */ + 2 /* fudge */ + + 2 /* mac size */ + mac_sz + 2 /* origid */ + + 2 /* error_code */ + 2 /* otherlen */ + other_len) { + verbose(VERB_ALGO, "TSIG rdata too short for Other Data"); + sldns_buffer_write_u16_at(pkt, 0, current_query_id); + sldns_buffer_set_position(pkt, end_of_message); + return -1; + } + if(rdlength > algname_size + 6 /* time */ + 2 /* fudge */ + + 2 /* mac size */ + mac_sz + 2 /* origid */ + + 2 /* error_code */ + 2 /* otherlen */ + other_len) { + /* Trailing data in TSIG RR */ + verbose(VERB_ALGO, "TSIG rdata wrong, it has trailing data"); + sldns_buffer_write_u16_at(pkt, 0, current_query_id); + sldns_buffer_set_position(pkt, end_of_message); + return -1; + } + if(other_len > 16) { + verbose(VERB_ALGO, "TSIG Other Len too large"); + sldns_buffer_write_u16_at(pkt, 0, current_query_id); + sldns_buffer_set_position(pkt, end_of_message); + return -1; + } + /* The buffer for the packet is used for the digest buffer + * for this message. It fits in the buffer. */ /* move CLASS (uint16_t) and TTL (uint32_t) -> TYPE position */ memmove( sldns_buffer_at(pkt, pos) , sldns_buffer_at(pkt, pos + sizeof(uint16_t) /* type */) @@ -196,13 +285,16 @@ tsig_verify(sldns_buffer* pkt, const uint8_t* name, const uint8_t* alg, HMAC( digester, secret, secret_len, sldns_buffer_begin(pkt), pos , hmac_result, &hmac_result_len); if(CRYPTO_memcmp(mac, hmac_result, hmac_result_len) == 0) { + /* Restore the query id of the packet. */ + sldns_buffer_write_u16_at(pkt, 0, current_query_id); + sldns_buffer_set_position(pkt, end_of_message); + /* The TSIG has verified. */ return now > time_signed ? ( time_signed - now > fudge ? LDNS_TSIG_ERROR_BADTIME : 0 ) : now - time_signed > fudge ? LDNS_TSIG_ERROR_BADTIME : 0 ; - sldns_buffer_set_position(pkt, end_of_message); - /* The TSIG has verified. */ - return 0; } + /* Restore the query id of the packet. */ + sldns_buffer_write_u16_at(pkt, 0, current_query_id); sldns_buffer_set_position(pkt, end_of_message); return LDNS_TSIG_ERROR_BADSIG; } From 3d9242b3d36b42e5baae236548be459541832e24 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 12 Jun 2025 16:05:10 +0200 Subject: [PATCH 09/43] - xfr-tsig, key table. --- Makefile.in | 2 +- util/fptr_wlist.c | 2 ++ util/tsig.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++ util/tsig.h | 38 ++++++++++++++++++++++++++-- 4 files changed, 102 insertions(+), 3 deletions(-) diff --git a/Makefile.in b/Makefile.in index de02a4e8e..f2efa5d2c 100644 --- a/Makefile.in +++ b/Makefile.in @@ -970,7 +970,7 @@ fptr_wlist.lo fptr_wlist.o: $(srcdir)/util/fptr_wlist.c config.h $(srcdir)/util/ $(srcdir)/util/netevent.h $(srcdir)/dnscrypt/dnscrypt.h \ $(srcdir)/util/storage/lruhash.h $(srcdir)/util/locks.h $(srcdir)/util/log.h $(srcdir)/util/module.h \ $(srcdir)/util/data/msgreply.h $(srcdir)/util/data/packed_rrset.h $(srcdir)/util/data/msgparse.h \ - $(srcdir)/sldns/pkthdr.h $(srcdir)/sldns/rrdef.h $(srcdir)/util/tube.h $(srcdir)/services/mesh.h $(srcdir)/util/rbtree.h \ + $(srcdir)/sldns/pkthdr.h $(srcdir)/sldns/rrdef.h $(srcdir)/util/tsig.h $(srcdir)/util/tube.h $(srcdir)/services/mesh.h $(srcdir)/util/rbtree.h \ $(srcdir)/services/modstack.h $(srcdir)/services/rpz.h $(srcdir)/services/localzone.h \ $(srcdir)/util/storage/dnstree.h $(srcdir)/services/view.h $(srcdir)/sldns/sbuffer.h \ $(srcdir)/util/config_file.h $(srcdir)/services/authzone.h $(srcdir)/daemon/stats.h $(srcdir)/util/timehist.h \ diff --git a/util/fptr_wlist.c b/util/fptr_wlist.c index c6f3ca24a..464ab0138 100644 --- a/util/fptr_wlist.c +++ b/util/fptr_wlist.c @@ -72,6 +72,7 @@ #include "libunbound/libworker.h" #include "libunbound/context.h" #include "libunbound/worker.h" +#include "util/tsig.h" #include "util/tube.h" #include "util/config_file.h" #include "daemon/remote.h" @@ -262,6 +263,7 @@ fptr_whitelist_rbtree_cmp(int (*fptr) (const void *, const void *)) else if(fptr == &auth_zone_cmp) return 1; else if(fptr == &auth_data_cmp) return 1; else if(fptr == &auth_xfer_cmp) return 1; + else if(fptr == &tsig_key_compare) return 1; #ifdef HAVE_NGTCP2 else if(fptr == &doq_conn_cmp) return 1; else if(fptr == &doq_conid_cmp) return 1; diff --git a/util/tsig.c b/util/tsig.c index 59ea4b671..bde331926 100644 --- a/util/tsig.c +++ b/util/tsig.c @@ -50,6 +50,69 @@ #include #include +int +tsig_key_compare(const void* v1, const void* v2) +{ + struct tsig_key* a = (struct tsig_key*)v1; + struct tsig_key* b = (struct tsig_key*)v2; + + return query_dname_compare(a->name, b->name); +} + +struct tsig_key_table* +tsig_key_table_create(void) +{ + struct tsig_key_table* key_table; + key_table = (struct tsig_key_table*)calloc(1, sizeof(*key_table)); + if(!key_table) + return NULL; + key_table->tree = rbtree_create(&tsig_key_compare); + if(!key_table->tree) { + free(key_table); + return NULL; + } + lock_rw_init(&key_table->lock); + lock_protect(&key_table->lock, key_table->tree, + sizeof(*key_table->tree)); + return key_table; +} + +/** Delete the tsig key table key. */ +static void +tsig_key_table_delete_key(rbnode_type* node, void* ATTR_UNUSED(arg)) +{ + struct tsig_key* key = (struct tsig_key*)node->key; + tsig_key_delete(key); +} + +void +tsig_key_table_delete(struct tsig_key_table* key_table) +{ + if(!key_table) + return; + lock_rw_destroy(&key_table->lock); + if(key_table->tree) { + traverse_postorder(key_table->tree, &tsig_key_table_delete_key, + NULL); + free(key_table->tree); + } + free(key_table); +} + +void tsig_key_delete(struct tsig_key* key) +{ + if(!key) + return; + free(key->name_str); + free(key->name); + if(key->data) { + /* The secret data is removed. */ + explicit_bzero(key->data, key->data_len); + free(key->data); + } + free(key); +} + /** * Skip packet query rr. * @param pkt: the packet, position before the rr, ends after the rr. diff --git a/util/tsig.h b/util/tsig.h index 21bb18878..4b49fa242 100644 --- a/util/tsig.h +++ b/util/tsig.h @@ -41,6 +41,8 @@ #ifndef UTIL_TSIG_H #define UTIL_TSIG_H +#include "util/locks.h" +#include "util/rbtree.h" struct sldns_buffer; /** @@ -95,10 +97,10 @@ struct tsig_algorithm { * TSIG key. This is used to sign and verify packets. */ struct tsig_key { + /** the rbtree node */ + rbnode_type node; /** name of the key as string */ char* name_str; - /** algorithm string */ - char* algo_str; /** the algorithm structure */ struct tsig_algorithm* algo; /** @@ -116,6 +118,35 @@ struct tsig_key { size_t data_len; }; +/** + * The TSIG key storage. Keys are stored by name. + * They are read from config. + */ +struct tsig_key_table { + /* Lock on the tsig key table and all keys. */ + lock_rw_type lock; + /* Tree of tsig keys, by wireformat name. */ + struct rbtree_type* tree; +}; + +/** + * Create TSIG key table. + * @return NULL on alloc failure. + */ +struct tsig_key_table* tsig_key_table_create(void); + +/** + * Delete TSIG key table. And the keys in it. + * @param key_table: to delete. + */ +void tsig_key_table_delete(struct tsig_key_table* key_table); + +/** + * Delete TSIG key. + * @param key: to delete + */ +void tsig_key_delete(struct tsig_key* key); + /** * Verify pkt with the name (domain name), algorithm and key. * out 0 on success, an error code otherwise. @@ -124,4 +155,7 @@ int tsig_verify(struct sldns_buffer* pkt, const uint8_t* name, const uint8_t* alg, const uint8_t* secret, size_t secret_len, uint64_t now); +/** Compare function for the key table keys. */ +int tsig_key_compare(const void* v1, const void* v2); + #endif /* UTIL_TSIG_H */ From 364edccebcfc787e9590937d84afa2453661a557 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 13 Jun 2025 10:15:41 +0200 Subject: [PATCH 10/43] - xfr-tsig, algorithm table. --- util/tsig.c | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++ util/tsig.h | 19 +++++++++ 2 files changed, 129 insertions(+) diff --git a/util/tsig.c b/util/tsig.c index bde331926..3d8243b44 100644 --- a/util/tsig.c +++ b/util/tsig.c @@ -50,6 +50,21 @@ #include #include +/** + * The list of TSIG algorithms. It has short_name, wireformat_name, + * wireformat_name_len, digest, max_digest_size. + */ +static struct tsig_algorithm tsig_algorithm_table[] = { + { "hmac-md5", + (uint8_t*)"\x08hmac-md5\x07sig-alg\x03reg\x03int\x00", 26, + "md5", 16 }, + { "hmac-sha1", (uint8_t*)"\x09hmac-sha1\x00", 11, "sha1", 20 }, + { "hmac-sha224", (uint8_t*)"\x0Bhmac-sha224\x00", 13, "sha224", 28 }, + { "hmac-sha256", (uint8_t*)"\x0Bhmac-sha256\x00", 13, "sha256", 32 }, + { "hmac-sha384", (uint8_t*)"\x0Bhmac-sha384\x00", 13, "sha384", 48 }, + { "hmac-sha512", (uint8_t*)"\x0Bhmac-sha512\x00", 13, "sha512", 64 } +}; + int tsig_key_compare(const void* v1, const void* v2) { @@ -113,6 +128,101 @@ void tsig_key_delete(struct tsig_key* key) free(key); } +int +tsig_algo_check_name(const char* algo_name) +{ + /* It is either the long name for md5, "hmac-md5.sig-alg.reg.int." + * or a short name, "hmac-sha256", or a digest name "sha256". + * The name is case insensitive. */ + if(strncasecmp(algo_name, "hmac-", 5) == 0) { + /* The name starts with 'hmac-'. */ + if(strncasecmp(algo_name+5, "sha", 3) == 0) { + /* sha1 */ + if(strcasecmp(algo_name+8, "1") == 0 || + strcasecmp(algo_name+8, "1.") == 0) + return 1; + /* sha224 */ + if(strcasecmp(algo_name+8, "224") == 0 || + strcasecmp(algo_name+8, "224.") == 0) + return 1; + /* sha256 */ + if(strcasecmp(algo_name+8, "256") == 0 || + strcasecmp(algo_name+8, "256.") == 0) + return 1; + /* sha384 */ + if(strcasecmp(algo_name+8, "384") == 0 || + strcasecmp(algo_name+8, "384.") == 0) + return 1; + /* sha512 */ + if(strcasecmp(algo_name+8, "512") == 0 || + strcasecmp(algo_name+8, "512.") == 0) + return 1; + } + if(strncasecmp(algo_name+5, "md5", 3) == 0) { + /* 'hmac-md5' or 'hmac-md5.' */ + if(strcasecmp(algo_name+8, "") == 0 || + strcasecmp(algo_name+8, ".") == 0) + return 1; + if(strcasecmp(algo_name, + "hmac-md5.sig-alg.reg.int.") == 0 + || strcasecmp(algo_name, + "hmac-md5.sig-alg.reg.int") == 0) + return 1; + } + } + if(strncasecmp(algo_name, "sha", 3) == 0) { + if(strcasecmp(algo_name+3, "1") == 0) + return 1; + if(strcasecmp(algo_name+3, "224") == 0) + return 1; + if(strcasecmp(algo_name+3, "256") == 0) + return 1; + if(strcasecmp(algo_name+3, "384") == 0) + return 1; + if(strcasecmp(algo_name+3, "512") == 0) + return 1; + } + if(strcasecmp(algo_name, "md5") == 0) + return 1; + return 0; +} + +struct tsig_algorithm* +tsig_algo_find_name(const char* algo_name) +{ + size_t i; + char buf[40]; + const char* lookfor = algo_name; + if(algo_name == NULL || algo_name[0] == 0 || + strlen(algo_name)+5 >= sizeof(buf)) + return NULL; + if(strncasecmp(algo_name, "hmac-", 5) != 0) { + snprintf(buf, sizeof(buf), "hmac-%s", algo_name); + lookfor = buf; + if(buf[strlen(buf)-1] == '.') { + /* Remove trailing '.' */ + buf[strlen(buf)-1] = 0; + } + } else { + if(algo_name[strlen(algo_name)-1] == '.') { + /* Remove trailing '.' */ + snprintf(buf, sizeof(buf), "%s", algo_name); + buf[strlen(buf)-1] = 0; + lookfor = buf; + } + if(strcasecmp(lookfor, "hmac-md5.sig-alg.reg.int") == 0) + lookfor = "hmac-md5"; + } + + for(i=0; i Date: Fri, 13 Jun 2025 10:17:47 +0200 Subject: [PATCH 11/43] - xfr-tsig, fix algorithm lookup. --- util/tsig.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/util/tsig.c b/util/tsig.c index 3d8243b44..81309699d 100644 --- a/util/tsig.c +++ b/util/tsig.c @@ -199,10 +199,6 @@ tsig_algo_find_name(const char* algo_name) if(strncasecmp(algo_name, "hmac-", 5) != 0) { snprintf(buf, sizeof(buf), "hmac-%s", algo_name); lookfor = buf; - if(buf[strlen(buf)-1] == '.') { - /* Remove trailing '.' */ - buf[strlen(buf)-1] = 0; - } } else { if(algo_name[strlen(algo_name)-1] == '.') { /* Remove trailing '.' */ @@ -211,9 +207,9 @@ tsig_algo_find_name(const char* algo_name) lookfor = buf; } if(strcasecmp(lookfor, "hmac-md5.sig-alg.reg.int") == 0) - lookfor = "hmac-md5"; + lookfor = "hmac-md5"; /* Look for short name. */ } - + for(i=0; i Date: Fri, 13 Jun 2025 12:12:49 +0200 Subject: [PATCH 12/43] - xfr-tsig, tsig-key, with name, algorithm and secret options. --- Makefile.in | 10 ++--- daemon/daemon.c | 17 ++++++++ libunbound/context.c | 3 ++ libunbound/libunbound.c | 14 ++++++ util/config_file.c | 26 ++++++++++- util/config_file.h | 29 +++++++++++++ util/configlexer.lex | 3 ++ util/configparser.y | 81 ++++++++++++++++++++++++++++++++++- util/module.h | 3 ++ util/tsig.c | 95 ++++++++++++++++++++++++++++++++++++++++- util/tsig.h | 10 +++++ 11 files changed, 283 insertions(+), 8 deletions(-) diff --git a/Makefile.in b/Makefile.in index f2efa5d2c..095717e16 100644 --- a/Makefile.in +++ b/Makefile.in @@ -937,8 +937,8 @@ config_file.lo config_file.o: $(srcdir)/util/config_file.c config.h $(srcdir)/ut configlexer.lo configlexer.o: util/configlexer.c config.h $(srcdir)/util/configyyrename.h \ $(srcdir)/util/config_file.h util/configparser.h configparser.lo configparser.o: util/configparser.c config.h $(srcdir)/util/configyyrename.h \ - $(srcdir)/util/config_file.h $(srcdir)/util/net_help.h $(srcdir)/util/log.h $(srcdir)/sldns/str2wire.h \ - $(srcdir)/sldns/rrdef.h + $(srcdir)/util/config_file.h $(srcdir)/util/net_help.h $(srcdir)/util/log.h $(srcdir)/util/tsig.h $(srcdir)/sldns/str2wire.h \ + $(srcdir)/sldns/rrdef.h $(srcdir)/sldns/parseutil.h shm_main.lo shm_main.o: $(srcdir)/util/shm_side/shm_main.c config.h $(srcdir)/util/shm_side/shm_main.h \ $(srcdir)/libunbound/unbound.h $(srcdir)/daemon/daemon.h $(srcdir)/util/locks.h $(srcdir)/util/log.h \ $(srcdir)/util/alloc.h $(srcdir)/services/modstack.h \ @@ -1299,7 +1299,7 @@ daemon.lo daemon.o: $(srcdir)/daemon/daemon.c config.h $(srcdir)/daemon/daemon.h $(srcdir)/util/edns.h $(srcdir)/services/listen_dnsport.h $(srcdir)/services/cache/rrset.h \ $(srcdir)/services/cache/infra.h $(srcdir)/util/rtt.h $(srcdir)/services/localzone.h \ $(srcdir)/services/authzone.h $(srcdir)/services/mesh.h $(srcdir)/services/rpz.h $(srcdir)/respip/respip.h \ - $(srcdir)/util/random.h $(srcdir)/util/tube.h $(srcdir)/util/net_help.h $(srcdir)/sldns/keyraw.h \ + $(srcdir)/util/random.h $(srcdir)/util/tube.h $(srcdir)/util/net_help.h $(srcdir)/util/tsig.h $(srcdir)/sldns/keyraw.h \ $(srcdir)/iterator/iter_fwd.h $(srcdir)/iterator/iter_hints.h remote.lo remote.o: $(srcdir)/daemon/remote.c config.h $(srcdir)/daemon/remote.h $(srcdir)/daemon/worker.h \ $(srcdir)/libunbound/worker.h $(srcdir)/sldns/sbuffer.h $(srcdir)/util/data/packed_rrset.h \ @@ -1509,7 +1509,7 @@ context.lo context.o: $(srcdir)/libunbound/context.c config.h $(srcdir)/libunbou $(srcdir)/util/storage/slabhash.h $(srcdir)/services/cache/infra.h $(srcdir)/util/rtt.h \ $(srcdir)/util/netevent.h $(srcdir)/dnscrypt/dnscrypt.h \ $(srcdir)/services/authzone.h $(srcdir)/services/mesh.h $(srcdir)/services/rpz.h $(srcdir)/daemon/stats.h \ - $(srcdir)/util/timehist.h $(srcdir)/respip/respip.h $(srcdir)/util/edns.h \ + $(srcdir)/util/timehist.h $(srcdir)/respip/respip.h $(srcdir)/util/edns.h $(srcdir)/util/tsig.h \ $(srcdir)/iterator/iter_fwd.h $(srcdir)/iterator/iter_hints.h libunbound.lo libunbound.o: $(srcdir)/libunbound/libunbound.c $(srcdir)/libunbound/unbound.h \ $(srcdir)/libunbound/unbound-event.h config.h $(srcdir)/libunbound/context.h $(srcdir)/util/locks.h \ @@ -1517,7 +1517,7 @@ libunbound.lo libunbound.o: $(srcdir)/libunbound/libunbound.c $(srcdir)/libunbou $(srcdir)/util/data/packed_rrset.h $(srcdir)/util/storage/lruhash.h $(srcdir)/libunbound/libworker.h \ $(srcdir)/util/config_file.h $(srcdir)/util/module.h $(srcdir)/util/data/msgreply.h \ $(srcdir)/util/data/msgparse.h $(srcdir)/sldns/pkthdr.h $(srcdir)/sldns/rrdef.h $(srcdir)/util/regional.h \ - $(srcdir)/util/random.h $(srcdir)/util/net_help.h $(srcdir)/util/tube.h $(srcdir)/util/ub_event.h $(srcdir)/util/edns.h \ + $(srcdir)/util/random.h $(srcdir)/util/net_help.h $(srcdir)/util/tube.h $(srcdir)/util/ub_event.h $(srcdir)/util/edns.h $(srcdir)/util/tsig.h \ $(srcdir)/util/storage/dnstree.h $(srcdir)/services/localzone.h $(srcdir)/services/view.h \ $(srcdir)/sldns/sbuffer.h $(srcdir)/services/cache/infra.h $(srcdir)/util/rtt.h $(srcdir)/util/netevent.h \ $(srcdir)/dnscrypt/dnscrypt.h $(srcdir)/services/cache/rrset.h \ diff --git a/daemon/daemon.c b/daemon/daemon.c index f882bb9ad..92d0d031b 100644 --- a/daemon/daemon.c +++ b/daemon/daemon.c @@ -89,6 +89,7 @@ #include "util/random.h" #include "util/tube.h" #include "util/net_help.h" +#include "util/tsig.h" #include "sldns/keyraw.h" #include "respip/respip.h" #include "iterator/iter_fwd.h" @@ -320,6 +321,17 @@ daemon_init(void) free(daemon); return NULL; } + if(!(daemon->env->tsig_key_table = tsig_key_table_create())) { + auth_zones_delete(daemon->env->auth_zones); + acl_list_delete(daemon->acl_interface); + acl_list_delete(daemon->acl); + tcl_list_delete(daemon->tcl); + edns_known_options_delete(daemon->env); + edns_strings_delete(daemon->env->edns_strings); + free(daemon->env); + free(daemon); + return NULL; + } return daemon; } @@ -771,6 +783,10 @@ daemon_fork(struct daemon* daemon) daemon->use_response_ip = !respip_set_is_empty( daemon->env->respip_set) || have_view_respip_cfg; + /* setup tsig keys */ + if(!tsig_key_table_apply_cfg(daemon->env->tsig_key_table, daemon->cfg)) + fatal_exit("Could not set up TSIG keys"); + /* setup modules */ daemon_setup_modules(daemon); @@ -944,6 +960,7 @@ daemon_delete(struct daemon* daemon) edns_known_options_delete(daemon->env); edns_strings_delete(daemon->env->edns_strings); auth_zones_delete(daemon->env->auth_zones); + tsig_key_table_delete(daemon->env->tsig_key_table); } ub_randfree(daemon->rand); alloc_clear(&daemon->superalloc); diff --git a/libunbound/context.c b/libunbound/context.c index a1a4adf98..38b5dbf00 100644 --- a/libunbound/context.c +++ b/libunbound/context.c @@ -52,6 +52,7 @@ #include "util/data/msgreply.h" #include "util/storage/slabhash.h" #include "util/edns.h" +#include "util/tsig.h" #include "sldns/sbuffer.h" #include "iterator/iter_fwd.h" #include "iterator/iter_hints.h" @@ -81,6 +82,8 @@ context_finalize(struct ub_ctx* ctx) return UB_INITFAIL; listen_setup_locks(); log_edns_known_options(VERB_ALGO, ctx->env); + if(!tsig_key_table_apply_cfg(ctx->env->tsig_key_table, cfg)) + return UB_INITFAIL; ctx->local_zones = local_zones_create(); if(!ctx->local_zones) return UB_NOMEM; diff --git a/libunbound/libunbound.c b/libunbound/libunbound.c index 9c6a3e309..e5b340013 100644 --- a/libunbound/libunbound.c +++ b/libunbound/libunbound.c @@ -59,6 +59,7 @@ #include "util/tube.h" #include "util/ub_event.h" #include "util/edns.h" +#include "util/tsig.h" #include "services/modstack.h" #include "services/localzone.h" #include "services/cache/infra.h" @@ -168,6 +169,18 @@ static struct ub_ctx* ub_ctx_create_nopipe(void) errno = ENOMEM; return NULL; } + ctx->env->tsig_key_table = tsig_key_table_create(); + if(!ctx->env->tsig_key_table) { + auth_zones_delete(ctx->env->auth_zones); + edns_known_options_delete(ctx->env); + edns_strings_delete(ctx->env->edns_strings); + config_delete(ctx->env->cfg); + free(ctx->env); + ub_randfree(ctx->seed_rnd); + free(ctx); + errno = ENOMEM; + return NULL; + } ctx->env->alloc = &ctx->superalloc; ctx->env->worker = NULL; @@ -388,6 +401,7 @@ ub_ctx_delete(struct ub_ctx* ctx) config_delete(ctx->env->cfg); edns_known_options_delete(ctx->env); edns_strings_delete(ctx->env->edns_strings); + tsig_key_table_delete(ctx->env->tsig_key_table); forwards_delete(ctx->env->fwds); hints_delete(ctx->env->hints); auth_zones_delete(ctx->env->auth_zones); diff --git a/util/config_file.c b/util/config_file.c index b1e767b3b..89e8760ce 100644 --- a/util/config_file.c +++ b/util/config_file.c @@ -222,6 +222,7 @@ config_create(void) cfg->stubs = NULL; cfg->forwards = NULL; cfg->auths = NULL; + cfg->tsig_keys = NULL; #ifdef CLIENT_SUBNET cfg->client_subnet = NULL; cfg->client_subnet_zone = NULL; @@ -928,7 +929,7 @@ int config_set_option(struct config_file* cfg, const char* opt, * max-client-subnet-ipv4, max-client-subnet-ipv6, * min-client-subnet-ipv4, min-client-subnet-ipv6, * max-ecs-tree-size-ipv4, max-ecs-tree-size-ipv6, ipsecmod_hook, - * ipsecmod_whitelist. */ + * ipsecmod_whitelist, tsig-key. */ return 0; } return 1; @@ -1433,6 +1434,7 @@ config_get_option(struct config_file* cfg, const char* opt, * local-data-ptr - converted to local-data entries * stub-zone, name, stub-addr, stub-host, stub-prime * forward-zone, name, forward-addr, forward-host + * tsig-key */ else return 0; return 1; @@ -1704,6 +1706,27 @@ config_delviews(struct config_view* p) } } +void +config_deltsig_key(struct config_tsig_key* p) +{ + if(!p) return; + free(p->name); + free(p->algorithm); + free(p->secret); + free(p); +} + +void +config_deltsig_keys(struct config_tsig_key* p) +{ + struct config_tsig_key* np; + while(p) { + np = p->next; + config_deltsig_key(p); + p = np; + } +} + void config_del_strarray(char** array, int num) { @@ -1758,6 +1781,7 @@ config_delete(struct config_file* cfg) config_delstubs(cfg->forwards); config_delauths(cfg->auths); config_delviews(cfg->views); + config_deltsig_keys(cfg->tsig_keys); config_delstrlist(cfg->donotqueryaddrs); config_delstrlist(cfg->root_hints); #ifdef CLIENT_SUBNET diff --git a/util/config_file.h b/util/config_file.h index 44ac036b8..9e6314561 100644 --- a/util/config_file.h +++ b/util/config_file.h @@ -45,6 +45,7 @@ struct config_stub; struct config_auth; struct config_view; +struct config_tsig_key; struct config_strlist; struct config_str2list; struct config_str3list; @@ -260,6 +261,8 @@ struct config_file { struct config_auth* auths; /** the views definitions, linked list */ struct config_view* views; + /** the tsig-key definitions, linked list */ + struct config_tsig_key* tsig_keys; /** list of donotquery addresses, linked list */ struct config_strlist* donotqueryaddrs; #ifdef CLIENT_SUBNET @@ -904,6 +907,20 @@ struct config_view { struct config_str2list* respip_data; }; +/** + * Tsig-key config options + */ +struct config_tsig_key { + /** next in list */ + struct config_tsig_key* next; + /** name of the tsig key */ + char* name; + /** algorithm */ + char* algorithm; + /** secret date, in base64 */ + char* secret; +}; + /** * List of strings for config options */ @@ -1216,6 +1233,18 @@ void config_delview(struct config_view* p); */ void config_delviews(struct config_view* list); +/** + * Delete a tsig_key item + * @param p: tsig_key item + */ +void config_deltsig_key(struct config_tsig_key* p); + +/** + * Delete items in config tsig_key list. + * @param list: list. + */ +void config_deltsig_keys(struct config_tsig_key* list); + /** check if config for remote control turns on IP-address interface * with certificates or a named pipe without certificates. */ int options_remote_is_address(struct config_file* cfg); diff --git a/util/configlexer.lex b/util/configlexer.lex index bc258673d..d415e7a04 100644 --- a/util/configlexer.lex +++ b/util/configlexer.lex @@ -606,6 +606,9 @@ proxy-protocol-port{COLON} { YDVAR(1, VAR_PROXY_PROTOCOL_PORT) } iter-scrub-ns{COLON} { YDVAR(1, VAR_ITER_SCRUB_NS) } iter-scrub-cname{COLON} { YDVAR(1, VAR_ITER_SCRUB_CNAME) } max-global-quota{COLON} { YDVAR(1, VAR_MAX_GLOBAL_QUOTA) } +tsig-key{COLON} { YDVAR(0, VAR_TSIG_KEY) } +algorithm{COLON} { YDVAR(1, VAR_ALGORITHM) } +secret{COLON} { YDVAR(1, VAR_SECRET) } {NEWLINE} { LEXOUT(("NL\n")); cfg_parser->line++; } /* Quoted strings. Strip leading and ending quotes */ diff --git a/util/configparser.y b/util/configparser.y index ebb23f41c..9638740c9 100644 --- a/util/configparser.y +++ b/util/configparser.y @@ -47,7 +47,9 @@ #include "util/configyyrename.h" #include "util/config_file.h" #include "util/net_help.h" +#include "util/tsig.h" #include "sldns/str2wire.h" +#include "sldns/parseutil.h" int ub_c_lex(void); void ub_c_error(const char *message); @@ -215,6 +217,7 @@ extern struct config_parser_state* cfg_parser; %token VAR_LOG_DESTADDR VAR_CACHEDB_CHECK_WHEN_SERVE_EXPIRED %token VAR_COOKIE_SECRET_FILE VAR_ITER_SCRUB_NS VAR_ITER_SCRUB_CNAME %token VAR_MAX_GLOBAL_QUOTA VAR_HARDEN_UNVERIFIED_GLUE VAR_LOG_TIME_ISO +%token VAR_TSIG_KEY VAR_ALGORITHM VAR_SECRET %% toplevelvars: /* empty */ | toplevelvars toplevelvar ; @@ -223,7 +226,7 @@ toplevelvar: serverstart contents_server | stub_clause | rcstart contents_rc | dtstart contents_dt | view_clause | dnscstart contents_dnsc | cachedbstart contents_cachedb | ipsetstart contents_ipset | authstart contents_auth | - rpzstart contents_rpz | dynlibstart contents_dl | + rpzstart contents_rpz | dynlibstart contents_dl | tsig_key_clause | force_toplevel ; force_toplevel: VAR_FORCE_TOPLEVEL @@ -3732,6 +3735,82 @@ dl_file: VAR_DYNLIB_FILE STRING_ARG yyerror("out of memory"); } ; +tsig_key_clause: tsig_key_start contents_tsig_key + { + /* tsig-key end */ + if(cfg_parser->cfg->tsig_keys) { + if(!cfg_parser->cfg->tsig_keys->name) + yyerror("tsig-key without name"); + else if(!cfg_parser->cfg->tsig_keys->algorithm) + ub_c_error_msg("tsig-key %s has no algorithm", + cfg_parser->cfg->tsig_keys->name); + else if(!cfg_parser->cfg->tsig_keys->secret) + ub_c_error_msg("tsig-key %s has no secret blob", + cfg_parser->cfg->tsig_keys->name); + } + } + ; +tsig_key_start: VAR_TSIG_KEY + { + struct config_tsig_key* s; + OUTYY(("\nP(tsig-key:)\n")); + cfg_parser->started_toplevel = 1; + s = (struct config_tsig_key*)calloc(1, + sizeof(struct config_tsig_key)); + if(s) { + s->next = cfg_parser->cfg->tsig_keys; + cfg_parser->cfg->tsig_keys = s; + } else { + yyerror("out of memory"); + } + } + ; +contents_tsig_key: contents_tsig_key content_tsig_key + | ; +content_tsig_key: tsig_key_name | tsig_key_algorithm | tsig_key_secret + ; +tsig_key_name: VAR_NAME STRING_ARG + { + uint8_t buf[LDNS_MAX_DOMAINLEN+1]; + size_t len = sizeof(buf); + int r; + + OUTYY(("P(name:%s)\n", $2)); + free(cfg_parser->cfg->tsig_keys->name); + cfg_parser->cfg->tsig_keys->name = $2; + + if((r=sldns_str2wire_dname_buf($2, buf, &len))!=0) + ub_c_error_msg("could not parse tsig key name" + " '%s':%d: %s", $2, LDNS_WIREPARSE_OFFSET(r), + sldns_get_errorstr_parse(r)); + } +tsig_key_algorithm: VAR_ALGORITHM STRING_ARG + { + OUTYY(("P(algorithm:%s)\n", $2)); + free(cfg_parser->cfg->tsig_keys->algorithm); + cfg_parser->cfg->tsig_keys->algorithm = $2; + if(!tsig_algo_check_name($2)) + ub_c_error_msg("could not parse tsig key algorithm '%s'", + $2); + } +tsig_key_secret: VAR_SECRET STRING_ARG + { + uint8_t data[16384]; + int size; + + OUTYY(("P(secret:%s)\n", $2)); + free(cfg_parser->cfg->tsig_keys->secret); + cfg_parser->cfg->tsig_keys->secret = $2; + + size = sldns_b64_pton($2, data, sizeof(data)); + if(size == -1) { + ub_c_error_msg("cannot base64 decode tsig secret %s", + cfg_parser->cfg->tsig_keys->name? + cfg_parser->cfg->tsig_keys->name:""); + } else if(size != 0) { + explicit_bzero(data, size); + } + } server_disable_dnssec_lame_check: VAR_DISABLE_DNSSEC_LAME_CHECK STRING_ARG { OUTYY(("P(disable_dnssec_lame_check:%s)\n", $2)); diff --git a/util/module.h b/util/module.h index edce4a523..aa7508ac2 100644 --- a/util/module.h +++ b/util/module.h @@ -181,6 +181,7 @@ struct views; struct respip_set; struct respip_client_info; struct respip_addr_info; +struct tsig_key_table; struct module_stack; /** Maximum number of modules in operation */ @@ -529,6 +530,8 @@ struct module_env { struct views* views; /** response-ip set with associated actions and tags. */ struct respip_set* respip_set; + /** the TSIG keys */ + struct tsig_key_table* tsig_key_table; /** module specific data. indexed by module id. */ void* modinfo[MAX_MODULE]; diff --git a/util/tsig.c b/util/tsig.c index 81309699d..79438adc0 100644 --- a/util/tsig.c +++ b/util/tsig.c @@ -41,10 +41,13 @@ #include "config.h" #include "util/tsig.h" +#include "util/config_file.h" #include "util/log.h" +#include "sldns/parseutil.h" #include "sldns/pkthdr.h" #include "sldns/rrdef.h" #include "sldns/sbuffer.h" +#include "sldns/str2wire.h" #include "util/data/msgparse.h" #include "util/data/dname.h" #include @@ -114,6 +117,95 @@ tsig_key_table_delete(struct tsig_key_table* key_table) free(key_table); } +/** Create a tsig_key */ +static struct tsig_key* +tsig_key_create(const char* name, const char* algorithm, const char* secret) +{ + struct tsig_key* key = (struct tsig_key*)calloc(1, sizeof(*key)); + int ret; + if(!key) { + log_err("out of memory"); + return NULL; + } + key->node.key = key; + key->name_str = strdup(name); + if(!key->name_str) { + log_err("out of memory"); + tsig_key_delete(key); + return NULL; + } + key->algo = tsig_algo_find_name(algorithm); + if(!key->algo) { + log_err("tsig key %s could not parse algorithm '%s'", + name, algorithm); + tsig_key_delete(key); + return NULL; + } + key->name = sldns_str2wire_dname(name, &key->name_len); + if(!key->name) { + log_err("tsig key %s could not parse name into wireformat", + name); + tsig_key_delete(key); + return NULL; + } + + key->data_len = sldns_b64_pton_calculate_size(strlen(secret)); + if(key->data_len == 0) + key->data = NULL; + else key->data = malloc(key->data_len); + if(!key->data) { + log_err("out of memory"); + tsig_key_delete(key); + return NULL; + } + ret = sldns_b64_pton(secret, key->data, key->data_len); + if(ret == -1 || ret > (int)key->data_len) { + log_err("tsig key %s could not base64 decode secret blob", + name); + tsig_key_delete(key); + return NULL; + } + key->data_len = ret; + + return key; +} + +/** Add a key to the TSIG key table. */ +static int +tsig_key_table_add_key(struct tsig_key_table* key_table, + struct config_tsig_key* s) +{ + struct tsig_key* key; + key = tsig_key_create(s->name, s->algorithm, s->secret); + if(!key) + return 0; + /* Wipe the secret from config, it is in the key_table. */ + if(s->secret[0] != 0) + explicit_bzero(s->secret, strlen(s->secret)); + + lock_rw_wrlock(&key_table->lock); + if(!rbtree_insert(key_table->tree, &key->node)) { + log_warn("duplicate tsig-key: %s", key->name_str); + lock_rw_unlock(&key_table->lock); + tsig_key_delete(key); + return 0; + } + lock_rw_unlock(&key_table->lock); + return 1; +} + +int +tsig_key_table_apply_cfg(struct tsig_key_table* key_table, + struct config_file* cfg) +{ + struct config_tsig_key* s; + for(s = cfg->tsig_keys; s; s = s->next) { + if(!tsig_key_table_add_key(key_table, s)) + return 0; + } + return 1; +} + void tsig_key_delete(struct tsig_key* key) { if(!key) @@ -122,7 +214,8 @@ void tsig_key_delete(struct tsig_key* key) free(key->name); if(key->data) { /* The secret data is removed. */ - explicit_bzero(key->data, key->data_len); + if(key->data_len > 0) + explicit_bzero(key->data, key->data_len); free(key->data); } free(key); diff --git a/util/tsig.h b/util/tsig.h index eb4391d75..b6ac605dd 100644 --- a/util/tsig.h +++ b/util/tsig.h @@ -44,6 +44,7 @@ #include "util/locks.h" #include "util/rbtree.h" struct sldns_buffer; +struct config_file; /** * TSIG record, the RR that is in the packet. @@ -146,6 +147,15 @@ struct tsig_key_table* tsig_key_table_create(void); */ void tsig_key_table_delete(struct tsig_key_table* key_table); +/** + * Apply config to the tsig key table. + * @param key_table: the tsig key table. + * @param cfg: the config to read. + * @return false on failure. + */ +int tsig_key_table_apply_cfg(struct tsig_key_table* key_table, + struct config_file* cfg); + /** * Delete TSIG key. * @param key: to delete From 31e8118b760d6a5cb5755820b7573932f172790d Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 13 Jun 2025 16:32:36 +0200 Subject: [PATCH 13/43] - xfr-tsig, man page and example config. --- doc/example.conf.in | 11 +++++++++++ doc/unbound.conf.rst | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/doc/example.conf.in b/doc/example.conf.in index a85b58de4..12b8379eb 100644 --- a/doc/example.conf.in +++ b/doc/example.conf.in @@ -1418,3 +1418,14 @@ remote-control: # rpz-signal-nxdomain-ra: no # for-downstream: no # tags: "example" + +# TSIG keys +# tsig-key: +# # The key name is sent to the other party, it must be the same +# name: "keyname" +# # algorithm hmac-md5, or sha1, sha256, sha224, sha384, sha512 +# algorithm: sha256 +# # secret material, must be the same as the other party uses. +# # base64 encoded random number. +# # e.g. from dd if=/dev/random of=/dev/stdout count=1 bs=32 | base64 +# secret: "K2tf3TRjvQkVCmJF3/Z9vA==" diff --git a/doc/unbound.conf.rst b/doc/unbound.conf.rst index 80a62309d..cc5f246b3 100644 --- a/doc/unbound.conf.rst +++ b/doc/unbound.conf.rst @@ -4934,6 +4934,42 @@ The RPZ zones can be configured in the config file with these settings in the If no tags are specified the policies from this clause will be applied for all clients. +.. _unbound.conf.tsig-key: + +TSIG Key Options +^^^^^^^^^^^^^^^^^ + +The **tsig-key:** clauses specify the TSIG keys that are used. +There can be multiple **tsig-key:** clauses, with each specifying a +different key. +Each key has a name, algorithm and secret key material. + +TSIG keys are shared secrets. +Both sides of the connection share the secret information. +Also they must both use the same name for the key, and same algorithm. + +With ``include: "key.conf"`` it is possible to put the declaration of the key +or some lines of it in an external file from the main configuration file. +It can also be used without such an include, with it the config statements +and key material can be put in separate files. + + +@@UAHL@unbound.conf.tsig-key@name@@: *""* + Name of the TSIG key. + The key name is transferred in DNS wireformat in the TSIG record, and + is used to reference the TSIG key from where it is configured to be used. + + +@@UAHL@unbound.conf.tsig-key@algorithm@@: ** + Name of the algorithm to use with this TSIG key. + This can be md5, sha1, sha224, sha256, sha384 or sha512. + + +@@UAHL@unbound.conf.tsig-key@secret@@: *""* + The secret contents is a base64 string. + A way to get random base64 bytes is e.g. + from ``dd if=/dev/random of=/dev/stdout count=1 bs=32 | base64`` + Memory Control Example ---------------------- From 497161f72f628a279dfbc521c94e66e5bdb6f551 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Mon, 16 Jun 2025 16:59:53 +0200 Subject: [PATCH 14/43] - xfr-tsig, tsig_verify return failure comment improved. --- util/tsig.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/util/tsig.h b/util/tsig.h index b6ac605dd..d0d4226ca 100644 --- a/util/tsig.h +++ b/util/tsig.h @@ -178,7 +178,9 @@ struct tsig_algorithm* tsig_algo_find_name(const char* algo_name); /** * Verify pkt with the name (domain name), algorithm and key. - * out 0 on success, an error code otherwise. + * out 0 on success, on failure: + * -1 for malformed, no tsig RR, or too large for buffer. + * >0 rcode with a TSIG error code otherwise. */ int tsig_verify(struct sldns_buffer* pkt, const uint8_t* name, const uint8_t* alg, const uint8_t* secret, size_t secret_len, From 69354298fc1735c0dca7fbb070a94faf3716a45c Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Tue, 17 Jun 2025 16:54:52 +0200 Subject: [PATCH 15/43] - xfr-tsig, tsig_create and tsig_delete. --- util/tsig.c | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++ util/tsig.h | 53 +++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+) diff --git a/util/tsig.c b/util/tsig.c index 79438adc0..984e3ba5b 100644 --- a/util/tsig.c +++ b/util/tsig.c @@ -43,6 +43,7 @@ #include "util/tsig.h" #include "util/config_file.h" #include "util/log.h" +#include "util/net_help.h" #include "sldns/parseutil.h" #include "sldns/pkthdr.h" #include "sldns/rrdef.h" @@ -206,6 +207,20 @@ tsig_key_table_apply_cfg(struct tsig_key_table* key_table, return 1; } +struct tsig_key* +tsig_key_table_search(struct tsig_key_table* key_table, uint8_t* name, + size_t namelen) +{ + rbnode_type* node; + struct tsig_key k; + k.node.key = &k; + k.name = name; + k.name_len = namelen; + node = rbtree_search(key_table->tree, &k); + if(!node) return NULL; + return (struct tsig_key*)node->key; +} + void tsig_key_delete(struct tsig_key* key) { if(!key) @@ -560,3 +575,64 @@ tsig_verify(sldns_buffer* pkt, const uint8_t* name, const uint8_t* alg, sldns_buffer_set_position(pkt, end_of_message); return LDNS_TSIG_ERROR_BADSIG; } + +struct tsig_data* +tsig_create(struct tsig_key_table* key_table, uint8_t* name, size_t namelen) +{ + struct tsig_key* key; + struct tsig_data* tsig; + lock_rw_rdlock(&key_table->lock); + key = tsig_key_table_search(key_table, name, namelen); + if(!key) { + lock_rw_unlock(&key_table->lock); + return NULL; + } + /* the key table lock is also on the returned key */ + + tsig = calloc(1, sizeof(*tsig)); + if(!tsig) { + lock_rw_unlock(&key_table->lock); + log_err("out of memory"); + return NULL; + } + tsig->key_name_len = key->name_len; + tsig->key_name = memdup(key->name, key->name_len); + if(!tsig->key_name) { + lock_rw_unlock(&key_table->lock); + tsig_delete(tsig); + log_err("out of memory"); + return NULL; + } + tsig->algorithm_name_len = key->algo->wireformat_name_len; + tsig->mac_size = key->algo->max_digest_size; + tsig->mac = calloc(1, tsig->mac_size); + if(!tsig->mac) { + lock_rw_unlock(&key_table->lock); + tsig_delete(tsig); + log_err("out of memory"); + return NULL; + } + lock_rw_unlock(&key_table->lock); + return tsig; +} + +struct tsig_data* +tsig_create_fromstr(struct tsig_key_table* key_table, char* name) +{ + uint8_t buf[LDNS_MAX_DOMAINLEN+1]; + size_t len = sizeof(buf); + if(!sldns_str2wire_dname_buf(name, buf, &len)) { + log_err("could not parse '%s'", name); + return NULL; + } + return tsig_create(key_table, buf, len); +} + +void +tsig_delete(struct tsig_data* tsig) +{ + if(!tsig) return; + free(tsig->key_name); + free(tsig->mac); + free(tsig); +} diff --git a/util/tsig.h b/util/tsig.h index d0d4226ca..68b71c23d 100644 --- a/util/tsig.h +++ b/util/tsig.h @@ -79,6 +79,23 @@ struct tsig_record { uint8_t* other_data; }; +/** + * TSIG data. This keeps track of the information between packets, + * for the TSIG signature, and state, errors, key. + */ +struct tsig_data { + /** The key name, in wireformat */ + uint8_t* key_name; + /** length of the key name */ + size_t key_name_len; + /** length of the algorithm name */ + size_t algorithm_name_len; + /** mac size */ + size_t mac_size; + /** digest buffer */ + uint8_t* mac; +}; + /** * TSIG algorithm. This is the HMAC algorithm used for the TSIG mac. */ @@ -156,6 +173,17 @@ void tsig_key_table_delete(struct tsig_key_table* key_table); int tsig_key_table_apply_cfg(struct tsig_key_table* key_table, struct config_file* cfg); +/** + * Find key in key table. Caller must hold lock on the table. + * @param key_table: the tsig key table. + * @param name: name to look for in wireformat. + * @param namelen: length of name. + * @return the found key or NULL if not found. The item is locked + * by the key_table lock. + */ +struct tsig_key* tsig_key_table_search(struct tsig_key_table* key_table, + uint8_t* name, size_t namelen); + /** * Delete TSIG key. * @param key: to delete @@ -189,4 +217,29 @@ int tsig_verify(struct sldns_buffer* pkt, const uint8_t* name, /** Compare function for the key table keys. */ int tsig_key_compare(const void* v1, const void* v2); +/** + * Find tsig key and create new tsig data. + * @param key_table: the tsig key table. + * @param name: key name in wireformat. + * @param namelen: length of name. + * @return NULL if not found, or alloc failure. + */ +struct tsig_data* tsig_create(struct tsig_key_table* key_table, + uint8_t* name, size_t namelen); + +/** + * Find tsig key and create new tsig data. + * @param key_table: the tsig key table. + * @param name: key name string. + * @return NULL if not found, or alloc failure, or could not parse string. + */ +struct tsig_data* tsig_create_fromstr(struct tsig_key_table* key_table, + char* name); + +/** + * Delete tsig data. + * @param tsig: the tsig data to delete. + */ +void tsig_delete(struct tsig_data* tsig); + #endif /* UTIL_TSIG_H */ From 8b95785b8c61060cdba7353d6886c380e68230dd Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 18 Jun 2025 12:18:20 +0200 Subject: [PATCH 16/43] - xfr-tsig, tsig functions. --- util/tsig.h | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/util/tsig.h b/util/tsig.h index 68b71c23d..d982d840a 100644 --- a/util/tsig.h +++ b/util/tsig.h @@ -45,6 +45,7 @@ #include "util/rbtree.h" struct sldns_buffer; struct config_file; +struct regional; /** * TSIG record, the RR that is in the packet. @@ -242,4 +243,69 @@ struct tsig_data* tsig_create_fromstr(struct tsig_key_table* key_table, */ void tsig_delete(struct tsig_data* tsig); +/** + * Sign a query with TSIG. Appends the TSIG record. + * @param tsig: the tsig data, keeps state to verify reply. + * @param pkt: query packet. position must be at end of packet. + * @return false on failure. + */ +int tsig_sign_query(struct tsig_data* tsig, struct sldns_buffer* pkt); + +/** + * Verify a query with TSIG. + * @param tsig: the tsig data, keep state to sign reply. + * @param pkt: the query packet. + * @return false on failure. There must be a TSIG with the key or it fails. + */ +int tsig_verify_query(struct tsig_data* tsig, struct sldns_buffer* pkt); + +/** + * Look up key from TSIG in packet. + * @param key_table: the tsig key table. + * @param pkt: the packet to look at TSIG. + * @param tsig: the tsig key is returned here. Or it can be NULL, no TSIG. + * @param region: if nonNULL used to allocate. + * @return fail for alloc failure servfail or wireformat malformed formerr, + * success has 0 NOERROR, for no TSIG in packet with tsig returned NULL, + * and for key not found with tsig returned with a tsig error in it, + * and for key found with tsig returned with tsig in it. + * After this call, the return value is the rcode for failure. Then the + * tsig, is NULL for no TSIG, or nonNULL, with a TSIG error or content that + * can be verified with tsig_verify_query. + */ +int tsig_parse_query(struct tsig_key_table* key_table, + struct sldns_buffer* pkt, struct tsig_data** tsig, + struct regional* region); + +/** + * Parse and verify the TSIG in query packet. + * @param key_table: the tsig key table. + * @param pkt: the packet + * @param tsig: the tsig key is returned. Or it can be NULL. + * @param region: if nonNULL used to allocate. + * @return rcode with failure for alloc failure or malformed wireformat. + * 0 NOERROR is success, if tsig is nonNULL it has either verified + * or contains a TSIG error. + */ +int tsig_parse_verify_query(struct tsig_key_table* key_table, + struct sldns_buffer* pkt, struct tsig_data** tsig, + struct regional* region); + +/** + * Sign a reply with TSIG. Appends the TSIG record. + * @param tsig: the tsig data. + * @param pkt: the packet to sign. + * @return false on failure. + */ +int tsig_sign_reply(struct tsig_data* tsig, struct sldns_buffer* pkt); + +/** + * Verify a reply with TSIG. + * @param tsig: the tsig data. + * @param pkt: the reply to verify. + * @return false on failure, like + * alloc failure, wireformat malformed, did not verify. + */ +int tsig_verify_reply(struct tsig_data* tsig, struct sldns_buffer* pkt); + #endif /* UTIL_TSIG_H */ From dd4ee42eb6cdb963cb57b24154d6e6b59dc1ed20 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 18 Jun 2025 15:00:18 +0200 Subject: [PATCH 17/43] - xfr-tsig, tsig_sign_query. --- Makefile.in | 6 +- testcode/unitmain.c | 1 + testcode/unitmain.h | 2 + testcode/unittsig.c | 49 +++++++++++++ util/tsig.c | 168 ++++++++++++++++++++++++++++++++++++++++++++ util/tsig.h | 28 +++++++- 6 files changed, 251 insertions(+), 3 deletions(-) create mode 100644 testcode/unittsig.c diff --git a/Makefile.in b/Makefile.in index 095717e16..417e8c4ef 100644 --- a/Makefile.in +++ b/Makefile.in @@ -179,11 +179,12 @@ testcode/unitlruhash.c testcode/unitmain.c testcode/unitmsgparse.c \ testcode/unitneg.c testcode/unitregional.c testcode/unitslabhash.c \ testcode/unitverify.c testcode/readhex.c testcode/testpkts.c testcode/unitldns.c \ testcode/unitecs.c testcode/unitauth.c testcode/unitzonemd.c \ -testcode/unittcpreuse.c testcode/unitdoq.c testcode/unitinfra.c +testcode/unittcpreuse.c testcode/unitdoq.c testcode/unitinfra.c \ +testcode/unittsig.c UNITTEST_OBJ=unitanchor.lo unitdname.lo unitlruhash.lo unitmain.lo \ unitmsgparse.lo unitneg.lo unitregional.lo unitslabhash.lo unitverify.lo \ readhex.lo testpkts.lo unitldns.lo unitecs.lo unitauth.lo unitzonemd.lo \ -unittcpreuse.lo unitdoq.lo unitinfra.lo +unittcpreuse.lo unitdoq.lo unitinfra.lo unittsig.lo UNITTEST_OBJ_LINK=$(UNITTEST_OBJ) worker_cb.lo $(COMMON_OBJ) $(SLDNS_OBJ) \ $(COMPAT_OBJ) DAEMON_SRC=daemon/acl_list.c daemon/cachedump.c daemon/daemon.c \ @@ -1267,6 +1268,7 @@ unitzonemd.lo unitzonemd.o: $(srcdir)/testcode/unitzonemd.c config.h $(srcdir)/u unittcpreuse.lo unittcpreuse.o: $(srcdir)/testcode/unittcpreuse.c config.h $(srcdir)/services/outside_network.h \ $(srcdir)/util/random.h unitinfra.lo unitinfra.o: $(srcdir)/testcode/unitinfra.c config.h $(srcdir)/util/config_file.h $(srcdir)/util/net_help.h $(srcdir)/iterator/iterator.h +unittsig.lo unittsig.o: $(srcdir)/testcode/unittsig.c config.h $(srcdir)/util/tsig.h $(srcdir)/testcode/unitmain.h acl_list.lo acl_list.o: $(srcdir)/daemon/acl_list.c config.h $(srcdir)/daemon/acl_list.h \ $(srcdir)/util/storage/dnstree.h $(srcdir)/util/rbtree.h $(srcdir)/services/view.h $(srcdir)/util/locks.h \ $(srcdir)/util/log.h $(srcdir)/util/regional.h $(srcdir)/util/config_file.h $(srcdir)/util/net_help.h \ diff --git a/testcode/unitmain.c b/testcode/unitmain.c index 07c016d7b..059206678 100644 --- a/testcode/unitmain.c +++ b/testcode/unitmain.c @@ -1333,6 +1333,7 @@ main(int argc, char* argv[]) if(NSS_NoDB_Init(".") != SECSuccess) fatal_exit("could not init NSS"); #endif /* HAVE_SSL or HAVE_NSS*/ + tsig_test(); authzone_test(); neg_test(); rnd_test(); diff --git a/testcode/unitmain.h b/testcode/unitmain.h index 00b5a6220..d4aa83a1c 100644 --- a/testcode/unitmain.h +++ b/testcode/unitmain.h @@ -88,5 +88,7 @@ void tcpreuse_test(void); void doq_test(void); /** unit test for infra cache functions */ void infra_test(void); +/** unit test for tsig functions */ +void tsig_test(void); #endif /* TESTCODE_UNITMAIN_H */ diff --git a/testcode/unittsig.c b/testcode/unittsig.c new file mode 100644 index 000000000..2156c2237 --- /dev/null +++ b/testcode/unittsig.c @@ -0,0 +1,49 @@ +/* + * testcode/unittsig.c - unit test for TSIG signatures. + * + * Copyright (c) 2025, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +/** + * \file + * Unit test for tsig code. + */ +#include "config.h" +#include "util/tsig.h" +#include "testcode/unitmain.h" + +/** test tsig code */ +void +tsig_test(void) +{ + unit_show_feature("tsig"); +} diff --git a/util/tsig.c b/util/tsig.c index 984e3ba5b..69094e088 100644 --- a/util/tsig.c +++ b/util/tsig.c @@ -54,6 +54,9 @@ #include #include +/** Fudge time to allow in signed time for TSIG records. In seconds. */ +#define TSIG_FUDGE_TIME 300 + /** * The list of TSIG algorithms. It has short_name, wireformat_name, * wireformat_name_len, digest, max_digest_size. @@ -613,6 +616,15 @@ tsig_create(struct tsig_key_table* key_table, uint8_t* name, size_t namelen) return NULL; } lock_rw_unlock(&key_table->lock); + + tsig->original_query_id = 0; + tsig->klass = LDNS_RR_CLASS_ANY; + tsig->ttl = 0; + tsig->time_signed = 0; + tsig->fudge = TSIG_FUDGE_TIME; /* seconds */ + tsig->error = 0; + tsig->other_len = 0; + tsig->other_time = 0; return tsig; } @@ -636,3 +648,159 @@ tsig_delete(struct tsig_data* tsig) free(tsig->mac); free(tsig); } + +size_t +tsig_reserved_space(struct tsig_data* tsig) +{ + if(!tsig) + return 0; + return + tsig->key_name_len /* Owner */ + + sizeof(uint16_t) /* Type */ + + sizeof(uint16_t) /* Class */ + + sizeof(uint32_t) /* TTL */ + + sizeof(uint16_t) /* RDATA length */ + + tsig->algorithm_name_len /* Algorithm */ + + 6 /* 48bit */ /* Signed time */ + + sizeof(uint16_t) /* Fudge */ + + sizeof(uint16_t) /* Mac size */ + + tsig->mac_size /* Mac data */ + + sizeof(uint16_t) /* Original query ID */ + + sizeof(uint16_t) /* Error code */ + + sizeof(uint16_t) /* Other size */ + + 6; /* 48bit in case of Other data */ +} + +/** + * Calculate the digest for the TSIG algorithm over the packet. + * Called must hold locks, on key. This routine performs one-host + * calculation. + * @param key: the key, and algorithm. + * @param pkt: packet with contents. + * @param tsig: where to store the result. + * @return false on failure. + */ +static int +tsig_algo_calc(struct tsig_key* key, struct sldns_buffer* pkt, + struct tsig_data* tsig) +{ + const EVP_MD* evp_md; + unsigned int hmac_result_len; + unsigned char* hmac_result; + + /* Setup digester algorithm */ + evp_md = EVP_get_digestbyname(key->algo->digest); + if(!evp_md) { + /* Could not fetch algorithm. */ + char name[256], buf[1024]; + dname_str(key->name, name); + snprintf(buf, sizeof(buf), "EVP_get_digestbyname failed for %s %s", + name, key->algo->digest); + log_crypto_err(buf); + return 0; + } + + /* Perform calculation */ + hmac_result_len = tsig->mac_size; + hmac_result = HMAC(evp_md, key->data, key->data_len, + sldns_buffer_begin(pkt), sldns_buffer_position(pkt), + tsig->mac, &hmac_result_len); + if(!hmac_result) { + /* The HMAC calculation failed. */ + char name[256], buf[1024]; + dname_str(key->name, name); + snprintf(buf, sizeof(buf), "HMAC failed for %s %s", + name, key->algo->digest); + log_crypto_err(buf); + return 0; + } + return 1; +} + +int +tsig_sign_query(struct tsig_data* tsig, struct sldns_buffer* pkt, + struct tsig_key_table* key_table, uint64_t now) +{ + size_t aftername_pos; + struct tsig_key* key; + if(!tsig) + return 0; + tsig->time_signed = now; + tsig->fudge = TSIG_FUDGE_TIME; /* seconds */ + if(sldns_buffer_remaining(pkt) < tsig_reserved_space(tsig)) { + /* Not enough space in buffer for packet and TSIG. */ + return 0; + } + lock_rw_rdlock(&key_table->lock); + key = tsig_key_table_search(key_table, tsig->key_name, + tsig->key_name_len); + if(!key) { + /* The tsig key has disappeared from the key table. */ + lock_rw_unlock(&key_table->lock); + return 0; + } + + tsig->original_query_id = sldns_buffer_read_u16_at(pkt, 0); + + /* What is signed is this buffer: + * + * TSIG owner is key name + * u16 class, u32 TTL, algorithm_name, u48 time_signed, + * u16 fudge, u16 error, u16 other_len, other_data. */ + /* That fits in the current buffer, since the reserved space for + * the TSIG record is larger. */ + + /* Write uncompressed TSIG owner, it is the key name. */ + sldns_buffer_write(pkt, tsig->key_name, tsig->key_name_len); + aftername_pos = sldns_buffer_position(pkt); + sldns_buffer_write_u16(pkt, tsig->klass); + sldns_buffer_write_u32(pkt, tsig->ttl); + sldns_buffer_write(pkt, key->algo->wireformat_name, + key->algo->wireformat_name_len); + sldns_buffer_write_u48(pkt, tsig->time_signed); + sldns_buffer_write_u16(pkt, tsig->fudge); + sldns_buffer_write_u16(pkt, tsig->error); + sldns_buffer_write_u16(pkt, tsig->other_len); + if(tsig->other_len != 0) + sldns_buffer_write_u48(pkt, tsig->other_time); + + /* Sign it */ + if(!tsig_algo_calc(key, pkt, tsig)) { + /* Failure to calculate digest. */ + lock_rw_unlock(&key_table->lock); + return 0; + } + + /* Append TSIG record */ + /* The record appended consists of: + * owner name, u16 type, u16 class, u32 TTL, u16 rdlength, + * algo name, u48 signed_time, u16 fudge, u16 mac_len, mac data, + * u16 original_query_id, u16 error, u16 other_len, other data. + */ + sldns_buffer_set_position(pkt, aftername_pos); + sldns_buffer_write_u16(pkt, LDNS_RR_TYPE_TSIG); + sldns_buffer_write_u16(pkt, tsig->klass); + sldns_buffer_write_u32(pkt, tsig->ttl); + + /* rdlength */ + sldns_buffer_write_u16(pkt, key->algo->wireformat_name_len + + 6 + 2 + 2 /* time,fudge,maclen */ + tsig->mac_size + + 2 + 2 + 2 /* id,error,otherlen */ + tsig->other_len); + sldns_buffer_write(pkt, key->algo->wireformat_name, + key->algo->wireformat_name_len); + sldns_buffer_write_u48(pkt, tsig->time_signed); + sldns_buffer_write_u16(pkt, tsig->fudge); + sldns_buffer_write_u16(pkt, tsig->mac_size); + sldns_buffer_write(pkt, tsig->mac, tsig->mac_size); + sldns_buffer_write_u16(pkt, tsig->original_query_id); + sldns_buffer_write_u16(pkt, tsig->error); + sldns_buffer_write_u16(pkt, tsig->other_len); + if(tsig->other_len == 6) + sldns_buffer_write_u48(pkt, tsig->other_time); + + LDNS_ARCOUNT_SET(sldns_buffer_begin(pkt), + LDNS_ARCOUNT(sldns_buffer_begin(pkt))+1); + + lock_rw_unlock(&key_table->lock); + return 1; +} diff --git a/util/tsig.h b/util/tsig.h index d982d840a..b63ecd530 100644 --- a/util/tsig.h +++ b/util/tsig.h @@ -95,6 +95,22 @@ struct tsig_data { size_t mac_size; /** digest buffer */ uint8_t* mac; + /** original query ID */ + uint16_t original_query_id; + /** the TSIG class */ + uint16_t klass; + /** the TSIG TTL */ + uint16_t ttl; + /** the time signed, 48bit */ + uint64_t time_signed; + /** fudge amount of time_signed */ + uint16_t fudge; + /** the TSIG error code */ + uint16_t error; + /** other data length, 6 for other_time as failed time. */ + uint16_t other_len; + /** if other len 6, this is 48bit time of error. */ + uint64_t other_time; }; /** @@ -247,9 +263,12 @@ void tsig_delete(struct tsig_data* tsig); * Sign a query with TSIG. Appends the TSIG record. * @param tsig: the tsig data, keeps state to verify reply. * @param pkt: query packet. position must be at end of packet. + * @param key_table: the tsig key table is used to fetch the key details. + * @param now: time to sign the query, the current time. * @return false on failure. */ -int tsig_sign_query(struct tsig_data* tsig, struct sldns_buffer* pkt); +int tsig_sign_query(struct tsig_data* tsig, struct sldns_buffer* pkt, + struct tsig_key_table* key_table, uint64_t now); /** * Verify a query with TSIG. @@ -308,4 +327,11 @@ int tsig_sign_reply(struct tsig_data* tsig, struct sldns_buffer* pkt); */ int tsig_verify_reply(struct tsig_data* tsig, struct sldns_buffer* pkt); +/** + * Calculate reserved space for TSIG. + * @param tsig: the tsig data + * @return number of bytes to keep reserved for the TSIG added. + */ +size_t tsig_reserved_space(struct tsig_data* tsig); + #endif /* UTIL_TSIG_H */ From 4bbb74da398ed3295b571a551f57b4f1119510b6 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 18 Jun 2025 16:41:10 +0200 Subject: [PATCH 18/43] - xfr-tsig, tsig test. --- testcode/unittsig.c | 243 ++++++++++++++++++++++++++++++++++++++++++++ util/tsig.c | 26 ++++- util/tsig.h | 9 ++ 3 files changed, 276 insertions(+), 2 deletions(-) diff --git a/testcode/unittsig.c b/testcode/unittsig.c index 2156c2237..197623175 100644 --- a/testcode/unittsig.c +++ b/testcode/unittsig.c @@ -39,11 +39,254 @@ */ #include "config.h" #include "util/tsig.h" +#include "util/config_file.h" #include "testcode/unitmain.h" +#include "sldns/sbuffer.h" +#include + +#define xstr(s) str(s) +#define str(s) #s +#define SRCDIRSTR xstr(SRCDIR) + +/** verbosity for this file */ +static int vtest = 0; + +/** + * Content of the TSIG test files. + * + * The tsig test files have this syntax. It is made of lines, lines started + * with # are a comment. empty lines are ignored. + * file_algorithm + * The name is like md5, sha1, sha256 and if the algorithm is not + * supported at the test run time, the file is skipped, silently. + * tsig-key: + * the following lines define name:, algorithm: and secret: + * and it adds a tsig-key that can be used. + * del-key + * The tsig key is deleted, from the in-memory key table. + * + */ + +/** Clean up first keyword */ +static char* +get_keyword(char* line) +{ + char* s = line; + while(isspace(*s)) + s++; + if(strlen(s)>0 && s[strlen(s)-1] == '\n') + s[strlen(s)-1] = 0; + return s; +} + +/** Get argument from line */ +static char* +get_arg_on_line(char* line, char* keyword) +{ + char* s = line; + s += strlen(keyword); + while(isspace(*s)) + s++; + return s; +} + +/** See if algorithm is supported for tsig test */ +static int +tsig_algo_test(char* algo) +{ + if(strcmp(algo, "md5") == 0) + return 1; + if(strcmp(algo, "sha1") == 0) + return 1; + if(strcmp(algo, "sha224") == 0) { + /* The EVP_sha256 test is also used for sha224. */ +#ifdef HAVE_EVP_SHA256 + return 1; +#else + return 0; +#endif + } + if(strcmp(algo, "sha256") == 0) { +#ifdef HAVE_EVP_SHA256 + return 1; +#else + return 0; +#endif + } + if(strcmp(algo, "sha384") == 0) { + /* The EVP_sha512 test is also used for sha384. */ +#ifdef HAVE_EVP_SHA512 + return 1; +#else + return 0; +#endif + } + if(strcmp(algo, "sha512") == 0) { +#ifdef HAVE_EVP_SHA512 + return 1; +#else + return 0; +#endif + } + if(vtest) + printf("Unknown tsig test algorithm %s\n", algo); + return 0; +} + +/** Handle the file_algorithm */ +static void +handle_file_algorithm(char* line, int* break_file) +{ + char* algo = get_arg_on_line(line, "file_algorithm"); + if(!tsig_algo_test(algo)) { + if(vtest) + printf("algorithm not supported\n"); + *break_file = 1; + return; + } + if(vtest) + printf("algorithm supported\n"); +} + +/** Removes quotes if any */ +static char* +quote_removal(char* line) +{ + if(line[0] == '"') { + char* s = line+1; + if(strlen(s)>0 && s[strlen(s)-1] == '"') + s[strlen(s)-1] = 0; + return s; + } + return line; +} + +/** Handle the tsig-key */ +static void +handle_tsig_key(struct tsig_key_table* key_table, FILE* in, const char* fname) +{ + char line[1024]; + char* s; + char* name = NULL, *algorithm = NULL, *secret = NULL; + struct config_tsig_key k; + + while(fgets(line, sizeof(line), in)) { + line[sizeof(line)-1]=0; + s = get_keyword(line); + if(strncmp(s, "name:", 5) == 0) { + name = strdup(quote_removal( + get_arg_on_line(s, "name:"))); + if(!name) + fatal_exit("out of memory"); + } else if(strncmp(s, "algorithm:", 10) == 0) { + algorithm = strdup(quote_removal( + get_arg_on_line(s, "algorithm:"))); + if(!algorithm) + fatal_exit("out of memory"); + } else if(strncmp(s, "secret:", 7) == 0) { + secret = strdup(quote_removal( + get_arg_on_line(s, "secret:"))); + if(!secret) + fatal_exit("out of memory"); + } else { + fatal_exit("unknown tsig-key element %s: %s", + fname, s); + } + + if(name && algorithm && secret) + break; + } + + k.next = NULL; + k.name = name; + k.algorithm = algorithm; + k.secret = secret; + if(!tsig_key_table_add_key(key_table, &k)) + fatal_exit("could not tsig_key_table_add_key, out of memory"); + if(vtest) + printf("add key %s %s\n", name, algorithm); + + free(name); + free(algorithm); + explicit_bzero(secret, strlen(secret)); + free(secret); +} + +/** Handle the del_key */ +static void +handle_del_key(char* line, struct tsig_key_table* key_table) +{ + char* name = get_arg_on_line(line, "del_key"); + tsig_key_table_del_key_fromstr(key_table, name); + if(vtest) + printf("deleted key %s\n", name); +} + +/** Handle one line from the TSIG test file */ +static void +handle_line(char* line, struct tsig_key_table* key_table, + struct sldns_buffer* pkt, FILE* in, const char* fname, + int* break_file) +{ + char* s = get_keyword(line); + if(vtest) + printf("line: %s\n", s); + if(strncmp(s, "file_algorithm", 14) == 0) { + handle_file_algorithm(s, break_file); + } else if(strcmp(s, "tsig-key:") == 0) { + handle_tsig_key(key_table, in, fname); + } else if(strncmp(s, "delkey", 6) == 0) { + handle_del_key(s, key_table); + } else if(strncmp(s, "#", 1) == 0) { + /* skip comment */ + } else if(strcmp(s, "") == 0) { + /* skip empty lines */ + } else { + fatal_exit("Unknown tsig line %s: %s", fname, s); + } + (void)pkt; +} + +/** test tsig */ +static void +tsig_test_one(const char* fname) +{ + struct tsig_key_table* key_table; + sldns_buffer* pkt; + FILE* in; + char line[1024]; + int break_file = 0; + + unit_show_func("tsig", fname); + key_table = tsig_key_table_create(); + if(!key_table) + fatal_exit("out of memory"); + pkt = sldns_buffer_new(65536); + if(!pkt) + fatal_exit("out of memory"); + sldns_buffer_flip(pkt); /* start with empty buffer */ + in = fopen(fname, "r"); + if(!in) + fatal_exit("could not open %s: %s", fname, strerror(errno)); + + while(fgets(line, sizeof(line), in)) { + line[sizeof(line)-1]=0; + handle_line(line, key_table, pkt, in, fname, &break_file); + if(break_file) + break; + } + if(ferror(in)) + fatal_exit("error read %s: %s", fname, strerror(errno)); + + tsig_key_table_delete(key_table); + sldns_buffer_free(pkt); + fclose(in); +} /** test tsig code */ void tsig_test(void) { unit_show_feature("tsig"); + tsig_test_one(SRCDIRSTR "/testdata/tsig_test.1"); } diff --git a/util/tsig.c b/util/tsig.c index 69094e088..7a82848d3 100644 --- a/util/tsig.c +++ b/util/tsig.c @@ -174,8 +174,7 @@ tsig_key_create(const char* name, const char* algorithm, const char* secret) return key; } -/** Add a key to the TSIG key table. */ -static int +int tsig_key_table_add_key(struct tsig_key_table* key_table, struct config_tsig_key* s) { @@ -198,6 +197,29 @@ tsig_key_table_add_key(struct tsig_key_table* key_table, return 1; } +void +tsig_key_table_del_key_fromstr(struct tsig_key_table* key_table, + char* name) +{ + uint8_t buf[LDNS_MAX_DOMAINLEN+1]; + size_t len = sizeof(buf); + struct tsig_key k, *key; + rbnode_type* node; + if(!sldns_str2wire_dname_buf(name, buf, &len)) { + log_err("could not parse '%s'", name); + return; + } + + k.node.key = &k; + k.name = buf; + k.name_len = len; + node = rbtree_delete(key_table->tree, &k); + if(!node) + return; + key = (struct tsig_key*)node->key; + tsig_key_delete(key); +} + int tsig_key_table_apply_cfg(struct tsig_key_table* key_table, struct config_file* cfg) diff --git a/util/tsig.h b/util/tsig.h index b63ecd530..5c44d4f06 100644 --- a/util/tsig.h +++ b/util/tsig.h @@ -45,6 +45,7 @@ #include "util/rbtree.h" struct sldns_buffer; struct config_file; +struct config_tsig_key; struct regional; /** @@ -181,6 +182,14 @@ struct tsig_key_table* tsig_key_table_create(void); */ void tsig_key_table_delete(struct tsig_key_table* key_table); +/** Add a key to the TSIG key table. */ +int tsig_key_table_add_key(struct tsig_key_table* key_table, + struct config_tsig_key* s); + +/** Delete a key from the TSIG key table. */ +void tsig_key_table_del_key_fromstr(struct tsig_key_table* key_table, + char* name); + /** * Apply config to the tsig key table. * @param key_table: the tsig key table. From aa22fd936e3fd0a97b84d235081420771c123bbe Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 18 Jun 2025 17:01:35 +0200 Subject: [PATCH 19/43] - xfr-tsig, test buffer size. --- util/tsig.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/util/tsig.c b/util/tsig.c index 7a82848d3..18c04a3b4 100644 --- a/util/tsig.c +++ b/util/tsig.c @@ -771,6 +771,13 @@ tsig_sign_query(struct tsig_data* tsig, struct sldns_buffer* pkt, * u16 fudge, u16 error, u16 other_len, other_data. */ /* That fits in the current buffer, since the reserved space for * the TSIG record is larger. */ + if(!sldns_buffer_available(pkt, tsig->key_name_len + 2 + 4 + + key->algo->wireformat_name_len + 6 + 2 + 2 + + 2 + tsig->other_len)) { + /* Buffer is too small */ + lock_rw_unlock(&key_table->lock); + return 0; + } /* Write uncompressed TSIG owner, it is the key name. */ sldns_buffer_write(pkt, tsig->key_name, tsig->key_name_len); From f2c609b9a5b334606035695bf728f2f96800853e Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 20 Jun 2025 12:13:51 +0200 Subject: [PATCH 20/43] - xfr-tsig, unit test for tsig_sign_query. --- testcode/unitmain.c | 2 +- testcode/unittsig.c | 253 +++++++++++++++++++++++++++++++++++++++++++- util/tsig.c | 8 +- 3 files changed, 256 insertions(+), 7 deletions(-) diff --git a/testcode/unitmain.c b/testcode/unitmain.c index 059206678..e88313ee7 100644 --- a/testcode/unitmain.c +++ b/testcode/unitmain.c @@ -1333,7 +1333,6 @@ main(int argc, char* argv[]) if(NSS_NoDB_Init(".") != SECSuccess) fatal_exit("could not init NSS"); #endif /* HAVE_SSL or HAVE_NSS*/ - tsig_test(); authzone_test(); neg_test(); rnd_test(); @@ -1363,6 +1362,7 @@ main(int argc, char* argv[]) #ifdef HAVE_NGTCP2 doq_test(); #endif /* HAVE_NGTCP2 */ + tsig_test(); if(log_get_lock()) { lock_basic_destroy((lock_basic_type*)log_get_lock()); } diff --git a/testcode/unittsig.c b/testcode/unittsig.c index 197623175..a352100c7 100644 --- a/testcode/unittsig.c +++ b/testcode/unittsig.c @@ -40,15 +40,18 @@ #include "config.h" #include "util/tsig.h" #include "util/config_file.h" +#include "util/net_help.h" #include "testcode/unitmain.h" +#include "sldns/parseutil.h" #include "sldns/sbuffer.h" +#include "sldns/wire2str.h" #include #define xstr(s) str(s) #define str(s) #s #define SRCDIRSTR xstr(SRCDIR) -/** verbosity for this file */ +/** verbosity for this file, 0 no, 1 print some, 2 print packet dumps */ static int vtest = 0; /** @@ -56,15 +59,28 @@ static int vtest = 0; * * The tsig test files have this syntax. It is made of lines, lines started * with # are a comment. empty lines are ignored. - * file_algorithm + * file-algorithm * The name is like md5, sha1, sha256 and if the algorithm is not * supported at the test run time, the file is skipped, silently. + * * tsig-key: * the following lines define name:, algorithm: and secret: * and it adds a tsig-key that can be used. * del-key * The tsig key is deleted, from the in-memory key table. * + * packet + * A packet in hex dump, on the following lines. Until 'endpacket'. + * It can be used to sign or verify. + * check-packet + * A packet in hex dump, on the following lines. Until 'endpacket'. + * It is compared to the packet buffer, and the test fails if not equal. + * + * tsig-sign-query