- For #762: relocate EDNS cookie code to util/edns and introduce unit

tests.
This commit is contained in:
George Thessalonikefs 2023-08-04 14:26:08 +02:00
parent 6e47c1e05b
commit 702f485587
4 changed files with 345 additions and 78 deletions

View file

@ -530,6 +530,208 @@ infra_test(void)
config_delete(cfg);
}
#include "util/edns.h"
/* Complete version-invalid client cookie; needs a new one.
* Based on edns_cookie_rfc9018_a2 */
static void
edns_cookie_invalid_version(void)
{
uint32_t timestamp = 1559734385;
uint8_t client_cookie[] = {
0x24, 0x64, 0xc4, 0xab, 0xcf, 0x10, 0xc9, 0x57,
0x99, 0x00, 0x00, 0x00,
0x5c, 0xf7, 0x9f, 0x11,
0x1f, 0x81, 0x30, 0xc3, 0xee, 0xe2, 0x94, 0x80 };
uint8_t server_cookie[] = {
0x24, 0x64, 0xc4, 0xab, 0xcf, 0x10, 0xc9, 0x57,
0x01, 0x00, 0x00, 0x00,
0x5c, 0xf7, 0xa8, 0x71,
0xd4, 0xa5, 0x64, 0xa1, 0x44, 0x2a, 0xca, 0x77 };
uint8_t server_secret[] = {
0xe5, 0xe9, 0x73, 0xe5, 0xa6, 0xb2, 0xa4, 0x3f,
0x48, 0xe7, 0xdc, 0x84, 0x9e, 0x37, 0xbf, 0xcf };
uint8_t buf[32];
/* copy client cookie|version|reserved|timestamp */
memcpy(buf, client_cookie, 8 + 4 + 4);
/* copy ip 198.51.100.100 */
memcpy(buf + 16, "\306\063\144\144", 4);
unit_assert(edns_cookie_server_validate(client_cookie,
sizeof(client_cookie), server_secret, sizeof(server_secret), 1,
buf, timestamp) == 0);
edns_cookie_server_write(buf, server_secret, 1, timestamp);
log_hex("server:", buf, 32);
unit_assert(memcmp(server_cookie, buf, 24) == 0);
}
/* Complete hash-invalid client cookie; needs a new one. */
static void
edns_cookie_invalid_hash(void)
{
uint32_t timestamp = 0;
uint8_t client_cookie[] = {
0xfc, 0x93, 0xfc, 0x62, 0x80, 0x7d, 0xdb, 0x86,
0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x32, 0xF2, 0x43, 0xB9, 0xBC, 0xFE, 0xC4, 0x06 };
uint8_t server_cookie[] = {
0xfc, 0x93, 0xfc, 0x62, 0x80, 0x7d, 0xdb, 0x86,
0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0xBA, 0x0D, 0x82, 0x90, 0x8F, 0xAA, 0xEB, 0xBD };
uint8_t server_secret[] = {
0xe5, 0xe9, 0x73, 0xe5, 0xa6, 0xb2, 0xa4, 0x3f,
0x48, 0xe7, 0xdc, 0x84, 0x9e, 0x37, 0xbf, 0xcf };
uint8_t buf[32];
/* copy client cookie|version|reserved|timestamp */
memcpy(buf, client_cookie, 8 + 4 + 4);
/* copy ip 203.0.113.203 */
memcpy(buf + 16, "\313\000\161\313", 4);
unit_assert(edns_cookie_server_validate(client_cookie,
sizeof(client_cookie), server_secret, sizeof(server_secret), 1,
buf, timestamp) == 0);
edns_cookie_server_write(buf, server_secret, 1, timestamp);
unit_assert(memcmp(server_cookie, buf, 24) == 0);
}
/* Complete hash-valid client cookie; more than 30 minutes old; needs a
* refreshed server cookie.
* A slightly better variation of edns_cookie_rfc9018_a3 for Unbound to check
* that RESERVED bits do not influence cookie validation. */
static void
edns_cookie_rfc9018_a3_better(void)
{
uint32_t timestamp = 1800 + 1;
uint8_t client_cookie[] = {
0xfc, 0x93, 0xfc, 0x62, 0x80, 0x7d, 0xdb, 0x86,
0x01, 0xab, 0xcd, 0xef,
0x00, 0x00, 0x00, 0x00,
0x32, 0xF2, 0x43, 0xB9, 0xBC, 0xFE, 0xC4, 0x06 };
uint8_t server_cookie[] = {
0xfc, 0x93, 0xfc, 0x62, 0x80, 0x7d, 0xdb, 0x86,
0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x07, 0x09,
0x62, 0xD5, 0x93, 0x09, 0x14, 0x5C, 0x23, 0x9D };
uint8_t server_secret[] = {
0xe5, 0xe9, 0x73, 0xe5, 0xa6, 0xb2, 0xa4, 0x3f,
0x48, 0xe7, 0xdc, 0x84, 0x9e, 0x37, 0xbf, 0xcf };
uint8_t buf[32];
/* copy client cookie|version|reserved|timestamp */
memcpy(buf, client_cookie, 8 + 4 + 4);
/* copy ip 203.0.113.203 */
memcpy(buf + 16, "\313\000\161\313", 4);
unit_assert(edns_cookie_server_validate(client_cookie,
sizeof(client_cookie), server_secret, sizeof(server_secret), 1,
buf, timestamp) == -1);
edns_cookie_server_write(buf, server_secret, 1, timestamp);
unit_assert(memcmp(server_cookie, buf, 24) == 0);
}
/* Complete hash-valid client cookie; more than 60 minutes old; needs a
* refreshed server cookie. */
static void
edns_cookie_rfc9018_a3(void)
{
uint32_t timestamp = 1559734700;
uint8_t client_cookie[] = {
0xfc, 0x93, 0xfc, 0x62, 0x80, 0x7d, 0xdb, 0x86,
0x01, 0xab, 0xcd, 0xef,
0x5c, 0xf7, 0x8f, 0x71,
0xa3, 0x14, 0x22, 0x7b, 0x66, 0x79, 0xeb, 0xf5 };
uint8_t server_cookie[] = {
0xfc, 0x93, 0xfc, 0x62, 0x80, 0x7d, 0xdb, 0x86,
0x01, 0x00, 0x00, 0x00,
0x5c, 0xf7, 0xa9, 0xac,
0xf7, 0x3a, 0x78, 0x10, 0xac, 0xa2, 0x38, 0x1e };
uint8_t server_secret[] = {
0xe5, 0xe9, 0x73, 0xe5, 0xa6, 0xb2, 0xa4, 0x3f,
0x48, 0xe7, 0xdc, 0x84, 0x9e, 0x37, 0xbf, 0xcf };
uint8_t buf[32];
/* copy client cookie|version|reserved|timestamp */
memcpy(buf, client_cookie, 8 + 4 + 4);
/* copy ip 203.0.113.203 */
memcpy(buf + 16, "\313\000\161\313", 4);
unit_assert(edns_cookie_server_validate(client_cookie,
sizeof(client_cookie), server_secret, sizeof(server_secret), 1,
buf, timestamp) == 0);
edns_cookie_server_write(buf, server_secret, 1, timestamp);
unit_assert(memcmp(server_cookie, buf, 24) == 0);
}
/* Complete hash-valid client cookie; more than 30 minutes old; needs a
* refreshed server cookie. */
static void
edns_cookie_rfc9018_a2(void)
{
uint32_t timestamp = 1559734385;
uint8_t client_cookie[] = {
0x24, 0x64, 0xc4, 0xab, 0xcf, 0x10, 0xc9, 0x57,
0x01, 0x00, 0x00, 0x00,
0x5c, 0xf7, 0x9f, 0x11,
0x1f, 0x81, 0x30, 0xc3, 0xee, 0xe2, 0x94, 0x80 };
uint8_t server_cookie[] = {
0x24, 0x64, 0xc4, 0xab, 0xcf, 0x10, 0xc9, 0x57,
0x01, 0x00, 0x00, 0x00,
0x5c, 0xf7, 0xa8, 0x71,
0xd4, 0xa5, 0x64, 0xa1, 0x44, 0x2a, 0xca, 0x77 };
uint8_t server_secret[] = {
0xe5, 0xe9, 0x73, 0xe5, 0xa6, 0xb2, 0xa4, 0x3f,
0x48, 0xe7, 0xdc, 0x84, 0x9e, 0x37, 0xbf, 0xcf };
uint8_t buf[32];
/* copy client cookie|version|reserved|timestamp */
memcpy(buf, client_cookie, 8 + 4 + 4);
/* copy ip 198.51.100.100 */
memcpy(buf + 16, "\306\063\144\144", 4);
unit_assert(edns_cookie_server_validate(client_cookie,
sizeof(client_cookie), server_secret, sizeof(server_secret), 1,
buf, timestamp) == -1);
edns_cookie_server_write(buf, server_secret, 1, timestamp);
unit_assert(memcmp(server_cookie, buf, 24) == 0);
}
/* Only client cookie; needs a complete server cookie. */
static void
edns_cookie_rfc9018_a1(void)
{
uint32_t timestamp = 1559731985;
uint8_t client_cookie[] = {
0x24, 0x64, 0xc4, 0xab, 0xcf, 0x10, 0xc9, 0x57 };
uint8_t server_cookie[] = {
0x24, 0x64, 0xc4, 0xab, 0xcf, 0x10, 0xc9, 0x57,
0x01, 0x00, 0x00, 0x00,
0x5c, 0xf7, 0x9f, 0x11,
0x1f, 0x81, 0x30, 0xc3, 0xee, 0xe2, 0x94, 0x80 };
uint8_t server_secret[] = {
0xe5, 0xe9, 0x73, 0xe5, 0xa6, 0xb2, 0xa4, 0x3f,
0x48, 0xe7, 0xdc, 0x84, 0x9e, 0x37, 0xbf, 0xcf };
uint8_t buf[32];
/* copy client cookie|version|reserved|timestamp */
memcpy(buf, server_cookie, 8 + 4 + 4);
/* copy ip 198.51.100.100 */
memcpy(buf + 16, "\306\063\144\144", 4);
unit_assert(edns_cookie_server_validate(client_cookie,
sizeof(client_cookie),
/* these will not be used; it will return invalid
* because of the size. */
NULL, 0, 1, NULL, 0) == 0);
edns_cookie_server_write(buf, server_secret, 1, timestamp);
unit_assert(memcmp(server_cookie, buf, 24) == 0);
}
/** test interoperable EDNS cookies (RFC9018) */
static void
edns_cookie_test(void)
{
unit_show_feature("interoperable edns cookies");
/* Check RFC9018 appendix test vectors */
edns_cookie_rfc9018_a1();
edns_cookie_rfc9018_a2();
edns_cookie_rfc9018_a3();
/* More tests */
edns_cookie_rfc9018_a3_better();
edns_cookie_invalid_hash();
edns_cookie_invalid_version();
}
#include "util/random.h"
/** test randomness */
static void
@ -906,6 +1108,7 @@ main(int argc, char* argv[])
slabhash_test();
infra_test();
ldns_test();
edns_cookie_test();
zonemd_test();
tcpreuse_test();
msgparse_test();

View file

@ -43,10 +43,10 @@
#include "util/data/dname.h"
#include "util/data/packed_rrset.h"
#include "util/netevent.h"
#include "util/siphash.h"
#include "util/storage/lookup3.h"
#include "util/regional.h"
#include "util/rfc_1982.h"
#include "util/edns.h"
#include "sldns/rrdef.h"
#include "sldns/sbuffer.h"
#include "sldns/parseutil.h"
@ -942,21 +942,6 @@ parse_packet(sldns_buffer* pkt, struct msg_parse* msg, struct regional* region)
return 0;
}
static uint8_t *
cookie_hash(uint8_t *hash, uint8_t *buf,
struct sockaddr_storage *addr, uint8_t *secret)
{
if (addr->ss_family == AF_INET6) {
memcpy(buf+16, &((struct sockaddr_in6 *)addr)->sin6_addr, 16);
siphash(buf, 32, secret, hash, 8);
} else {
memcpy(buf+16, &((struct sockaddr_in *)addr)->sin_addr, 4);
siphash(buf, 20, secret, hash, 8);
}
return hash;
}
/** parse EDNS options from EDNS wireformat rdata */
static int
parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len,
@ -985,9 +970,9 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len,
while(rdata_len >= 4) {
uint16_t opt_code = sldns_read_uint16(rdata_ptr);
uint16_t opt_len = sldns_read_uint16(rdata_ptr+2);
uint8_t server_cookie[40], hash[8];
uint32_t cookie_time, subt_1982;
int comp_1982;
uint8_t server_cookie[40];
int cookie_is_valid;
int cookie_is_v4 = 1;
rdata_ptr += 4;
rdata_len -= 4;
@ -1052,11 +1037,11 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len,
break;
case LDNS_EDNS_COOKIE:
if(!cfg || !cfg->do_answer_cookie)
if(!cfg || !cfg->do_answer_cookie || !repinfo)
break;
if(opt_len != 8 && (opt_len < 16 || opt_len > 40)) {
verbose(VERB_ALGO, "worker request: "
"badly formatted cookie");
"badly formatted cookie");
return LDNS_RCODE_FORMERR;
}
edns->cookie_present = 1;
@ -1066,67 +1051,41 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len,
*/
memcpy(server_cookie, rdata_ptr, 16);
/* In the "if, if else" block below, we validate a
* RFC9018 cookie. If it doesn't match the recipe, or
* if it doesn't validate, or if the cookie is too old
* (< 30 min), a new cookie is generated.
/* Copy client ip for validation and creation
* purposes. It will be overwritten if (re)creation
* is needed.
*/
if (opt_len != 24)
; /* RFC9018 cookies are 24 bytes long */
if(repinfo->remote_addr.ss_family == AF_INET) {
memcpy(server_cookie + 16,
&((struct sockaddr_in*)&repinfo->remote_addr)->sin_addr, 4);
} else {
cookie_is_v4 = 0;
memcpy(server_cookie + 16,
&((struct sockaddr_in6*)&repinfo->remote_addr)->sin6_addr, 16);
}
else if (cfg->cookie_secret_len != 16)
; /* RFC9018 cookies have 16 byte secrets */
else if (rdata_ptr[8] != 1)
; /* RFC9018 cookies are cookie version 1 */
else if ((comp_1982 = compare_1982(now,
(cookie_time = sldns_read_uint32(rdata_ptr + 12)))) > 0
&& (subt_1982 = subtract_1982(cookie_time, now)) > 3600)
; /* Cookie is older than 1 hour
* (see RFC9018 Section 4.3.)
*/
else if (comp_1982 <= 0
&& subtract_1982(now, cookie_time) > 300)
; /* Cookie time is more than 5 minutes in the
* future. (see RFC9018 Section 4.3.)
*/
else if (memcmp( cookie_hash( hash, server_cookie
, &repinfo->remote_addr
, cfg->cookie_secret)
, rdata_ptr + 16 , 8 ) == 0) {
/* Cookie is valid! */
edns->cookie_valid = 1;
if (comp_1982 > 0 && subt_1982 > 1800)
; /* But older than 30 minutes,
* so create a new one anyway */
else if (!edns_opt_list_append( /* Reuse cookie */
&edns->opt_list_out, LDNS_EDNS_COOKIE, opt_len,
rdata_ptr, region)) {
cookie_is_valid = edns_cookie_server_validate(
rdata_ptr, opt_len, cfg->cookie_secret,
cfg->cookie_secret_len, cookie_is_v4,
server_cookie, now);
if(cookie_is_valid != 0) edns->cookie_valid = 1;
if(cookie_is_valid == 1) {
/* Reuse cookie */
if(!edns_opt_list_append(
&edns->opt_list_out, LDNS_EDNS_COOKIE,
opt_len, rdata_ptr, region)) {
log_err("out of memory");
return LDNS_RCODE_SERVFAIL;
} else
/* Cookie to be reused added to
* outgoing options. Done!
*/
break;
}
/* Cookie to be reused added to outgoing
* options. Done!
*/
break;
}
/* Add a new server cookie to outgoing cookies */
server_cookie[ 8] = 1; /* Version */
server_cookie[ 9] = 0; /* Reserved */
server_cookie[10] = 0; /* Reserved */
server_cookie[11] = 0; /* Reserved */
sldns_write_uint32(server_cookie + 12, now);
cookie_hash( hash, server_cookie, &repinfo->remote_addr
, cfg->cookie_secret);
memcpy(server_cookie + 16, hash, 8);
if (!edns_opt_list_append( &edns->opt_list_out
, LDNS_EDNS_COOKIE
, 24, server_cookie, region)) {
edns_cookie_server_write(server_cookie,
cfg->cookie_secret, cookie_is_v4, now);
if(!edns_opt_list_append(&edns->opt_list_out,
LDNS_EDNS_COOKIE, 24, server_cookie, region)) {
log_err("out of memory");
return LDNS_RCODE_SERVFAIL;
}
@ -1309,7 +1268,7 @@ parse_edns_from_query_pkt(sldns_buffer* pkt, struct edns_data* edns,
rdata_ptr = sldns_buffer_current(pkt);
/* ignore rrsigs */
return parse_edns_options_from_query(rdata_ptr, rdata_len, edns, cfg,
c, repinfo, now, region);
c, repinfo, now, region);
}
void

View file

@ -45,8 +45,11 @@
#include "util/netevent.h"
#include "util/net_help.h"
#include "util/regional.h"
#include "util/rfc_1982.h"
#include "util/siphash.h"
#include "util/data/msgparse.h"
#include "util/data/msgreply.h"
#include "sldns/sbuffer.h"
struct edns_strings* edns_strings_create(void)
{
@ -128,3 +131,56 @@ edns_string_addr_lookup(rbtree_type* tree, struct sockaddr_storage* addr,
return (struct edns_string_addr*)addr_tree_lookup(tree, addr, addrlen);
}
uint8_t*
edns_cookie_server_hash(const uint8_t* in, const uint8_t* secret, int v4,
uint8_t* hash)
{
v4?siphash(in, 20, secret, hash, 8):siphash(in, 32, secret, hash, 8);
return hash;
}
void
edns_cookie_server_write(uint8_t* buf, const uint8_t* secret, int v4,
uint32_t timestamp)
{
uint8_t hash[8];
buf[ 8] = 1; /* Version */
buf[ 9] = 0; /* Reserved */
buf[10] = 0; /* Reserved */
buf[11] = 0; /* Reserved */
sldns_write_uint32(buf + 12, timestamp);
(void)edns_cookie_server_hash(buf, secret, v4, hash);
memcpy(buf + 16, hash, 8);
}
int
edns_cookie_server_validate(const uint8_t* cookie, size_t cookie_len,
const uint8_t* secret, size_t secret_len, int v4,
const uint8_t* hash_input, uint32_t now)
{
uint8_t hash[8];
uint32_t timestamp, subt_1982;
int comp_1982;
if(cookie_len != 24 || /* RFC9018 cookies are 24 bytes long */
secret_len != 16 || /* RFC9018 cookies have 16 byte secrets */
cookie[8] != 1) /* RFC9018 cookies are cookie version 1 */
return 0;
timestamp = sldns_read_uint32(cookie + 12);
if((comp_1982 = compare_1982(now, timestamp)) > 0
&& (subt_1982 = subtract_1982(timestamp, now)) > 3600)
/* Cookie is older than 1 hour (see RFC9018 Section 4.3.) */
return 0;
if(comp_1982 <= 0 && subtract_1982(now, timestamp) > 300)
/* Cookie time is more than 5 minutes in the future.
* (see RFC9018 Section 4.3.) */
return 0;
if(memcmp(edns_cookie_server_hash(hash_input, secret, v4, hash),
cookie + 16, 8) != 0)
/* Hashes do not match */
return 0;
if(comp_1982 > 0 && subt_1982 > 1800)
/* Valid cookie but older than 30 minutes, so create a new one
* anyway */
return -1;
return 1;
}

View file

@ -106,4 +106,53 @@ struct edns_string_addr*
edns_string_addr_lookup(rbtree_type* tree, struct sockaddr_storage* addr,
socklen_t addrlen);
/**
* Compute the interoperable EDNS cookie (RFC9018) hash.
* @param in: buffer input for the hash generation. It needs to be:
* Client Cookie | Version | Reserved | Timestamp | Client-IP
* @param secret: the server secret; implicit length of 16 octets.
* @param v4: if the client IP is v4 or v6.
* @param hash: buffer to write the hash to.
* return a pointer to the hash.
*/
uint8_t* edns_cookie_server_hash(const uint8_t* in, const uint8_t* secret,
int v4, uint8_t* hash);
/**
* Write an interoperable EDNS server cookie (RFC9018).
* @param buf: buffer to write to. It should have a size of at least 32 octets
* as it doubles as the output buffer and the hash input buffer.
* The first 8 octets are expected to be the Client Cookie and will be
* left untouched.
* The next 8 octets will be written with Version | Reserved | Timestamp.
* The next 4 or 16 octets are expected to be the IPv4 or the IPv6 address
* based on the v4 flag.
* Thus the first 20 or 32 octets, based on the v4 flag, will be used as
* the hash input.
* The server hash (8 octets) will be written after the first 16 octets;
* overwriting the address information.
* The caller expects a complete, 24 octet long cookie in the buffer.
* @param secret: the server secret; implicit length of 16 octets.
* @param v4: if the client IP is v4 or v6.
* @param timestamp: the timestamp to use.
*/
void edns_cookie_server_write(uint8_t* buf, const uint8_t* secret, int v4,
uint32_t timestamp);
/**
* Validate an interoperable EDNS cookie (RFC9018).
* @param cookie: pointer to the cookie data.
* @param cookie_len: the length of the cookie data.
* @param secret: pointer to the server secret.
* @param secret_len: the length of the secret.
* @param v4: if the client IP is v4 or v6.
* @param hash_input: pointer to the hash input for validation. It needs to be:
* Client Cookie | Version | Reserved | Timestamp | Client-IP
* @param now: the current time.
* return 1 if valid, -1 if valid but a new one SHOULD be generated, else 0.
*/
int edns_cookie_server_validate(const uint8_t* cookie, size_t cookie_len,
const uint8_t* secret, size_t secret_len, int v4,
const uint8_t* hash_input, uint32_t now);
#endif