From 0955238cd3d0c1d993a03945a6f468c5260ca79a Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 27 Jun 2025 14:26:15 +0200 Subject: [PATCH] - xfr-tsig, tsig_verify_reply function. --- util/tsig.c | 163 ++++++++++++++++++++++++++++++++++++++++++++++++++-- util/tsig.h | 18 +++++- 2 files changed, 176 insertions(+), 5 deletions(-) diff --git a/util/tsig.c b/util/tsig.c index 6d7cb039d..dc6f91dce 100644 --- a/util/tsig.c +++ b/util/tsig.c @@ -553,14 +553,16 @@ tsig_algo_calc(struct tsig_key* key, struct sldns_buffer* pkt, * Called must hold locks, on key. The routine concatenates the argument * datas with digest updates. * @param key: the key, and algorithm. + * @param prior: before the next buffer. or NULL. * @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) +tsig_algo_calc_parts(struct tsig_key* key, struct sldns_buffer* prior, + 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. */ @@ -582,6 +584,15 @@ tsig_algo_calc_parts(struct tsig_key* key, struct sldns_buffer* pkt, EVP_MAC_free(mac); return 0; } + + if(prior && !EVP_MAC_update(ctx, sldns_buffer_begin(prior), + sldns_buffer_position(prior))) { + 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(pkt), sldns_buffer_position(pkt))) { log_crypto_err("Could not EVP_MAC_update"); @@ -635,6 +646,12 @@ tsig_algo_calc_parts(struct tsig_key* key, struct sldns_buffer* pkt, HMAC_CTX_free(ctx); return 0; } + if(prior && !HMAC_Update(ctx, sldns_buffer_begin(prior), + sldns_buffer_position(prior))) { + log_crypto_err("Could not HMAC_Update"); + 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"); @@ -685,6 +702,20 @@ tsig_algo_calc_parts(struct tsig_key* key, struct sldns_buffer* pkt, return 0; } #endif + if(prior) { +#ifndef HMAC_INIT_EX_RETURNS_VOID + r = +#endif + HMAC_Update(&ctx, sldns_buffer_begin(prior), + sldns_buffer_position(prior)); +#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 @@ -914,7 +945,7 @@ tsig_verify_query(struct tsig_data* tsig, struct sldns_buffer* pkt, tsig_write_vars_parsed(rr, &var, key); - if(!tsig_algo_calc_parts(key, pkt, &var, tsig)) { + if(!tsig_algo_calc_parts(key, NULL, 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"); @@ -1414,7 +1445,7 @@ tsig_sign_reply(struct tsig_data* tsig, struct sldns_buffer* pkt, sldns_buffer_write(&var, tsig->mac, tsig->mac_size); /* Calculate the TSIG. */ - if(!tsig_algo_calc_parts(key, &var, pkt, tsig)) { + if(!tsig_algo_calc_parts(key, NULL, &var, pkt, tsig)) { /* Failure to calculate digest. */ lock_rw_unlock(&key_table->lock); sldns_buffer_write_u16_at(pkt, 0, current_query_id); @@ -1430,6 +1461,130 @@ tsig_sign_reply(struct tsig_data* tsig, struct sldns_buffer* pkt, return 1; } +int +tsig_verify_reply(struct tsig_data* tsig, struct sldns_buffer* pkt, + struct tsig_key* key, struct tsig_record* rr, uint64_t now) +{ + uint8_t var_buf[2048], prior_buf[1024]; + struct sldns_buffer var, prior; + uint16_t current_query_id; + + sldns_buffer_init_frm_data(&var, var_buf, sizeof(var_buf)); + sldns_buffer_init_frm_data(&prior, prior_buf, sizeof(prior_buf)); + + /* Create the variables to digest in the buffer. */ + /* packet with original query ID and ARCOUNT without TSIG. */ + /* It is prepended with the prior mac. */ + if(!tsig_vars_available_parsed(rr, &var)) { + verbose(VERB_ALGO, "tsig_verify_reply: variable buffer too small"); + return 0; + } + if(sldns_buffer_remaining(&prior) < + 2 /* mac_size */ + tsig->mac_size) { + verbose(VERB_ALGO, "tsig_verify_reply: prior buffer too small"); + return 0; + } + + sldns_buffer_write_u16(&prior, tsig->mac_size); + sldns_buffer_write(&prior, tsig->mac, tsig->mac_size); + + current_query_id = sldns_buffer_read_u16_at(pkt, 0); + sldns_buffer_write_u16_at(pkt, 0, rr->original_query_id); + LDNS_ARCOUNT_SET( sldns_buffer_begin(pkt) + , LDNS_ARCOUNT(sldns_buffer_begin(pkt)) - 1); + sldns_buffer_set_position(pkt, rr->tsig_pos); + + tsig_write_vars_parsed(rr, &var, key); + + if(!tsig_algo_calc_parts(key, &prior, pkt, &var, tsig)) { + /* Failure to calculate digest. */ + sldns_buffer_write_u16_at(pkt, 0, current_query_id); + verbose(VERB_ALGO, "tsig_verify_reply: failed to calculate digest"); + return 0; + } + 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_reply: TSIG %s has wrong digest", + keynm); + } + return 0; + } + /* 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 */ + 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_reply: 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_reply: other time is reported at %llu", + (unsigned long long)rr->other_time); + } + return 0; + } + + return 1; +} + +int +tsig_parse_verify_reply(struct tsig_data* tsig, struct sldns_buffer* pkt, + struct tsig_key_table* key_table, uint64_t now) +{ + struct tsig_key* key; + struct tsig_record rr; + int ret; + + /* Parse the TSIG RR from the query. */ + ret = tsig_parse(pkt, &rr); + if(ret != 0) + return 0; + + /* Check it is the same key, same algo, same digest_size */ + if(dname_pkt_compare(pkt, tsig->key_name, rr.key_name) != 0) { + verbose(VERB_ALGO, "tsig_verify_reply: key name wrong"); + 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); + verbose(VERB_ALGO, "tsig_verify_reply: key not in table"); + return 0; + } + + if(query_dname_compare(key->algo->wireformat_name, + rr.algorithm_name) != 0) { + lock_rw_unlock(&key_table->lock); + verbose(VERB_ALGO, "tsig_verify_reply: algorithm wrong"); + return 0; + } + if(tsig->mac_size != rr.mac_size) { + lock_rw_unlock(&key_table->lock); + verbose(VERB_ALGO, "tsig_verify_reply: mac size wrong"); + return 0; + } + + /* Verify the TSIG. */ + ret = tsig_verify_reply(tsig, pkt, key, &rr, now); + lock_rw_unlock(&key_table->lock); + return ret; +} + /** * Verify pkt with the name (domain name), algorithm and key in Base64. * out 0 on success, an error code otherwise. diff --git a/util/tsig.h b/util/tsig.h index b9c8a7ee7..06a63f5bb 100644 --- a/util/tsig.h +++ b/util/tsig.h @@ -381,10 +381,26 @@ 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. + * @param key: the key with algorithm, caller must hold lock. + * @param rr: the tsig record parsed from the reply. + * @param now: time to sign the query, the current time. + * @return false on failure, like + * alloc failure, wireformat malformed, did not verify. + */ +int tsig_verify_reply(struct tsig_data* tsig, struct sldns_buffer* pkt, + struct tsig_key* key, struct tsig_record* rr, uint64_t now); + +/** + * Verify a reply with TSIG. + * @param tsig: the tsig data. + * @param pkt: the reply to verify. + * @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, like * alloc failure, wireformat malformed, did not verify. */ -int tsig_verify_reply(struct tsig_data* tsig, struct sldns_buffer* pkt); +int tsig_parse_verify_reply(struct tsig_data* tsig, struct sldns_buffer* pkt, + struct tsig_key_table* key_table, uint64_t now); /** * Calculate reserved space for TSIG. -- 2.47.2