]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
- xfr-tsig, tsig_sign_query.
authorW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Wed, 18 Jun 2025 13:00:18 +0000 (15:00 +0200)
committerW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Wed, 18 Jun 2025 13:00:18 +0000 (15:00 +0200)
Makefile.in
testcode/unitmain.c
testcode/unitmain.h
testcode/unittsig.c [new file with mode: 0644]
util/tsig.c
util/tsig.h

index 095717e1608d83ab1cc54619db08d230d3b0d2d5..417e8c4ef534a0ae4cfeeed68e47c65942838f05 100644 (file)
@@ -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 \
index 07c016d7ba7431a3960716cfdd6fb9627faab037..05920667893efc39457024d370cdd289569f9088 100644 (file)
@@ -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();
index 00b5a622087325ca1b607158aaa7a7d2c0233e24..d4aa83a1cf4a2e68352f1b697c85fe510b24ae72 100644 (file)
@@ -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 (file)
index 0000000..2156c22
--- /dev/null
@@ -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");
+}
index 984e3ba5bdc75ee7094107812496078d67f2a404..69094e088df1b8dc4564af739f1fdf7556bc053c 100644 (file)
@@ -54,6 +54,9 @@
 #include <openssl/evp.h>
 #include <openssl/hmac.h>
 
+/** 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:
+        * <packet with original query id and ARCOUNT without TSIG>
+        * <name> TSIG owner is key name
+        * u16 class, u32 TTL, <name> algorithm_name, u48 time_signed,
+        * u16 fudge, u16 error, u16 other_len, <data> 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;
+}
index d982d840ae599cb7e9e4abfe324968886b1f7e47..b63ecd53072e5816648345b88fa5e4682feee7da 100644 (file)
@@ -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 */