]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
- xfr-tsig, parse and verify query tsig.
authorW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Tue, 24 Jun 2025 14:31:18 +0000 (16:31 +0200)
committerW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Tue, 24 Jun 2025 14:31:18 +0000 (16:31 +0200)
config.h.in
configure
configure.ac
sldns/rrdef.h
sldns/wire2str.c
testcode/unittsig.c
util/tsig.c
util/tsig.h

index b166f6f23e86a91e9d641fa35cac55b44f408034..3fe6c73a283a2cf077b58e3982db1b8d73c1a2a6 100644 (file)
 /* Define to 1 if you have the `EVP_EncryptInit_ex' function. */
 #undef HAVE_EVP_ENCRYPTINIT_EX
 
+/* Define to 1 if you have the `EVP_MAC_CTX_new' function. */
+#undef HAVE_EVP_MAC_CTX_NEW
+
 /* Define to 1 if you have the `EVP_MAC_CTX_set_params' function. */
 #undef HAVE_EVP_MAC_CTX_SET_PARAMS
 
 /* Define to 1 if you have the <hiredis/hiredis.h> header file. */
 #undef HAVE_HIREDIS_HIREDIS_H
 
+/* Define to 1 if you have the `HMAC_CTX_new' function. */
+#undef HAVE_HMAC_CTX_NEW
+
 /* Define to 1 if you have the `HMAC_Init_ex' function. */
 #undef HAVE_HMAC_INIT_EX
 
index af58f5ef6f1d48c585aa67dc6f9d42bd44c5bb42..e9d8a473fed1b40b15df0399dc07af6ae2975aec 100755 (executable)
--- a/configure
+++ b/configure
@@ -20824,6 +20824,18 @@ then :
   printf "%s\n" "#define HAVE_SSL_CTX_SET_TMP_ECDH 1" >>confdefs.h
 
 fi
+ac_fn_c_check_func "$LINENO" "HMAC_CTX_new" "ac_cv_func_HMAC_CTX_new"
+if test "x$ac_cv_func_HMAC_CTX_new" = xyes
+then :
+  printf "%s\n" "#define HAVE_HMAC_CTX_NEW 1" >>confdefs.h
+
+fi
+ac_fn_c_check_func "$LINENO" "EVP_MAC_CTX_new" "ac_cv_func_EVP_MAC_CTX_new"
+if test "x$ac_cv_func_EVP_MAC_CTX_new" = xyes
+then :
+  printf "%s\n" "#define HAVE_EVP_MAC_CTX_NEW 1" >>confdefs.h
+
+fi
 
 
 # these check_funcs need -lssl
index ff50e1e27f4a7f9e2377a887680b838fea620f53..d2e9b47f4508eb37dbf2bef4cf1fdcf1c70e3a0b 100644 (file)
@@ -996,7 +996,7 @@ else
        AC_MSG_RESULT([no])
 fi
 AC_CHECK_HEADERS([openssl/conf.h openssl/engine.h openssl/bn.h openssl/dh.h openssl/dsa.h openssl/rsa.h openssl/core_names.h openssl/param_build.h],,, [AC_INCLUDES_DEFAULT])
-AC_CHECK_FUNCS([OPENSSL_config EVP_sha1 EVP_sha256 EVP_sha512 FIPS_mode EVP_default_properties_is_fips_enabled EVP_MD_CTX_new OpenSSL_add_all_digests OPENSSL_init_crypto EVP_cleanup ENGINE_cleanup ERR_load_crypto_strings CRYPTO_cleanup_all_ex_data ERR_free_strings RAND_cleanup DSA_SIG_set0 EVP_dss1 EVP_DigestVerify EVP_aes_256_cbc EVP_EncryptInit_ex HMAC_Init_ex CRYPTO_THREADID_set_callback EVP_MAC_CTX_set_params OSSL_PARAM_BLD_new BIO_set_callback_ex SSL_CTX_set_tmp_ecdh])
+AC_CHECK_FUNCS([OPENSSL_config EVP_sha1 EVP_sha256 EVP_sha512 FIPS_mode EVP_default_properties_is_fips_enabled EVP_MD_CTX_new OpenSSL_add_all_digests OPENSSL_init_crypto EVP_cleanup ENGINE_cleanup ERR_load_crypto_strings CRYPTO_cleanup_all_ex_data ERR_free_strings RAND_cleanup DSA_SIG_set0 EVP_dss1 EVP_DigestVerify EVP_aes_256_cbc EVP_EncryptInit_ex HMAC_Init_ex CRYPTO_THREADID_set_callback EVP_MAC_CTX_set_params OSSL_PARAM_BLD_new BIO_set_callback_ex SSL_CTX_set_tmp_ecdh HMAC_CTX_new EVP_MAC_CTX_new])
 
 # these check_funcs need -lssl
 BAKLIBS="$LIBS"
index 5404688898801e9f8c50d0e46b2b9308adeaac42..52d32e183f9652ef96165dd399cb52ac01d31b1f 100644 (file)
@@ -494,6 +494,7 @@ typedef enum sldns_enum_ede_code sldns_ede_code;
 #define LDNS_TSIG_ERROR_BADMODE  19
 #define LDNS_TSIG_ERROR_BADNAME  20
 #define LDNS_TSIG_ERROR_BADALG   21
+#define LDNS_TSIG_ERROR_BADTRUNC 22
 
 /** DNS Cookie extended rcode */
 #define LDNS_EXT_RCODE_BADCOOKIE 23
index 1bc5b9cf6e93c5f7c84689f7b419ad3b02ea5354..b2ac2794329fc0a83ae5c54c614ad590f94fc409 100644 (file)
@@ -255,6 +255,7 @@ static sldns_lookup_table sldns_tsig_errors_data[] = {
        { LDNS_TSIG_ERROR_BADMODE, "BADMODE" },
        { LDNS_TSIG_ERROR_BADNAME, "BADNAME" },
        { LDNS_TSIG_ERROR_BADALG, "BADALG" },
+       { LDNS_TSIG_ERROR_BADTRUNC, "BADTRUNC" },
        { 0, NULL }
 };
 sldns_lookup_table* sldns_tsig_errors = sldns_tsig_errors_data;
index 1092db1f3ac91a5958179157a947882e9544d64a..9b4c0539339302d203af3bb4c7cfd7a11434b934 100644 (file)
@@ -80,6 +80,7 @@ static int vtest = 0;
  *     It TSIG signs with key name, at timestamp in secs, and the
  *     result of the call is compared with the expected result, and
  *     the test fails if not equal. The result is in the packet buffer.
+ * tsig-verify-query <key> <time> <rcode> <tsigerror> <tsigothertime>
  *
  */
 
@@ -463,6 +464,96 @@ handle_tsig_sign_query(char* line, struct tsig_key_table* key_table,
        tsig_delete(tsig);
 }
 
+/** Handle the tsig-verify-query */
+static void
+handle_tsig_verify_query(char* line, struct tsig_key_table* key_table,
+       struct sldns_buffer* pkt)
+{
+       char* arg = get_arg_on_line(line, "tsig-verify-query");
+       char* keyname, *s, *timestr, *expected_rcode_str,
+               *expected_tsigerr_str, *expected_other_str;
+       int expected_rcode, expected_tsigerr, expected_other, ret;
+       uint64_t timepoint;
+       struct tsig_data* tsig;
+
+       keyname = arg;
+       s = arg;
+
+       s = strchr(s, ' ');
+       if(!s || !*s)
+               fatal_exit("expected arguments for %s", arg);
+       *s++ = 0;
+       while(*s && *s == ' ')
+               s++;
+       timestr = s;
+
+       s = strchr(s, ' ');
+       if(!s || !*s)
+               fatal_exit("expected arguments for %s", arg);
+       *s++ = 0;
+       while(*s && *s == ' ')
+               s++;
+       expected_rcode_str = s;
+
+       s = strchr(s, ' ');
+       if(!s || !*s)
+               fatal_exit("expected arguments for %s", arg);
+       *s++ = 0;
+       while(*s && *s == ' ')
+               s++;
+       expected_tsigerr_str = s;
+
+       s = strchr(s, ' ');
+       if(!s || !*s)
+               fatal_exit("expected arguments for %s", arg);
+       *s++ = 0;
+       while(*s && *s == ' ')
+               s++;
+       expected_other_str = s;
+
+       timepoint = (uint64_t)atoll(timestr);
+       if(timepoint == 0 && strcmp(timestr, "0") != 0)
+               fatal_exit("expected time argument for %s", timestr);
+       expected_rcode = atoi(expected_rcode_str);
+       if(expected_rcode == 0 && strcmp(expected_rcode_str, "0") != 0)
+               fatal_exit("expected int argument for %s", expected_rcode_str);
+       expected_tsigerr = atoi(expected_tsigerr_str);
+       if(expected_tsigerr == 0 && strcmp(expected_tsigerr_str, "0") != 0)
+               fatal_exit("expected int argument for %s", expected_tsigerr_str);
+       expected_other = atoi(expected_other_str);
+       if(expected_other == 0 && strcmp(expected_other_str, "0") != 0)
+               fatal_exit("expected int argument for %s", expected_other_str);
+
+       if(vtest)
+               printf("tsig-sign-query with %s %d %d\n", keyname,
+                       (int)timepoint, expected_rcode);
+
+       /* Put position before TSIG */
+       ret = tsig_parse_verify_query(key_table, pkt, &tsig, NULL, timepoint);
+
+       if(vtest) {
+               if(ret == expected_rcode)
+                       printf("function ok, %s\n", (ret?"success":"fail"));
+               else
+                       printf("function returned %d, expected result %d\n",
+                               ret, expected_rcode);
+       }
+       unit_assert(ret == expected_rcode);
+       if(tsig) {
+               unit_assert(tsig->error == expected_tsigerr);
+               if(tsig->other_len == 6) {
+                       unit_assert(tsig->other_time == (uint64_t)expected_other);
+               } else {
+                       unit_assert(0 == expected_other);
+               }
+       } else {
+               unit_assert(0 == expected_tsigerr);
+               unit_assert(0 == expected_other);
+       }
+
+       tsig_delete(tsig);
+}
+
 /** Handle one line from the TSIG test file */
 static void
 handle_line(char* line, struct tsig_key_table* key_table,
@@ -484,6 +575,8 @@ handle_line(char* line, struct tsig_key_table* key_table,
                handle_check_packet(s, pkt, in, fname);
        } else if(strncmp(s, "tsig-sign-query", 15) == 0) {
                handle_tsig_sign_query(s, key_table, pkt);
+       } else if(strncmp(s, "tsig-verify-query", 17) == 0) {
+               handle_tsig_verify_query(s, key_table, pkt);
        } else if(strncmp(s, "#", 1) == 0) {
                /* skip comment */
        } else if(strcmp(s, "") == 0) {
index 6cdc9353493ea659e5bc9bb391936acb0dfd4481..41037fd2acda34945600f0c053391eab3b2bd4cd 100644 (file)
@@ -44,6 +44,7 @@
 #include "util/config_file.h"
 #include "util/log.h"
 #include "util/net_help.h"
+#include "util/regional.h"
 #include "sldns/parseutil.h"
 #include "sldns/pkthdr.h"
 #include "sldns/rrdef.h"
@@ -53,6 +54,9 @@
 #include "util/data/dname.h"
 #include <openssl/evp.h>
 #include <openssl/hmac.h>
+#ifdef HAVE_OPENSSL_CORE_NAMES_H
+#include <openssl/core_names.h>
+#endif
 
 /** Fudge time to allow in signed time for TSIG records. In seconds. */
 #define TSIG_FUDGE_TIME 300
@@ -400,9 +404,9 @@ tsig_verify(sldns_buffer* pkt, const uint8_t* name, const uint8_t* alg,
 {
        size_t   n_rrs;
        size_t   end_of_message;
-       uint8_t  mac[1024];
+       uint8_t  mac[2048];
        uint16_t mac_sz;
-       uint8_t  hmac_result[1024];
+       uint8_t  hmac_result[2048];
        unsigned int hmac_result_len;
        size_t   pos;
        uint16_t other_len;
@@ -695,7 +699,7 @@ tsig_reserved_space(struct tsig_data* tsig)
 
 /**
  * Calculate the digest for the TSIG algorithm over the packet.
- * Called must hold locks, on key. This routine performs one-host
+ * Called must hold locks, on key. This routine performs one buffer
  * calculation.
  * @param key: the key, and algorithm.
  * @param pkt: packet with contents.
@@ -739,6 +743,184 @@ tsig_algo_calc(struct tsig_key* key, struct sldns_buffer* pkt,
        return 1;
 }
 
+/**
+ * Calculate TSIG MAC in parts.
+ * Called must hold locks, on key. The routine concatenates the argument
+ * datas with digest updates.
+ * @param key: the key, and algorithm.
+ * @param pkt: packet with contents.
+ * @param var: variables to digest.
+ * @param tsig: where to store the result.
+ * @return false on failure.
+ */
+static int
+tsig_algo_calc_parts(struct tsig_key* key, struct sldns_buffer* pkt,
+       struct sldns_buffer* var, struct tsig_data* tsig)
+{
+#ifdef HAVE_EVP_MAC_CTX_NEW
+       /* For EVP_MAC_CTX_new and OpenSSL since 3.0 functions. */
+       EVP_MAC* mac = EVP_MAC_fetch(NULL, "hmac", NULL);
+       EVP_MAC_CTX* ctx;
+       OSSL_PARAM params[2];
+       size_t outl = 0;
+       if(!mac) {
+               log_crypto_err("Could not EVP_MAC_FETCH(..hmac..)");
+               return 0;
+       }
+       params[0] = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_DIGEST,
+               (char*)key->algo->digest, 0);
+       params[1] = OSSL_PARAM_construct_end();
+       ctx = EVP_MAC_CTX_new(mac);
+       if(!EVP_MAC_init(ctx, key->data, key->data_len, params)) {
+               log_crypto_err("Could not EVP_MAC_init");
+               EVP_MAC_CTX_free(ctx);
+               EVP_MAC_free(mac);
+               return 0;
+       }
+       if(!EVP_MAC_update(ctx, sldns_buffer_begin(pkt),
+               sldns_buffer_position(pkt))) {
+               log_crypto_err("Could not EVP_MAC_update");
+               EVP_MAC_CTX_free(ctx);
+               EVP_MAC_free(mac);
+               return 0;
+       }
+
+       if(!EVP_MAC_update(ctx, sldns_buffer_begin(var),
+               sldns_buffer_limit(var))) {
+               log_crypto_err("Could not EVP_MAC_update");
+               EVP_MAC_CTX_free(ctx);
+               EVP_MAC_free(mac);
+               return 0;
+       }
+       if(!EVP_MAC_final(ctx, tsig->mac, &outl, tsig->mac_size)) {
+               log_crypto_err("Could not EVP_MAC_final");
+               EVP_MAC_CTX_free(ctx);
+               EVP_MAC_free(mac);
+               return 0;
+       }
+       if(outl != tsig->mac_size) {
+               log_err("Wrong output size of EVP_MAC_final");
+               EVP_MAC_CTX_free(ctx);
+               EVP_MAC_free(mac);
+               return 0;
+       }
+
+       EVP_MAC_CTX_free(ctx);
+       EVP_MAC_free(mac);
+#elif defined(HAVE_HMAC_CTX_NEW)
+       /* For HMAC_CTX_new and OpenSSL since 1.1 functions. */
+       const EVP_MD* evp_md;
+       HMAC_CTX* ctx;
+       unsigned int len;
+       evp_md = EVP_get_digestbyname(key->algo->digest);
+       if(!evp_md) {
+               char buf[256];
+               snprintf(buf, sizeof(buf), "EVP_get_digestbyname %s failed",
+                       key->algo->digest);
+               log_crypto_err(buf);
+               return 0;
+       }
+       ctx = HMAC_CTX_new();
+       if(!ctx) {
+               log_crypto_err("Could not HMAC_CTX_new");
+               return 0;
+       }
+       if(!HMAC_Init_ex(ctx, key->data, key->data_len, evp_md, NULL)) {
+               log_crypto_err("Could not HMAC_Init_ex");
+               HMAC_CTX_free(ctx);
+               return 0;
+       }
+       if(!HMAC_Update(ctx, sldns_buffer_begin(pkt),
+               sldns_buffer_position(pkt))) {
+               log_crypto_err("Could not HMAC_Update");
+               HMAC_CTX_free(ctx);
+               return 0;
+       }
+       if(!HMAC_Update(ctx, sldns_buffer_begin(var),
+               sldns_buffer_limit(var))) {
+               log_crypto_err("Could not HMAC_Update");
+               HMAC_CTX_free(ctx);
+               return 0;
+       }
+       len = (unsigned int)tsig->mac_size;
+       if(!HMAC_Final(ctx, tsig->mac, &len)) {
+               log_crypto_err("Could not HMAC_Final");
+               HMAC_CTX_free(ctx);
+               return 0;
+       }
+       HMAC_CTX_free(ctx);
+#else
+       /* For HMAC_CTX_init and OpenSSL before 1.1 functions. */
+       HMAC_CTX ctx;
+       const EVP_MD* evp_md;
+       unsigned int len;
+#ifndef HMAC_INIT_EX_RETURNS_VOID
+       int r;
+#endif
+
+       memset(&ctx, 0, sizeof(ctx));
+       HMAC_CTX_init(&ctx);
+
+       evp_md = EVP_get_digestbyname(key->algo->digest);
+       if(!evp_md) {
+               char buf[256];
+               snprintf(buf, sizeof(buf), "EVP_get_digestbyname %s failed",
+                       key->algo->digest);
+               log_crypto_err(buf);
+               return 0;
+       }
+#ifndef HMAC_INIT_EX_RETURNS_VOID
+       r =
+#endif
+       HMAC_Init_ex(&ctx, key->data, key->data_len, evp_md, NULL);
+#ifndef HMAC_INIT_EX_RETURNS_VOID
+       if(!r) {
+               log_crypto_err("Could not HMAC_Init_ex");
+               HMAC_CTX_cleanup(&ctx);
+               return 0;
+       }
+#endif
+#ifndef HMAC_INIT_EX_RETURNS_VOID
+       r =
+#endif
+       HMAC_Update(&ctx, sldns_buffer_begin(pkt),
+               sldns_buffer_position(pkt));
+#ifndef HMAC_INIT_EX_RETURNS_VOID
+       if(!r) {
+               log_crypto_err("Could not HMAC_Update");
+               HMAC_CTX_cleanup(&ctx);
+               return 0;
+       }
+#endif
+#ifndef HMAC_INIT_EX_RETURNS_VOID
+       r =
+#endif
+       HMAC_Update(&ctx, sldns_buffer_begin(var),
+               sldns_buffer_limit(var));
+#ifndef HMAC_INIT_EX_RETURNS_VOID
+       if(!r) {
+               log_crypto_err("Could not HMAC_Update");
+               HMAC_CTX_cleanup(&ctx);
+               return 0;
+       }
+#endif
+       len = (unsigned int)tsig->mac_size;
+#ifndef HMAC_INIT_EX_RETURNS_VOID
+       r =
+#endif
+       HMAC_Final(&ctx, tsig->mac, &len);
+#ifndef HMAC_INIT_EX_RETURNS_VOID
+       if(!r) {
+               log_crypto_err("Could not HMAC_Final");
+               HMAC_CTX_cleanup(&ctx);
+               return 0;
+       }
+#endif
+       HMAC_CTX_cleanup(&ctx);
+#endif
+       return 1;
+}
+
 int
 tsig_sign_query(struct tsig_data* tsig, struct sldns_buffer* pkt,
        struct tsig_key_table* key_table, uint64_t now)
@@ -843,3 +1025,360 @@ tsig_sign_query(struct tsig_data* tsig, struct sldns_buffer* pkt,
        lock_rw_unlock(&key_table->lock);
        return 1;
 }
+
+int
+tsig_verify_query(struct tsig_data* tsig, struct sldns_buffer* pkt,
+       struct tsig_key* key, struct tsig_record* rr, uint64_t now)
+{
+       uint16_t current_query_id;
+       uint8_t var_buf[2048];
+       struct sldns_buffer var;
+
+       sldns_buffer_init_frm_data(&var, var_buf, sizeof(var_buf));
+       tsig->error = 0;
+       tsig->other_time = 0;
+       tsig->other_len = 0;
+       tsig->time_signed = now;
+       tsig->original_query_id = rr->original_query_id;
+       current_query_id = sldns_buffer_read_u16_at(pkt, 0);
+
+       /* Create the variables to digest in the buffer. */
+       /* packet with original query ID and ARCOUNT without TSIG. */
+       if(sldns_buffer_capacity(&var) <
+               tsig->key_name_len /* TSIG owner */ + 2 /* class */ +
+               4 /* TTL */ + key->algo->wireformat_name_len /* algo */ +
+               6 /* time signed */ + 2 /* fudge */ + 2 /* error */ +
+               2 /* other len */ + rr->other_size) {
+               verbose(VERB_ALGO, "tsig_verify_query: variable buffer too small");
+               return LDNS_RCODE_SERVFAIL;
+       }
+       sldns_buffer_write_u16_at(pkt, 0, rr->original_query_id);
+
+       /* Write the key name uncompressed */
+       sldns_buffer_write(&var, key->name, key->name_len);
+       sldns_buffer_write_u16(&var, LDNS_RR_CLASS_ANY);
+       sldns_buffer_write_u32(&var, 0); /* TTL */
+       sldns_buffer_write(&var, key->algo->wireformat_name,
+               key->algo->wireformat_name_len);
+       sldns_buffer_write_u48(&var, rr->signed_time);
+       sldns_buffer_write_u16(&var, rr->fudge_time);
+       sldns_buffer_write_u16(&var, rr->error_code);
+       sldns_buffer_write_u16(&var, rr->other_size);
+       sldns_buffer_write(&var, rr->other_data, rr->other_size);
+       sldns_buffer_flip(&var);
+
+       if(!tsig_algo_calc_parts(key, pkt, &var, tsig)) {
+               /* Failure to calculate digest. */
+               sldns_buffer_write_u16_at(pkt, 0, current_query_id);
+               verbose(VERB_ALGO, "tsig_verify_query: failed to calculate digest");
+               return LDNS_RCODE_SERVFAIL;
+       }
+       if(CRYPTO_memcmp(rr->mac_data, tsig->mac, tsig->mac_size) != 0) {
+               /* TSIG has wrong digest. */
+               sldns_buffer_write_u16_at(pkt, 0, current_query_id);
+               if(verbosity >= VERB_ALGO) {
+                       char keynm[256];
+                       dname_str(tsig->key_name, keynm);
+                       verbose(VERB_ALGO, "tsig_verify_query: TSIG %s has wrong digest",
+                               keynm);
+               }
+               tsig->error = LDNS_TSIG_ERROR_BADSIG;
+               if(tsig->mac && tsig->mac_size) {
+                       /* The calculated digest can not be used for a reply.*/
+                       memset(tsig->mac, 0, tsig->mac_size);
+               }
+               return LDNS_RCODE_NOTAUTH;
+       }
+       /* The TSIG digest has verified */
+       sldns_buffer_write_u16_at(pkt, 0, current_query_id);
+
+       /* Check the TSIG timestamp. */
+       /* Time is checked after the MAC is verified, so that a bad MAC
+        * does not get a signed reply. */
+       if( (now > rr->signed_time && now - rr->signed_time > rr->fudge_time) ||
+           (now < rr->signed_time && rr->signed_time - now > rr->fudge_time)) {
+               /* TSIG has wrong timestamp. */
+               if(verbosity >= VERB_ALGO) {
+                       char keynm[256];
+                       dname_str(tsig->key_name, keynm);
+                       verbose(VERB_ALGO, "tsig_verify_query: TSIG %s has wrong timestamp, now=%llu packet time=%llu fudge time=%d",
+                               keynm, (unsigned long long)now,
+                               (unsigned long long)rr->signed_time,
+                               (int)rr->fudge_time);
+                       if(rr->other_size == 6)
+                               verbose(VERB_ALGO, "tsig_verify_query: other time is reported at %llu",
+                                       (unsigned long long)rr->other_time);
+               }
+               tsig->error = LDNS_TSIG_ERROR_BADTIME;
+               tsig->other_len = 6;
+               tsig->other_time = now;
+               tsig->fudge = rr->fudge_time;
+               return LDNS_RCODE_NOTAUTH;
+       }
+
+       return 0;
+}
+
+int
+tsig_parse(struct sldns_buffer* pkt, struct tsig_record* rr)
+{
+       size_t algopos;
+       uint16_t type, klass, rdlength;
+       uint32_t ttl;
+       memset(rr, 0, sizeof(*rr));
+
+       /* The buffer position is at the TSIG record. */
+       if(LDNS_ARCOUNT(sldns_buffer_begin(pkt)) < 1) {
+               /* The packet is malformed */
+               verbose(VERB_ALGO, "tsig_parse: arcount too low");
+               return LDNS_RCODE_FORMERR;
+       }
+       if(sldns_buffer_remaining(pkt) < 1) {
+               /* The packet is malformed */
+               verbose(VERB_ALGO, "tsig_verify_query: packet too short");
+               return LDNS_RCODE_FORMERR;
+       }
+       rr->key_name = sldns_buffer_current(pkt);
+       rr->key_name_len = pkt_dname_len(pkt);
+       if(rr->key_name_len == 0) {
+               verbose(VERB_ALGO, "tsig_verify_query: tsig name malformed");
+               return LDNS_RCODE_FORMERR;
+       }
+
+       if(sldns_buffer_remaining(pkt) < 2 /* type */ + 2 /* class */ +
+               4 /* ttl */ + 2 /* rdlength */) {
+               /* The packet is malformed */
+               verbose(VERB_ALGO, "tsig_verify_query: packet too short");
+               return LDNS_RCODE_FORMERR;
+       }
+       type = sldns_buffer_read_u16(pkt);
+       klass = sldns_buffer_read_u16(pkt);
+       ttl = sldns_buffer_read_u32(pkt);
+       rdlength = sldns_buffer_read_u16(pkt);
+       if(type != LDNS_RR_TYPE_TSIG) {
+               /* The packet is malformed */
+               verbose(VERB_ALGO, "tsig_verify_query: TSIG RR has wrong RR type, not TSIG");
+               return LDNS_RCODE_FORMERR;
+       }
+       if(klass != LDNS_RR_CLASS_ANY) {
+               /* The packet is malformed */
+               verbose(VERB_ALGO, "tsig_verify_query: TSIG RR has wrong RR class, not ANY");
+               return LDNS_RCODE_FORMERR;
+       }
+       if(ttl != 0) {
+               /* The packet is malformed */
+               verbose(VERB_ALGO, "tsig_verify_query: TSIG RR has wrong TTL, not 0");
+               return LDNS_RCODE_FORMERR;
+       }
+       if(sldns_buffer_remaining(pkt) < rdlength) {
+               /* The packet is malformed */
+               verbose(VERB_ALGO, "tsig_verify_query: packet too short for rdlength");
+               return LDNS_RCODE_FORMERR;
+       }
+       if(rdlength < 1 /* algo name first byte */
+               + 6 + 2 + 2 /* time,fudge,maclen */
+               + 2 + 2 + 2 /* id,error,otherlen */) {
+               /* The packet is malformed */
+               verbose(VERB_ALGO, "tsig_verify_query: TSIG record too short");
+               return LDNS_RCODE_FORMERR;
+       }
+
+       algopos = sldns_buffer_position(pkt);
+       rr->algorithm_name = sldns_buffer_current(pkt);
+       rr->algorithm_name_len = query_dname_len(pkt);
+       if(rr->algorithm_name_len == 0 ||
+               rdlength < sldns_buffer_position(pkt)-algopos) {
+               /* The packet is malformed */
+               verbose(VERB_ALGO, "tsig_verify_query: TSIG algorithm name malformed");
+               return LDNS_RCODE_FORMERR;
+       }
+
+       if(sldns_buffer_remaining(pkt) < 6 + 2 + 2 /* time,fudge,maclen */ ||
+               rdlength < sldns_buffer_position(pkt)-algopos) {
+               /* The packet is malformed */
+               verbose(VERB_ALGO, "tsig_verify_query: TSIG record too short");
+               return LDNS_RCODE_FORMERR;
+       }
+       rr->signed_time = sldns_buffer_read_u48(pkt);
+       rr->fudge_time = sldns_buffer_read_u16(pkt);
+       rr->mac_size = sldns_buffer_read_u16(pkt);
+       if(sldns_buffer_remaining(pkt) < rr->mac_size
+               + 2 + 2 + 2 /* id,error,otherlen */ ||
+               rdlength < sldns_buffer_position(pkt)-algopos) {
+               /* The packet is malformed */
+               verbose(VERB_ALGO, "tsig_verify_query: TSIG record too short");
+               return LDNS_RCODE_FORMERR;
+       }
+       if(rr->mac_size > 16384) {
+               /* the hash should not be too big, really 512/8=64 bytes */
+               verbose(VERB_ALGO, "tsig_verify_query: TSIG mac_size too large");
+               return LDNS_RCODE_FORMERR;
+       }
+       rr->mac_data = sldns_buffer_current(pkt);
+       sldns_buffer_skip(pkt, rr->mac_size);
+
+       rr->original_query_id = sldns_buffer_read_u16(pkt);
+       rr->error_code = sldns_buffer_read_u16(pkt);
+       rr->other_size = sldns_buffer_read_u16(pkt);
+       rr->other_data = sldns_buffer_current(pkt);
+
+       if(sldns_buffer_remaining(pkt) < rr->other_size ||
+               rdlength < sldns_buffer_position(pkt)-algopos) {
+               /* The packet is malformed */
+               verbose(VERB_ALGO, "tsig_verify_query: TSIG record too short");
+               return LDNS_RCODE_FORMERR;
+       }
+       if(rr->other_size > 16) {
+               /* The packet is malformed */
+               verbose(VERB_ALGO, "tsig_verify_query: other_size too large");
+               return LDNS_RCODE_FORMERR;
+       }
+       if(rr->other_size == 6)
+               rr->other_time = sldns_buffer_read_u48(pkt);
+       else
+               sldns_buffer_skip(pkt, rr->other_size);
+
+       if(rdlength != sldns_buffer_position(pkt)-algopos) {
+               /* The packet is malformed */
+               verbose(VERB_ALGO, "tsig_verify_query: TSIG record has trailing data");
+               return LDNS_RCODE_FORMERR;
+       }
+       if(sldns_buffer_remaining(pkt) > 0) {
+               /* The packet is malformed */
+               /* Trailing bytes after the RR, or more RRs after the TSIG. */
+               verbose(VERB_ALGO, "tsig_verify_query: TSIG record has trailing data after it");
+               return LDNS_RCODE_FORMERR;
+       }
+
+       return 0;
+}
+
+int
+tsig_lookup_key(struct tsig_key_table* key_table,
+       struct sldns_buffer* pkt, struct tsig_record* rr,
+       struct tsig_data** tsig_ret, struct regional* region,
+       struct tsig_key** key)
+{
+       struct tsig_data* tsig;
+
+       if(region)
+               tsig = regional_alloc_zero(region, sizeof(*tsig));
+       else
+               tsig = calloc(1, sizeof(*tsig));
+       if(!tsig) {
+               log_err("tsig_lookup_key: alloc failure");
+               return LDNS_RCODE_SERVFAIL;
+       }
+       tsig->fudge = TSIG_FUDGE_TIME; /* seconds */
+       if(region)
+               tsig->key_name = regional_alloc(region, rr->key_name_len);
+       else
+               tsig->key_name = malloc(rr->key_name_len);
+       tsig->key_name_len = rr->key_name_len;
+       if(!tsig->key_name) {
+               verbose(VERB_ALGO, "tsig_lookup_key: alloc failure");
+               if(!region)
+                       free(tsig);
+               return LDNS_RCODE_SERVFAIL;
+       }
+       dname_pkt_copy(pkt, tsig->key_name, rr->key_name);
+
+       /* Search for the key. */
+       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 is not in the key table. */
+               lock_rw_unlock(&key_table->lock);
+               if(verbosity >= VERB_ALGO) {
+                       char keynm[256];
+                       dname_str(tsig->key_name, keynm);
+                       verbose(VERB_ALGO, "tsig_verify_query: key %s not in table",
+                               keynm);
+               }
+               tsig->error = LDNS_TSIG_ERROR_BADKEY;
+               *tsig_ret = tsig;
+               return LDNS_RCODE_NOTAUTH;
+       }
+
+       /* Verify that the algorithm name matches the key. */
+       if(query_dname_compare(rr->algorithm_name,
+               (*key)->algo->wireformat_name) != 0) {
+               lock_rw_unlock(&key_table->lock);
+               if(verbosity >= VERB_ALGO) {
+                       char keynm[256], algonm[256];
+                       dname_str(tsig->key_name, keynm);
+                       dname_str(rr->algorithm_name, algonm);
+                       verbose(VERB_ALGO, "tsig_verify_query: TSIG algorithm different for key %s, it has algo %s but the packet has %s",
+                               keynm, (*key)->algo->short_name, algonm);
+               }
+               tsig->error = LDNS_TSIG_ERROR_BADKEY;
+               *tsig_ret = tsig;
+               return LDNS_RCODE_NOTAUTH;
+       }
+
+       /* Check mac size. */
+       if(rr->mac_size != (*key)->algo->max_digest_size ||
+               rr->mac_size > 16384) {
+               lock_rw_unlock(&key_table->lock);
+               if(rr->mac_size < (*key)->algo->max_digest_size &&
+                       rr->mac_size >= (*key)->algo->max_digest_size/2) {
+                       /* MAC truncation is not allowed. */
+                       verbose(VERB_ALGO, "tsig_verify_query: TSIG with truncated mac not allowed");
+                       tsig->error = LDNS_TSIG_ERROR_BADTRUNC;
+                       *tsig_ret = tsig;
+                       return LDNS_RCODE_NOTAUTH;
+               }
+               verbose(VERB_ALGO, "tsig_verify_query: TSIG wrong maclen");
+               if(!region) {
+                       free(tsig->key_name);
+                       free(tsig);
+               }
+               /* The length is just wrong */
+               return LDNS_RCODE_FORMERR;
+       }
+
+       tsig->mac_size = (*key)->algo->max_digest_size;
+       if(region)
+               tsig->mac = regional_alloc_zero(region, tsig->mac_size);
+       else
+               tsig->mac = calloc(1, tsig->mac_size);
+       if(!tsig->mac) {
+               lock_rw_unlock(&key_table->lock);
+               verbose(VERB_ALGO, "tsig_lookup_key: alloc failure");
+               if(!region) {
+                       free(tsig->key_name);
+                       free(tsig);
+               }
+               return LDNS_RCODE_SERVFAIL;
+       }
+
+       *tsig_ret = tsig;
+       return 0;
+}
+
+int
+tsig_parse_verify_query(struct tsig_key_table* key_table,
+       struct sldns_buffer* pkt, struct tsig_data** tsig,
+       struct regional* region, uint64_t now)
+{
+       int ret;
+       struct tsig_record rr;
+       struct tsig_key* key = NULL;
+       *tsig = NULL;
+
+       /* Parse the TSIG RR from the query. */
+       ret = tsig_parse(pkt, &rr);
+       if(ret != 0)
+               return ret;
+
+       /* Lookup key and create tsig data. */
+       ret = tsig_lookup_key(key_table, pkt, &rr, tsig, region, &key);
+       if(ret != 0)
+               return ret;
+
+       /* Verify the TSIG. */
+       ret = tsig_verify_query(*tsig, pkt, key, &rr, now);
+       lock_rw_unlock(&key_table->lock);
+       return ret;
+}
index 5c44d4f06891e4a1731d2eb342d599540185a9e5..4776f728b2eb94ccec6586c105ed178008fbf720 100644 (file)
@@ -61,10 +61,8 @@ struct tsig_record {
        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 signed time, 48bits on the wire */
+       uint64_t signed_time;
        /** the fudge time */
        uint16_t fudge_time;
        /** the mac size, uint16_t on the wire */
@@ -79,6 +77,8 @@ struct tsig_record {
        size_t other_size;
        /** the other data */
        uint8_t* other_data;
+       /** if the other size is 48bit, the timestamp in it. */
+       uint64_t other_time;
 };
 
 /**
@@ -283,16 +283,25 @@ 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.
+ * @param key: the key with algorithm, caller must hold lock.
+ * @param rr: the tsig record parsed from the query.
+ * @param now: time that is used, the current time.
+ * @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_verify_query(struct tsig_data* tsig, struct sldns_buffer* pkt);
+int tsig_verify_query(struct tsig_data* tsig, struct sldns_buffer* pkt,
+       struct tsig_key* key, struct tsig_record* rr, uint64_t now);
 
 /**
  * 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 rr: the TSIG record parsed.
+ * @param tsig_ret: the tsig key is returned here. Or it can be NULL, no TSIG.
  * @param region: if nonNULL used to allocate.
+ * @param key: if the key is in the key_table the key is returned.
+ *     On success the key table is locked for the key.
  * @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,
@@ -301,9 +310,19 @@ int tsig_verify_query(struct tsig_data* tsig, struct sldns_buffer* pkt);
  * 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);
+int tsig_lookup_key(struct tsig_key_table* key_table,
+       struct sldns_buffer* pkt, struct tsig_record* rr,
+       struct tsig_data** tsig_ret, struct regional* region,
+       struct tsig_key** key);
+
+/**
+ * Parse a TSIG from the packet. Current position is just before it.
+ * @param pkt: the packet.
+ * @param rr: data filled in, with pointers to the packet buffer.
+ *     The key name can be compressed.
+ * @return 0 if OK, otherwise an RCODE.
+ */
+int tsig_parse(struct sldns_buffer* pkt, struct tsig_record* rr);
 
 /**
  * Parse and verify the TSIG in query packet.
@@ -311,13 +330,14 @@ int tsig_parse_query(struct tsig_key_table* 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.
+ * @param now: time that is used, the current time.
  * @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);
+       struct regional* region, uint64_t now);
 
 /**
  * Sign a reply with TSIG. Appends the TSIG record.