From: W.C.A. Wijngaards Date: Thu, 26 Jun 2025 10:41:10 +0000 (+0200) Subject: - xfr-tsig, tsig_sign_reply function. X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=81d774fb11168b7ebef0489b3cc2c467d1be5d50;p=thirdparty%2Funbound.git - xfr-tsig, tsig_sign_reply function. --- diff --git a/util/tsig.c b/util/tsig.c index 85d41aab4..99dfa1854 100644 --- a/util/tsig.c +++ b/util/tsig.c @@ -156,6 +156,8 @@ tsig_key_create(const char* name, const char* algorithm, const char* secret) tsig_key_delete(key); return NULL; } + /* Canonicalize the key name. */ + query_dname_tolower(key->name); key->data_len = sldns_b64_pton_calculate_size(strlen(secret)); if(key->data_len == 0) @@ -632,7 +634,7 @@ tsig_create(struct tsig_key_table* key_table, uint8_t* name, size_t namelen) log_err("out of memory"); return NULL; } - tsig->algorithm_name_len = key->algo->wireformat_name_len; + tsig->algo_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) { @@ -671,6 +673,7 @@ tsig_delete(struct tsig_data* tsig) { if(!tsig) return; free(tsig->key_name); + free(tsig->algo_name); free(tsig->mac); free(tsig); } @@ -686,7 +689,7 @@ tsig_reserved_space(struct tsig_data* tsig) + sizeof(uint16_t) /* Class */ + sizeof(uint32_t) /* TTL */ + sizeof(uint16_t) /* RDATA length */ - + tsig->algorithm_name_len /* Algorithm */ + + tsig->algo_name_len /* Algorithm */ + 6 /* 48bit */ /* Signed time */ + sizeof(uint16_t) /* Fudge */ + sizeof(uint16_t) /* Mac size */ @@ -786,7 +789,7 @@ tsig_algo_calc_parts(struct tsig_key* key, struct sldns_buffer* pkt, } if(!EVP_MAC_update(ctx, sldns_buffer_begin(var), - sldns_buffer_limit(var))) { + sldns_buffer_position(var))) { log_crypto_err("Could not EVP_MAC_update"); EVP_MAC_CTX_free(ctx); EVP_MAC_free(mac); @@ -837,7 +840,7 @@ tsig_algo_calc_parts(struct tsig_key* key, struct sldns_buffer* pkt, return 0; } if(!HMAC_Update(ctx, sldns_buffer_begin(var), - sldns_buffer_limit(var))) { + sldns_buffer_position(var))) { log_crypto_err("Could not HMAC_Update"); HMAC_CTX_free(ctx); return 0; @@ -896,7 +899,7 @@ tsig_algo_calc_parts(struct tsig_key* key, struct sldns_buffer* pkt, r = #endif HMAC_Update(&ctx, sldns_buffer_begin(var), - sldns_buffer_limit(var)); + sldns_buffer_position(var)); #ifndef HMAC_INIT_EX_RETURNS_VOID if(!r) { log_crypto_err("Could not HMAC_Update"); @@ -921,52 +924,36 @@ tsig_algo_calc_parts(struct tsig_key* key, struct sldns_buffer* pkt, return 1; } -int -tsig_sign_query(struct tsig_data* tsig, struct sldns_buffer* pkt, - struct tsig_key_table* key_table, uint64_t now) +/** Returns true if buffer has space for vars. */ +static int +tsig_vars_available(struct tsig_data* tsig, struct sldns_buffer* pkt) { - 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. */ - verbose(VERB_ALGO, "tsig_sign_query: not enough buffer space"); - 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_sign_query: key not in table"); + if(!sldns_buffer_available(pkt, tsig->key_name_len + 2 + 4 + + tsig->algo_name_len + 6 + 2 + 2 + + 2 + tsig->other_len)) return 0; - } - - tsig->original_query_id = sldns_buffer_read_u16_at(pkt, 0); + return 1; +} - /* 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. */ - 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); - verbose(VERB_ALGO, "tsig_sign_query: not enough buffer space"); +/** Returns true if buffer has space for vars. */ +static int +tsig_vars_available_parsed(struct tsig_record* rr, struct sldns_buffer* pkt) +{ + if(!sldns_buffer_available(pkt, rr->key_name_len + 2 + 4 + + rr->algorithm_name_len + 6 + 2 + 2 + + 2 + rr->other_size)) return 0; - } + return 1; +} +/** Write tsig variables to buffer, from tsig data. */ +static void +tsig_write_vars(struct tsig_data* tsig, struct sldns_buffer* pkt, + struct tsig_key* key, size_t *aftername_pos) +{ /* 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); + *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, @@ -980,16 +967,32 @@ tsig_sign_query(struct tsig_data* tsig, struct sldns_buffer* pkt, } else { sldns_buffer_write_u16(pkt, 0); } +} - /* Sign it */ - if(!tsig_algo_calc(key, pkt, tsig)) { - /* Failure to calculate digest. */ - lock_rw_unlock(&key_table->lock); - verbose(VERB_ALGO, "tsig_sign_query: failed to calculate digest"); - return 0; - } +/** Write tsig variables to buffer, that have just been parsed. */ +static void +tsig_write_vars_parsed(struct tsig_record* rr, struct sldns_buffer* var, + struct tsig_key* key) +{ + /* 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); +} - /* Append TSIG record */ +/** Append TSIG RR to the packet. */ +static void +tsig_append_rr(struct tsig_data* tsig, struct sldns_buffer* pkt, + size_t aftername_pos, uint8_t* algo_name, size_t algo_name_len, + uint8_t* mac, size_t mac_size) +{ /* 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, @@ -1001,15 +1004,15 @@ tsig_sign_query(struct tsig_data* tsig, struct sldns_buffer* pkt, 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 + sldns_buffer_write_u16(pkt, algo_name_len + + 6 + 2 + 2 /* time,fudge,maclen */ + 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(pkt, algo_name, algo_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, mac_size); + if(mac_size != 0) + sldns_buffer_write(pkt, mac, mac_size); sldns_buffer_write_u16(pkt, tsig->original_query_id); sldns_buffer_write_u16(pkt, tsig->error); if(tsig->other_len == 6) { @@ -1021,7 +1024,61 @@ tsig_sign_query(struct tsig_data* tsig, struct sldns_buffer* pkt, LDNS_ARCOUNT_SET(sldns_buffer_begin(pkt), LDNS_ARCOUNT(sldns_buffer_begin(pkt))+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. */ + verbose(VERB_ALGO, "tsig_sign_query: not enough buffer space"); + 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_sign_query: key not in table"); + 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. */ + if(!tsig_vars_available(tsig, pkt)) { + /* Buffer is too small */ + lock_rw_unlock(&key_table->lock); + verbose(VERB_ALGO, "tsig_sign_query: not enough buffer space"); + return 0; + } + tsig_write_vars(tsig, pkt, key, &aftername_pos); + + /* Calculate the TSIG. */ + if(!tsig_algo_calc(key, pkt, tsig)) { + /* Failure to calculate digest. */ + lock_rw_unlock(&key_table->lock); + verbose(VERB_ALGO, "tsig_sign_query: failed to calculate digest"); + return 0; + } + /* Append TSIG record. */ + tsig_append_rr(tsig, pkt, aftername_pos, key->algo->wireformat_name, + key->algo->wireformat_name_len, tsig->mac, tsig->mac_size); lock_rw_unlock(&key_table->lock); return 1; } @@ -1044,11 +1101,7 @@ tsig_verify_query(struct tsig_data* tsig, struct sldns_buffer* pkt, /* 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) { + if(!tsig_vars_available_parsed(rr, &var)) { verbose(VERB_ALGO, "tsig_verify_query: variable buffer too small"); return LDNS_RCODE_SERVFAIL; } @@ -1057,18 +1110,7 @@ tsig_verify_query(struct tsig_data* tsig, struct sldns_buffer* pkt, , LDNS_ARCOUNT(sldns_buffer_begin(pkt)) - 1); sldns_buffer_set_position(pkt, rr->tsig_pos); - /* 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); + tsig_write_vars_parsed(rr, &var, key); if(!tsig_algo_calc_parts(key, pkt, &var, tsig)) { /* Failure to calculate digest. */ @@ -1286,6 +1328,8 @@ tsig_lookup_key(struct tsig_key_table* key_table, return LDNS_RCODE_SERVFAIL; } dname_pkt_copy(pkt, tsig->key_name, rr->key_name); + /* Canonicalize the key name. */ + query_dname_tolower(tsig->key_name); /* Search for the key. */ lock_rw_rdlock(&key_table->lock); @@ -1300,6 +1344,24 @@ tsig_lookup_key(struct tsig_key_table* key_table, verbose(VERB_ALGO, "tsig_verify_query: key %s not in table", keynm); } + + /* Allocate the algo name for the reply. */ + if(region) + tsig->algo_name = regional_alloc_init(region, + rr->algorithm_name, rr->algorithm_name_len); + else + tsig->algo_name = memdup(rr->algorithm_name, + rr->algorithm_name_len); + if(!tsig->algo_name) { + verbose(VERB_ALGO, "tsig_lookup_key: alloc failure"); + if(!region) { + free(tsig->key_name); + free(tsig); + } + return LDNS_RCODE_SERVFAIL; + } + tsig->algo_name_len = rr->algorithm_name_len; + tsig->error = LDNS_TSIG_ERROR_BADKEY; *tsig_ret = tsig; return LDNS_RCODE_NOTAUTH; @@ -1316,10 +1378,30 @@ tsig_lookup_key(struct tsig_key_table* key_table, 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); } + + /* Allocate the algo name for the reply. */ + if(region) + tsig->algo_name = regional_alloc_init(region, + rr->algorithm_name, rr->algorithm_name_len); + else + tsig->algo_name = memdup(rr->algorithm_name, + rr->algorithm_name_len); + if(!tsig->algo_name) { + verbose(VERB_ALGO, "tsig_lookup_key: alloc failure"); + if(!region) { + free(tsig->key_name); + free(tsig); + } + return LDNS_RCODE_SERVFAIL; + } + tsig->algo_name_len = rr->algorithm_name_len; + tsig->error = LDNS_TSIG_ERROR_BADKEY; *tsig_ret = tsig; return LDNS_RCODE_NOTAUTH; } + tsig->algo_name = NULL; + tsig->algo_name_len = (*key)->algo->wireformat_name_len; /* Check mac size. */ if(rr->mac_size != (*key)->algo->max_digest_size || @@ -1440,3 +1522,107 @@ tsig_find_rr(struct sldns_buffer* pkt) sldns_buffer_set_position(pkt, end_pos); return 1; } + +int +tsig_sign_reply(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; + uint8_t var_buf[2048]; + struct sldns_buffer var; + uint16_t current_query_id; + + if(!tsig) { + /* For some rcodes, like FORMERR, no tsig data is returned, + * and also no TSIG is needed on the reply. */ + verbose(VERB_ALGO, "tsig_sign_reply: no TSIG on error reply"); + return 1; + } + + if(sldns_buffer_remaining(pkt) < tsig_reserved_space(tsig)) { + /* Not enough space in buffer for packet and TSIG. */ + verbose(VERB_ALGO, "tsig_sign_reply: not enough buffer space"); + return 0; + } + + if(LDNS_RCODE_WIRE(sldns_buffer_begin(pkt)) == LDNS_RCODE_SERVFAIL || + LDNS_RCODE_WIRE(sldns_buffer_begin(pkt)) == LDNS_RCODE_FORMERR || + LDNS_RCODE_WIRE(sldns_buffer_begin(pkt)) == LDNS_RCODE_NOTIMPL || + (LDNS_RCODE_WIRE(sldns_buffer_begin(pkt)) == LDNS_RCODE_NOTAUTH && + tsig->error != LDNS_TSIG_ERROR_BADTIME)) { + uint8_t* algo_name; + size_t algo_name_len; + /* No TSIG calculation on error reply. */ + if(tsig->error == 0) + return 1; /* No TSIG needed for the error. Also, + copying in possible formerr contents is not desired. */ + if(tsig->algo_name) { + /* For errors, the tsig->algo_name is allocated. */ + algo_name = tsig->algo_name; + algo_name_len = tsig->algo_name_len; + } else { + /* Robust code in case there is algo name. */ + algo_name = (uint8_t*)"\000"; + algo_name_len = 1; + } + + /* The TSIG can be written straight away */ + sldns_buffer_write(pkt, tsig->key_name, tsig->key_name_len); + aftername_pos = sldns_buffer_position(pkt); + tsig_append_rr(tsig, pkt, aftername_pos, algo_name, + algo_name_len, NULL, 0); + return 1; + } + + 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_sign_reply: key not in table"); + return 0; + } + + tsig->time_signed = now; + /* The variables for the TSIG calculation fit in the current buffer, + * since the reserved space for the TSIG record is larger. */ + if(!tsig_vars_available(tsig, pkt)) { + /* Buffer is too small */ + lock_rw_unlock(&key_table->lock); + verbose(VERB_ALGO, "tsig_sign_reply: not enough buffer space"); + return 0; + } + + current_query_id = sldns_buffer_read_u16_at(pkt, 0); + sldns_buffer_write_u16_at(pkt, 0, tsig->original_query_id); + tsig_write_vars(tsig, pkt, key, &aftername_pos); + + /* The prior digest. It is prepended to the other variables. */ + sldns_buffer_init_frm_data(&var, var_buf, sizeof(var_buf)); + if(!sldns_buffer_available(&var, 2 /* mac_size */ + tsig->mac_size)) { + /* Buffer too small. */ + lock_rw_unlock(&key_table->lock); + verbose(VERB_ALGO, "tsig_sign_reply: not enough buffer space"); + return 0; + } + sldns_buffer_write_u16(&var, tsig->mac_size); + sldns_buffer_write(&var, tsig->mac, tsig->mac_size); + + /* Calculate the TSIG. */ + if(!tsig_algo_calc_parts(key, &var, pkt, tsig)) { + /* Failure to calculate digest. */ + lock_rw_unlock(&key_table->lock); + sldns_buffer_write_u16_at(pkt, 0, current_query_id); + verbose(VERB_ALGO, "tsig_sign_reply: failed to calculate digest"); + return 0; + } + + /* Append TSIG record. */ + sldns_buffer_write_u16_at(pkt, 0, current_query_id); + tsig_append_rr(tsig, pkt, aftername_pos, key->algo->wireformat_name, + key->algo->wireformat_name_len, tsig->mac, tsig->mac_size); + lock_rw_unlock(&key_table->lock); + return 1; +} diff --git a/util/tsig.h b/util/tsig.h index 97bf8414e..b13063ff0 100644 --- a/util/tsig.h +++ b/util/tsig.h @@ -93,8 +93,11 @@ struct tsig_data { uint8_t* key_name; /** length of the key name */ size_t key_name_len; - /** length of the algorithm name */ - size_t algorithm_name_len; + /** The algo name, if the key could not be found. If NULL, it can + * be found in the tsig_key algo. */ + uint8_t* algo_name; + /** length of the algo name */ + size_t algo_name_len; /** mac size */ size_t mac_size; /** digest buffer */ @@ -126,6 +129,7 @@ struct tsig_algorithm { /** * Full wireformat name of the algorith, such as * "hmac-md5.sig-alg.reg.int." + * In canonical format, that is in lowercase. */ uint8_t* wireformat_name; /** length of the wireformat_name */ @@ -346,9 +350,12 @@ int tsig_parse_verify_query(struct tsig_key_table* key_table, * Sign a reply with TSIG. Appends the TSIG record. * @param tsig: the tsig data. * @param pkt: the packet to sign. + * @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_reply(struct tsig_data* tsig, struct sldns_buffer* pkt); +int tsig_sign_reply(struct tsig_data* tsig, struct sldns_buffer* pkt, + struct tsig_key_table* key_table, uint64_t now); /** * Verify a reply with TSIG.