]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
- For #762: relocate EDNS cookie code to util/edns and introduce unit
authorGeorge Thessalonikefs <george@nlnetlabs.nl>
Fri, 4 Aug 2023 12:26:08 +0000 (14:26 +0200)
committerGeorge Thessalonikefs <george@nlnetlabs.nl>
Fri, 4 Aug 2023 12:26:08 +0000 (14:26 +0200)
  tests.

testcode/unitmain.c
util/data/msgparse.c
util/edns.c
util/edns.h

index b6dac5507faff184a5e7628c547886923023ae37..725393217c85a35e846a87204cc9b698b36f951a 100644 (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();
index 33002ddcabd3811973744bf275c979f753e25aa4..6aa853c151f56765823f95947279b0d55ecd0fb7 100644 (file)
 #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 */
-
-                       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 */
+                       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 (!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
index f55dcb97e755ad56a4a28578dd9d9cdef2e0f0a9..87eebef2adc8b62a12c74ccaf3ce974f8892d792 100644 (file)
 #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;
+}
index d9ded0b84dc4534c3af51c1f85a887dd82402416..021b500df6c9b6178c5973deb5768e73acaae5f1 100644 (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