* - cookie-secret-file, define struct.
* - cookie-secret-file, add config option, create, read and delete struct.
* - cookie-secret-file, check cookie secrets for cookie validation.
* - cookie-secret-file, unbound-control add_cookie_secret, drop_cookie_secret,
activate_cookie_secret and print_cookie_secrets.
* - cookie-secret-file, test and fix locks, renew writes a fresh cookie,
staging cookies get a fresh cookie and spelling in error message.
* - cookie-secret-file, remove unused variable from cookie file unit test.
* Remove unshare and faketime dependencies for cookie_file test; documentation nits.
---------
Co-authored-by: Yorgos Thessalonikefs <yorgos@nlnetlabs.nl>
$(srcdir)/validator/val_anchor.h $(srcdir)/iterator/iterator.h $(srcdir)/services/outbound_list.h \
$(srcdir)/iterator/iter_fwd.h $(srcdir)/iterator/iter_hints.h $(srcdir)/iterator/iter_delegpt.h \
$(srcdir)/services/outside_network.h $(srcdir)/sldns/str2wire.h $(srcdir)/sldns/parseutil.h \
- $(srcdir)/sldns/wire2str.h
+ $(srcdir)/sldns/wire2str.h $(srcdir)/util/edns.h
stats.lo stats.o: $(srcdir)/daemon/stats.c config.h $(srcdir)/daemon/stats.h $(srcdir)/util/timehist.h \
$(srcdir)/libunbound/unbound.h $(srcdir)/daemon/worker.h $(srcdir)/libunbound/worker.h $(srcdir)/sldns/sbuffer.h \
$(srcdir)/util/data/packed_rrset.h $(srcdir)/util/storage/lruhash.h $(srcdir)/util/locks.h $(srcdir)/util/log.h \
"dnscrypt support");
#endif
}
+ if(daemon->cfg->cookie_secret_file &&
+ daemon->cfg->cookie_secret_file[0]) {
+ if(!(daemon->cookie_secrets = cookie_secrets_create()))
+ fatal_exit("Could not create cookie_secrets: out of memory");
+ if(!cookie_secrets_apply_cfg(daemon->cookie_secrets,
+ daemon->cfg->cookie_secret_file))
+ fatal_exit("Could not setup cookie_secrets");
+ }
/* create global local_zones */
if(!(daemon->local_zones = local_zones_create()))
fatal_exit("Could not create local zones: out of memory");
acl_list_delete(daemon->acl);
acl_list_delete(daemon->acl_interface);
tcl_list_delete(daemon->tcl);
+ cookie_secrets_delete(daemon->cookie_secrets);
listen_desetup_locks();
free(daemon->chroot);
free(daemon->pidfile);
struct daemon_remote;
struct respip_set;
struct shm_main_info;
+struct cookie_secrets;
#include "dnstap/dnstap_config.h"
#ifdef USE_DNSTAP
#endif
/** reuse existing cache on reload if other conditions allow it. */
int reuse_cache;
+ /** the EDNS cookie secrets from the cookie-secret-file */
+ struct cookie_secrets* cookie_secrets;
};
/**
#include "sldns/wire2str.h"
#include "sldns/sbuffer.h"
#include "util/timeval_func.h"
+#include "util/edns.h"
#ifdef USE_CACHEDB
#include "cachedb/cachedb.h"
#endif
do_rpz_enable_disable(ssl, worker, arg, 0);
}
+/** Write the cookie secrets to file, returns `0` on failure.
+ * Caller has to hold the lock. */
+static int
+cookie_secret_file_dump(RES* ssl, struct worker* worker) {
+ char const* secret_file = worker->env.cfg->cookie_secret_file;
+ struct cookie_secrets* cookie_secrets = worker->daemon->cookie_secrets;
+ char secret_hex[UNBOUND_COOKIE_SECRET_SIZE * 2 + 1];
+ FILE* f;
+ size_t i;
+ if(secret_file == NULL || secret_file[0]==0) {
+ (void)ssl_printf(ssl, "error: no cookie secret file configured\n");
+ return 0;
+ }
+ log_assert( secret_file != NULL );
+
+ /* open write only and truncate */
+ if((f = fopen(secret_file, "w")) == NULL ) {
+ (void)ssl_printf(ssl, "unable to open cookie secret file %s: %s",
+ secret_file, strerror(errno));
+ return 0;
+ }
+ if(cookie_secrets == NULL) {
+ /* nothing to write */
+ fclose(f);
+ return 1;
+ }
+
+ for(i = 0; i < cookie_secrets->cookie_count; i++) {
+ struct cookie_secret const* cs = &cookie_secrets->
+ cookie_secrets[i];
+ ssize_t const len = hex_ntop(cs->cookie_secret,
+ UNBOUND_COOKIE_SECRET_SIZE, secret_hex,
+ sizeof(secret_hex));
+ (void)len; /* silence unused variable warning with -DNDEBUG */
+ log_assert( len == UNBOUND_COOKIE_SECRET_SIZE * 2 );
+ secret_hex[UNBOUND_COOKIE_SECRET_SIZE * 2] = '\0';
+ fprintf(f, "%s\n", secret_hex);
+ }
+ explicit_bzero(secret_hex, sizeof(secret_hex));
+ fclose(f);
+ return 1;
+}
+
+/** Activate cookie secret */
+static void
+do_activate_cookie_secret(RES* ssl, struct worker* worker) {
+ char const* secret_file = worker->env.cfg->cookie_secret_file;
+ struct cookie_secrets* cookie_secrets = worker->daemon->cookie_secrets;
+
+ if(secret_file == NULL || secret_file[0] == 0) {
+ (void)ssl_printf(ssl, "error: no cookie secret file configured\n");
+ return;
+ }
+ if(cookie_secrets == NULL) {
+ (void)ssl_printf(ssl, "error: there are no cookie_secrets.");
+ return;
+ }
+ lock_basic_lock(&cookie_secrets->lock);
+
+ if(cookie_secrets->cookie_count <= 1 ) {
+ lock_basic_unlock(&cookie_secrets->lock);
+ (void)ssl_printf(ssl, "error: no staging cookie secret to activate\n");
+ return;
+ }
+ /* Only the worker 0 writes to file, the others update state. */
+ if(worker->thread_num == 0 && !cookie_secret_file_dump(ssl, worker)) {
+ lock_basic_unlock(&cookie_secrets->lock);
+ (void)ssl_printf(ssl, "error: writing to cookie secret file: \"%s\"\n",
+ secret_file);
+ return;
+ }
+ activate_cookie_secret(cookie_secrets);
+ if(worker->thread_num == 0)
+ (void)cookie_secret_file_dump(ssl, worker);
+ lock_basic_unlock(&cookie_secrets->lock);
+ send_ok(ssl);
+}
+
+/** Drop cookie secret */
+static void
+do_drop_cookie_secret(RES* ssl, struct worker* worker) {
+ char const* secret_file = worker->env.cfg->cookie_secret_file;
+ struct cookie_secrets* cookie_secrets = worker->daemon->cookie_secrets;
+
+ if(secret_file == NULL || secret_file[0] == 0) {
+ (void)ssl_printf(ssl, "error: no cookie secret file configured\n");
+ return;
+ }
+ if(cookie_secrets == NULL) {
+ (void)ssl_printf(ssl, "error: there are no cookie_secrets.");
+ return;
+ }
+ lock_basic_lock(&cookie_secrets->lock);
+
+ if(cookie_secrets->cookie_count <= 1 ) {
+ lock_basic_unlock(&cookie_secrets->lock);
+ (void)ssl_printf(ssl, "error: can not drop the currently active cookie secret\n");
+ return;
+ }
+ /* Only the worker 0 writes to file, the others update state. */
+ if(worker->thread_num == 0 && !cookie_secret_file_dump(ssl, worker)) {
+ lock_basic_unlock(&cookie_secrets->lock);
+ (void)ssl_printf(ssl, "error: writing to cookie secret file: \"%s\"\n",
+ secret_file);
+ return;
+ }
+ drop_cookie_secret(cookie_secrets);
+ if(worker->thread_num == 0)
+ (void)cookie_secret_file_dump(ssl, worker);
+ lock_basic_unlock(&cookie_secrets->lock);
+ send_ok(ssl);
+}
+
+/** Add cookie secret */
+static void
+do_add_cookie_secret(RES* ssl, struct worker* worker, char* arg) {
+ uint8_t secret[UNBOUND_COOKIE_SECRET_SIZE];
+ char const* secret_file = worker->env.cfg->cookie_secret_file;
+ struct cookie_secrets* cookie_secrets = worker->daemon->cookie_secrets;
+
+ if(secret_file == NULL || secret_file[0] == 0) {
+ (void)ssl_printf(ssl, "error: no cookie secret file configured\n");
+ return;
+ }
+ if(cookie_secrets == NULL) {
+ worker->daemon->cookie_secrets = cookie_secrets_create();
+ if(!worker->daemon->cookie_secrets) {
+ (void)ssl_printf(ssl, "error: out of memory");
+ return;
+ }
+ cookie_secrets = worker->daemon->cookie_secrets;
+ }
+ lock_basic_lock(&cookie_secrets->lock);
+
+ if(*arg == '\0') {
+ lock_basic_unlock(&cookie_secrets->lock);
+ (void)ssl_printf(ssl, "error: missing argument (cookie_secret)\n");
+ return;
+ }
+ if(strlen(arg) != 32) {
+ lock_basic_unlock(&cookie_secrets->lock);
+ explicit_bzero(arg, strlen(arg));
+ (void)ssl_printf(ssl, "invalid cookie secret: invalid argument length\n");
+ (void)ssl_printf(ssl, "please provide a 128bit hex encoded secret\n");
+ return;
+ }
+ if(hex_pton(arg, secret, UNBOUND_COOKIE_SECRET_SIZE) !=
+ UNBOUND_COOKIE_SECRET_SIZE ) {
+ lock_basic_unlock(&cookie_secrets->lock);
+ explicit_bzero(secret, UNBOUND_COOKIE_SECRET_SIZE);
+ explicit_bzero(arg, strlen(arg));
+ (void)ssl_printf(ssl, "invalid cookie secret: parse error\n");
+ (void)ssl_printf(ssl, "please provide a 128bit hex encoded secret\n");
+ return;
+ }
+ /* Only the worker 0 writes to file, the others update state. */
+ if(worker->thread_num == 0 && !cookie_secret_file_dump(ssl, worker)) {
+ lock_basic_unlock(&cookie_secrets->lock);
+ explicit_bzero(secret, UNBOUND_COOKIE_SECRET_SIZE);
+ explicit_bzero(arg, strlen(arg));
+ (void)ssl_printf(ssl, "error: writing to cookie secret file: \"%s\"\n",
+ secret_file);
+ return;
+ }
+ add_cookie_secret(cookie_secrets, secret, UNBOUND_COOKIE_SECRET_SIZE);
+ explicit_bzero(secret, UNBOUND_COOKIE_SECRET_SIZE);
+ if(worker->thread_num == 0)
+ (void)cookie_secret_file_dump(ssl, worker);
+ lock_basic_unlock(&cookie_secrets->lock);
+ explicit_bzero(arg, strlen(arg));
+ send_ok(ssl);
+}
+
+/** Print cookie secrets */
+static void
+do_print_cookie_secrets(RES* ssl, struct worker* worker) {
+ struct cookie_secrets* cookie_secrets = worker->daemon->cookie_secrets;
+ char secret_hex[UNBOUND_COOKIE_SECRET_SIZE * 2 + 1];
+ int i;
+
+ if(!cookie_secrets)
+ return; /* Output is empty. */
+ lock_basic_lock(&cookie_secrets->lock);
+ for(i = 0; (size_t)i < cookie_secrets->cookie_count; i++) {
+ struct cookie_secret const* cs = &cookie_secrets->
+ cookie_secrets[i];
+ ssize_t const len = hex_ntop(cs->cookie_secret,
+ UNBOUND_COOKIE_SECRET_SIZE, secret_hex,
+ sizeof(secret_hex));
+ (void)len; /* silence unused variable warning with -DNDEBUG */
+ log_assert( len == UNBOUND_COOKIE_SECRET_SIZE * 2 );
+ secret_hex[UNBOUND_COOKIE_SECRET_SIZE * 2] = '\0';
+ if (i == 0)
+ (void)ssl_printf(ssl, "active : %s\n", secret_hex);
+ else if (cookie_secrets->cookie_count == 2)
+ (void)ssl_printf(ssl, "staging: %s\n", secret_hex);
+ else
+ (void)ssl_printf(ssl, "staging[%d]: %s\n", i,
+ secret_hex);
+ }
+ lock_basic_unlock(&cookie_secrets->lock);
+ explicit_bzero(secret_hex, sizeof(secret_hex));
+}
+
/** check for name with end-of-string, space or tab after it */
static int
cmdcmp(char* p, const char* cmd, size_t len)
} else if(cmdcmp(p, "view_local_datas", 16)) {
do_view_datas_add(rc, ssl, worker, skipwhite(p+16));
return;
+ } else if(cmdcmp(p, "print_cookie_secrets", 20)) {
+ do_print_cookie_secrets(ssl, worker);
+ return;
}
#ifdef THREADS_DISABLED
do_rpz_enable(ssl, worker, skipwhite(p+10));
} else if(cmdcmp(p, "rpz_disable", 11)) {
do_rpz_disable(ssl, worker, skipwhite(p+11));
+ } else if(cmdcmp(p, "add_cookie_secret", 17)) {
+ do_add_cookie_secret(ssl, worker, skipwhite(p+17));
+ } else if(cmdcmp(p, "drop_cookie_secret", 18)) {
+ do_drop_cookie_secret(ssl, worker);
+ } else if(cmdcmp(p, "activate_cookie_secret", 22)) {
+ do_activate_cookie_secret(ssl, worker);
} else {
(void)ssl_printf(ssl, "error unknown command '%s'\n", p);
}
if((ret=parse_edns_from_query_pkt(
c->buffer, &edns, worker->env.cfg, c, repinfo,
(worker->env.now ? *worker->env.now : time(NULL)),
- worker->scratchpad)) != 0) {
+ worker->scratchpad,
+ worker->daemon->cookie_secrets)) != 0) {
struct edns_data reply_edns;
verbose(VERB_ALGO, "worker parse edns: formerror.");
log_addr(VERB_CLIENT, "from", &repinfo->client_addr,
# example value "000102030405060708090a0b0c0d0e0f".
# cookie-secret: <128 bit random hex string>
+ # File with cookie secrets, the 'cookie-secret:' option is ignored
+ # and the file can be managed to have staging and active secrets
+ # with remote control commands. Disabled with "". Default is "".
+ # cookie-secret-file: "/usr/local/etc/unbound_cookiesecrets.txt"
+
# Enable to attach Extended DNS Error codes (RFC8914) to responses.
# ede: no
.TP
.B view_local_datas \fIview\fR
Add a list of \fIlocal_data\fR for given view from stdin. Like local_datas.
+.TP
+.B add_cookie_secret <secret>
+Add or replace a cookie secret persistently. <secret> needs to be an 128 bit
+hex string.
+.IP
+Cookie secrets can be either \fIactive\fR or \fIstaging\fR. \fIActive\fR cookie
+secrets are used to create DNS Cookies, but verification of a DNS Cookie
+succeeds with any of the \fIactive\fR or \fIstaging\fR cookie secrets. The
+state of the current cookie secrets can be printed with the
+\fBprint_cookie_secrets\fR command.
+.IP
+When there are no cookie secrets configured yet, the <secret> is added as
+\fIactive\fR. If there is already an \fIactive\fR cookie secret, the <secret>
+is added as \fIstaging\fR or replacing an existing \fIstaging\fR secret.
+.IP
+To "roll" a cookie secret used in an anycast set. The new secret has to be
+added as staging secret to \fBall\fR nodes in the anycast set. When \fBall\fR
+nodes can verify DNS Cookies with the new secret, the new secret can be
+activated with the \fBactivate_cookie_secret\fR command. After \fBall\fR nodes
+have the new secret \fIactive\fR for at least one hour, the previous secret can
+be dropped with the \fBdrop_cookie_secret\fR command.
+.IP
+Persistence is accomplished by writing to a file which if configured with the
+\fBcookie\-secret\-file\fR option in the server section of the config file.
+This is disabled by default, "".
+.TP
+.B drop_cookie_secret
+Drop the \fIstaging\fR cookie secret.
+.TP
+.B activate_cookie_secret
+Make the current \fIstaging\fR cookie secret \fIactive\fR, and the current
+\fIactive\fR cookie secret \fIstaging\fR.
+.TP
+.B print_cookie_secrets
+Show the current configured cookie secrets with their status.
.SH "EXIT CODE"
The unbound\-control program exits with status code 1 on error, 0 on success.
.SH "SET UP"
share the secret in order to verify each other's Server Cookies.
An example hex string would be "000102030405060708090a0b0c0d0e0f".
Default is a 128 bits random secret generated at startup time.
+This option is ignored if a \fBcookie\-secret\-file\fR is
+present. In that case the secrets from that file are used in DNS Cookie
+calculations.
+.TP 5
+.B cookie\-secret\-file: \fI<filename>
+File from which the secrets are read used in DNS Cookie calculations. When this
+file exists, the secrets in this file are used and the secret specified by the
+\fBcookie-secret\fR option is ignored.
+Enable it by setting a filename, like "/usr/local/etc/unbound_cookiesecrets.txt".
+The content of this file must be manipulated with the \fBadd_cookie_secret\fR,
+\fBdrop_cookie_secret\fR and \fBactivate_cookie_secret\fR commands to the
+\fIunbound\-control\fR(8) tool. Please see that manpage on how to perform a
+safe cookie secret rollover.
+Default is "" (disabled).
.TP 5
.B edns\-client\-string: \fI<IP netblock> <string>
Include an EDNS0 option containing configured ascii string in queries with
printf(" rpz_enable zone Enable the RPZ zone if it had previously\n");
printf(" been disabled\n");
printf(" rpz_disable zone Disable the RPZ zone\n");
+ printf(" add_cookie_secret <secret> add (or replace) a new cookie secret <secret>\n");
+ printf(" drop_cookie_secret drop a staging cookie secret\n");
+ printf(" activate_cookie_secret make a staging cookie secret active\n");
+ printf(" print_cookie_secrets show all cookie secrets with their status\n");
printf("Version %s\n", PACKAGE_VERSION);
printf("BSD licensed, see LICENSE in source package for details.\n");
printf("Report bugs to %s\n", PACKAGE_BUGREPORT);
sldns_buffer_skip(pkt, 2 + 2);
/* decode */
unit_assert(parse_edns_from_query_pkt(pkt, edns, NULL, NULL, NULL, 0,
- region) == 0);
+ region, NULL) == 0);
}
static void edns_ede_encode_check(struct edns_data* edns, int* found_ede,
--- /dev/null
+server:
+ verbosity: 7
+ use-syslog: no
+ directory: ""
+ pidfile: "unbound.pid"
+ chroot: ""
+ username: ""
+ do-not-query-localhost: no
+ use-caps-for-id: no
+ port: @SERVER_PORT@
+ interface: 127.0.0.1
+ cookie-secret-file: "cookie_secrets.txt"
+ answer-cookie: yes
+ access-control: 127.0.0.0/8 allow_cookie # BADCOOKIE for incomplete/invalid cookies
+
+remote-control:
+ control-enable: yes
+ control-port: @CONTROL_PORT@
+ control-use-cert: no
--- /dev/null
+BaseName: cookie_file
+Version: 1.0
+Description: Check the cookie rollover
+CreationDate: Fri 14 Jun 11:00:00 CEST 2024
+Maintainer:
+Category:
+Component:
+CmdDepends:
+Depends:
+Help:
+Pre: cookie_file.pre
+Post: cookie_file.post
+Test: cookie_file.test
+AuxFiles:
+Passed:
+Failure:
--- /dev/null
+# #-- cookie_file.post --#
+# source the master var file when it's there
+[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
+# source the test var file when it's there
+[ -f .tpkg.var.test ] && source .tpkg.var.test
+#
+# do your teardown here
+. ../common.sh
+kill_from_pidfile "unbound.pid"
+cat unbound.log
--- /dev/null
+# #-- cookie_file.pre--#
+PRE="../.."
+. ../common.sh
+
+get_random_port 2
+SERVER_PORT=$RND_PORT
+CONTROL_PORT=$(($RND_PORT + 1))
+echo "SERVER_PORT=$SERVER_PORT" >> .tpkg.var.test
+echo "CONTROL_PORT=$CONTROL_PORT" >> .tpkg.var.test
+
+# make config file
+sed \
+ -e 's/@SERVER_PORT\@/'$SERVER_PORT'/' \
+ -e 's/@CONTROL_PORT\@/'$CONTROL_PORT'/' \
+ < cookie_file.conf > ub.conf
+
+# empty cookie file
+touch cookie_secrets.txt
+
+# start unbound in the background
+$PRE/unbound -d -c ub.conf > unbound.log 2>&1 &
+
+cat .tpkg.var.test
+wait_unbound_up unbound.log
--- /dev/null
+# #-- cookie_file.test --#
+# source the master var file when it's there
+[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
+# use .tpkg.var.test for in test variable passing
+[ -f .tpkg.var.test ] && source .tpkg.var.test
+PRE="../.."
+. ../common.sh
+
+first_secret=dd3bdf9344b678b185a6f5cb60fca715
+second_secret=445536bcd2513298075a5d379663c962
+
+
+teststep "Add first secret"
+echo ">> add_cookie_secret $first_secret"
+$PRE/unbound-control -c ub.conf add_cookie_secret $first_secret
+# check secret is persisted
+outfile=cookie_secrets.1
+$PRE/unbound-control -c ub.conf print_cookie_secrets > $outfile
+if ! grep -q "$first_secret" $outfile
+then
+ sleep 1
+ $PRE/unbound-control -c ub.conf print_cookie_secrets > $outfile
+fi
+if ! grep -q "$first_secret" $outfile
+then
+ sleep 1
+ $PRE/unbound-control -c ub.conf print_cookie_secrets > $outfile
+fi
+if ! grep -q "$first_secret" $outfile
+then
+ sleep 1
+ $PRE/unbound-control -c ub.conf print_cookie_secrets > $outfile
+fi
+if ! grep -q "^active.*$first_secret" $outfile
+then
+ cat $outfile
+ echo "First secret was not provisioned"
+ exit 1
+fi
+echo ">> print_cookie_secrets"
+cat $outfile
+
+
+teststep "Get a valid cookie for this secret"
+outfile=dig.output.1
+dig version.server ch txt @127.0.0.1 -p $SERVER_PORT +cookie=3132333435363738 > $outfile
+if ! grep -q "BADCOOKIE" $outfile
+then
+ cat $outfile
+ echo "Did not get a BADCOOKIE response for a client-only cookie"
+ exit 1
+fi
+if ! grep -q "COOKIE: 3132333435363738" $outfile
+then
+ cat $outfile
+ echo "Did not get a cookie in the response"
+ exit 1
+fi
+first_cookie=$(grep "; COOKIE:" $outfile | cut -d ' ' -f 3)
+cat $outfile
+echo "first cookie: $first_cookie"
+
+
+teststep "Verify the first cookie can be reused"
+outfile=dig.output.2
+dig version.server ch txt @127.0.0.1 -p $SERVER_PORT +cookie=$first_cookie > $outfile
+if grep -q "BADCOOKIE" $outfile
+then
+ cat $outfile
+ echo "Got BADCOOKIE response for a valid cookie"
+ exit 1
+fi
+if ! grep -q "COOKIE: $first_cookie" $outfile
+then
+ cat $outfile
+ echo "Did not get the same first cookie in the response"
+ exit 1
+fi
+
+
+teststep "Add second secret"
+outfile=cookie_secrets.2
+echo ">> add_cookie_secret $second_secret"
+$PRE/unbound-control -c ub.conf add_cookie_secret $second_secret
+$PRE/unbound-control -c ub.conf print_cookie_secrets > $outfile
+if ! grep -q "$second_secret" $outfile
+then
+ sleep 1
+ $PRE/unbound-control -c ub.conf print_cookie_secrets > $outfile
+fi
+if ! grep -q "$second_secret" $outfile
+then
+ sleep 1
+ $PRE/unbound-control -c ub.conf print_cookie_secrets > $outfile
+fi
+if ! grep -q "$second_secret" $outfile
+then
+ sleep 1
+ $PRE/unbound-control -c ub.conf print_cookie_secrets > $outfile
+fi
+if ! grep -q "^staging.*$second_secret" $outfile \
+ || ! grep -q "^active.*$first_secret" $outfile
+then
+ cat $outfile
+ echo "Secrets were not provisioned"
+ exit 1
+fi
+echo ">> print_cookie_secrets"
+cat $outfile
+echo ">> cookie_secrets.txt"
+cat cookie_secrets.txt
+
+
+teststep "Verify the first cookie can be reused"
+outfile=dig.output.3
+dig version.server ch txt @127.0.0.1 -p $SERVER_PORT +cookie=$first_cookie > $outfile
+if grep -q "BADCOOKIE" $outfile
+then
+ cat $outfile
+ echo "Got BADCOOKIE response for a valid cookie"
+ exit 1
+fi
+if ! grep -q "COOKIE: $first_cookie" $outfile
+then
+ cat $outfile
+ echo "Did not get the same first cookie in the response"
+ exit 1
+fi
+
+
+teststep "Secret rollover"
+outfile=cookie_secrets.3
+$PRE/unbound-control -c ub.conf activate_cookie_secret
+$PRE/unbound-control -c ub.conf print_cookie_secrets > $outfile
+if ! grep -q "^active.*$second_secret" $outfile
+then
+ sleep 1
+ $PRE/unbound-control -c ub.conf print_cookie_secrets > $outfile
+fi
+if ! grep -q "^active.*$second_secret" $outfile
+then
+ sleep 1
+ $PRE/unbound-control -c ub.conf print_cookie_secrets > $outfile
+fi
+if ! grep -q "^active.*$second_secret" $outfile
+then
+ sleep 1
+ $PRE/unbound-control -c ub.conf print_cookie_secrets > $outfile
+fi
+if ! grep -q "^active.*$second_secret" $outfile \
+ || ! grep -q "^staging.*$first_secret" $outfile
+then
+ cat $outfile
+ echo "Second secret was not activated"
+ exit 1
+fi
+echo ">> activate cookie secret, printout"
+cat $outfile
+echo ">> cookie_secrets.txt"
+cat cookie_secrets.txt
+
+
+teststep "Verify the first cookie can be reused but a new cookie is returned from the second secret"
+outfile=dig.output.4
+dig version.server ch txt @127.0.0.1 -p $SERVER_PORT +cookie=$first_cookie > $outfile
+if grep -q "BADCOOKIE" $outfile
+then
+ cat $outfile
+ echo "Got BADCOOKIE response for a valid cookie"
+ exit 1
+fi
+if ! grep -q "COOKIE: 3132333435363738" $outfile
+then
+ cat $outfile
+ echo "Did not get a cookie in the response"
+ exit 1
+fi
+if grep -q "COOKIE: $first_cookie" $outfile
+then
+ cat $outfile
+ echo "Got the same first cookie in the response while the second secret is active"
+ exit 1
+fi
+second_cookie=$(grep "; COOKIE:" $outfile | cut -d ' ' -f 3)
+cat $outfile
+echo "second cookie: $second_cookie"
+
+
+teststep "Drop cookie secret"
+outfile=cookie_secrets.4
+$PRE/unbound-control -c ub.conf drop_cookie_secret
+$PRE/unbound-control -c ub.conf print_cookie_secrets > $outfile
+if grep -q "^staging.*$first_secret" $outfile
+then
+ sleep 1
+ $PRE/unbound-control -c ub.conf print_cookie_secrets > $outfile
+fi
+if grep -q "^staging.*$first_secret" $outfile
+then
+ sleep 1
+ $PRE/unbound-control -c ub.conf print_cookie_secrets > $outfile
+fi
+if grep -q "^staging.*$first_secret" $outfile
+then
+ sleep 1
+ $PRE/unbound-control -c ub.conf print_cookie_secrets > $outfile
+fi
+if grep -q "^staging.*$first_secret" $outfile
+then
+ cat $outfile
+ echo "First secret was not dropped"
+ exit 1
+fi
+echo ">> drop cookie secret, printout"
+cat $outfile
+echo ">> cookie_secrets.txt"
+cat cookie_secrets.txt
+
+
+teststep "Verify the first cookie can not be reused and the second cookie is returned instead"
+outfile=dig.output.4
+dig version.server ch txt @127.0.0.1 -p $SERVER_PORT +cookie=$first_cookie > $outfile
+if ! grep -q "BADCOOKIE" $outfile
+then
+ cat $outfile
+ echo "Did not get BADCOOKIE response for an invalid cookie"
+ exit 1
+fi
+if ! grep -q "COOKIE: 3132333435363738" $outfile
+then
+ cat $outfile
+ echo "Did not get a cookie in the response"
+ exit 1
+fi
+if grep -q "COOKIE: $first_cookie" $outfile
+then
+ cat $outfile
+ echo "Got the same first cookie in the response while the second secret is active"
+ exit 1
+fi
+if ! grep -q "COOKIE: $second_cookie" $outfile
+then
+ cat $outfile
+ echo "Did not get the same second cookie in the response"
+ exit 1
+fi
+
+exit 0
memset(cfg->cookie_secret, 0, sizeof(cfg->cookie_secret));
cfg->cookie_secret_len = 16;
init_cookie_secret(cfg->cookie_secret, cfg->cookie_secret_len);
+ cfg->cookie_secret_file = NULL;
#ifdef USE_CACHEDB
if(!(cfg->cachedb_backend = strdup("testframe"))) goto error_exit;
if(!(cfg->cachedb_secret = strdup("default"))) goto error_exit;
{ IS_NUMBER_OR_ZERO; cfg->ipsecmod_max_ttl = atoi(val); }
else S_YNO("ipsecmod-strict:", ipsecmod_strict)
#endif
+ else S_YNO("answer-cookie:", do_answer_cookie)
+ else S_STR("cookie-secret-file:", cookie_secret_file)
#ifdef USE_CACHEDB
else S_YNO("cachedb-no-store:", cachedb_no_store)
else S_YNO("cachedb-check-when-serve-expired:", cachedb_check_when_serve_expired)
else O_LST(opt, "ipsecmod-whitelist", ipsecmod_whitelist)
else O_YNO(opt, "ipsecmod-strict", ipsecmod_strict)
#endif
+ else O_YNO(opt, "answer-cookie", do_answer_cookie)
+ else O_STR(opt, "cookie-secret-file", cookie_secret_file)
#ifdef USE_CACHEDB
else O_STR(opt, "backend", cachedb_backend)
else O_STR(opt, "secret-seed", cachedb_secret)
free(cfg->ipsecmod_hook);
config_delstrlist(cfg->ipsecmod_whitelist);
#endif
+ free(cfg->cookie_secret_file);
#ifdef USE_CACHEDB
free(cfg->cachedb_backend);
free(cfg->cachedb_secret);
uint8_t cookie_secret[40];
/** cookie secret length */
size_t cookie_secret_len;
+ /** path to cookie secret store */
+ char* cookie_secret_file;
/* ipset module */
#ifdef USE_IPSET
tcp-connection-limit{COLON} { YDVAR(2, VAR_TCP_CONNECTION_LIMIT) }
answer-cookie{COLON} { YDVAR(1, VAR_ANSWER_COOKIE ) }
cookie-secret{COLON} { YDVAR(1, VAR_COOKIE_SECRET) }
+cookie-secret-file{COLON} { YDVAR(1, VAR_COOKIE_SECRET_FILE) }
edns-client-string{COLON} { YDVAR(2, VAR_EDNS_CLIENT_STRING) }
edns-client-string-opcode{COLON} { YDVAR(1, VAR_EDNS_CLIENT_STRING_OPCODE) }
nsid{COLON} { YDVAR(1, VAR_NSID ) }
%token VAR_PROXY_PROTOCOL_PORT VAR_STATISTICS_INHIBIT_ZERO
%token VAR_HARDEN_UNKNOWN_ADDITIONAL VAR_DISABLE_EDNS_DO VAR_CACHEDB_NO_STORE
%token VAR_LOG_DESTADDR VAR_CACHEDB_CHECK_WHEN_SERVE_EXPIRED
+%token VAR_COOKIE_SECRET_FILE
%%
toplevelvars: /* empty */ | toplevelvars toplevelvar ;
server_interface_automatic_ports | server_ede |
server_proxy_protocol_port | server_statistics_inhibit_zero |
server_harden_unknown_additional | server_disable_edns_do |
- server_log_destaddr
+ server_log_destaddr | server_cookie_secret_file
;
stubstart: VAR_STUB_ZONE
{
free($2);
}
;
- ipsetstart: VAR_IPSET
- {
- OUTYY(("\nP(ipset:)\n"));
- cfg_parser->started_toplevel = 1;
- }
- ;
- contents_ipset: contents_ipset content_ipset
- | ;
- content_ipset: ipset_name_v4 | ipset_name_v6
- ;
- ipset_name_v4: VAR_IPSET_NAME_V4 STRING_ARG
- {
- #ifdef USE_IPSET
- OUTYY(("P(name-v4:%s)\n", $2));
- if(cfg_parser->cfg->ipset_name_v4)
- yyerror("ipset name v4 override, there must be one "
- "name for ip v4");
- free(cfg_parser->cfg->ipset_name_v4);
- cfg_parser->cfg->ipset_name_v4 = $2;
- #else
- OUTYY(("P(Compiled without ipset, ignoring)\n"));
- free($2);
- #endif
- }
+server_cookie_secret_file: VAR_COOKIE_SECRET_FILE STRING_ARG
+ {
+ OUTYY(("P(cookie_secret_file:%s)\n", $2));
+ free(cfg_parser->cfg->cookie_secret_file);
+ cfg_parser->cfg->cookie_secret_file = $2;
+ }
;
- ipset_name_v6: VAR_IPSET_NAME_V6 STRING_ARG
- {
- #ifdef USE_IPSET
- OUTYY(("P(name-v6:%s)\n", $2));
- if(cfg_parser->cfg->ipset_name_v6)
- yyerror("ipset name v6 override, there must be one "
- "name for ip v6");
- free(cfg_parser->cfg->ipset_name_v6);
- cfg_parser->cfg->ipset_name_v6 = $2;
- #else
- OUTYY(("P(Compiled without ipset, ignoring)\n"));
- free($2);
- #endif
- }
+ipsetstart: VAR_IPSET
+ {
+ OUTYY(("\nP(ipset:)\n"));
+ cfg_parser->started_toplevel = 1;
+ }
+ ;
+contents_ipset: contents_ipset content_ipset
+ | ;
+content_ipset: ipset_name_v4 | ipset_name_v6
+ ;
+ipset_name_v4: VAR_IPSET_NAME_V4 STRING_ARG
+ {
+ #ifdef USE_IPSET
+ OUTYY(("P(name-v4:%s)\n", $2));
+ if(cfg_parser->cfg->ipset_name_v4)
+ yyerror("ipset name v4 override, there must be one "
+ "name for ip v4");
+ free(cfg_parser->cfg->ipset_name_v4);
+ cfg_parser->cfg->ipset_name_v4 = $2;
+ #else
+ OUTYY(("P(Compiled without ipset, ignoring)\n"));
+ free($2);
+ #endif
+ }
+ ;
+ipset_name_v6: VAR_IPSET_NAME_V6 STRING_ARG
+ {
+ #ifdef USE_IPSET
+ OUTYY(("P(name-v6:%s)\n", $2));
+ if(cfg_parser->cfg->ipset_name_v6)
+ yyerror("ipset name v6 override, there must be one "
+ "name for ip v6");
+ free(cfg_parser->cfg->ipset_name_v6);
+ cfg_parser->cfg->ipset_name_v6 = $2;
+ #else
+ OUTYY(("P(Compiled without ipset, ignoring)\n"));
+ free($2);
+ #endif
+ }
;
%%
static int
parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len,
struct edns_data* edns, struct config_file* cfg, struct comm_point* c,
- struct comm_reply* repinfo, uint32_t now, struct regional* region)
+ struct comm_reply* repinfo, uint32_t now, struct regional* region,
+ struct cookie_secrets* cookie_secrets)
{
/* To respond with a Keepalive option, the client connection must have
* received one message with a TCP Keepalive EDNS option, and that
&((struct sockaddr_in6*)&repinfo->remote_addr)->sin6_addr, 16);
}
- cookie_val_status = edns_cookie_server_validate(
- rdata_ptr, opt_len, cfg->cookie_secret,
- cfg->cookie_secret_len, cookie_is_v4,
- server_cookie, now);
+ if(cfg->cookie_secret_file &&
+ cfg->cookie_secret_file[0]) {
+ /* Loop over the active and staging cookies. */
+ cookie_val_status =
+ cookie_secrets_server_validate(
+ rdata_ptr, opt_len, cookie_secrets,
+ cookie_is_v4, server_cookie, now);
+ } else {
+ /* Use the cookie option value to validate. */
+ cookie_val_status = edns_cookie_server_validate(
+ rdata_ptr, opt_len, cfg->cookie_secret,
+ cfg->cookie_secret_len, cookie_is_v4,
+ server_cookie, now);
+ }
+ if(cookie_val_status == COOKIE_STATUS_VALID_RENEW)
+ edns->cookie_valid = 1;
switch(cookie_val_status) {
case COOKIE_STATUS_VALID:
- case COOKIE_STATUS_VALID_RENEW:
edns->cookie_valid = 1;
/* Reuse cookie */
if(!edns_opt_list_append(
edns->cookie_client = 1;
ATTR_FALLTHROUGH
/* fallthrough */
+ case COOKIE_STATUS_VALID_RENEW:
case COOKIE_STATUS_FUTURE:
case COOKIE_STATUS_EXPIRED:
case COOKIE_STATUS_INVALID:
default:
- edns_cookie_server_write(server_cookie,
- cfg->cookie_secret, cookie_is_v4, now);
+ if(cfg->cookie_secret_file &&
+ cfg->cookie_secret_file[0]) {
+ if(!cookie_secrets)
+ break;
+ lock_basic_lock(&cookie_secrets->lock);
+ if(cookie_secrets->cookie_count < 1) {
+ lock_basic_unlock(&cookie_secrets->lock);
+ break;
+ }
+ edns_cookie_server_write(server_cookie,
+ cookie_secrets->cookie_secrets[0].cookie_secret,
+ cookie_is_v4, now);
+ lock_basic_unlock(&cookie_secrets->lock);
+ } else {
+ 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)) {
int
parse_edns_from_query_pkt(sldns_buffer* pkt, struct edns_data* edns,
struct config_file* cfg, struct comm_point* c,
- struct comm_reply* repinfo, time_t now, struct regional* region)
+ struct comm_reply* repinfo, time_t now, struct regional* region,
+ struct cookie_secrets* cookie_secrets)
{
size_t rdata_len;
uint8_t* rdata_ptr;
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, cookie_secrets);
}
void
struct config_file;
struct comm_point;
struct comm_reply;
+struct cookie_secrets;
/** number of buckets in parse rrset hash table. Must be power of 2. */
#define PARSE_TABLE_SIZE 32
* @param repinfo: commreply to determine the client address
* @param now: current time
* @param region: region to alloc results in (edns option contents)
+ * @param cookie_secrets: the cookie secrets for EDNS COOKIE validation.
* @return: 0 on success, or an RCODE on error.
* RCODE formerr if OPT is badly formatted and so on.
*/
int parse_edns_from_query_pkt(struct sldns_buffer* pkt, struct edns_data* edns,
struct config_file* cfg, struct comm_point* c,
- struct comm_reply* repinfo, time_t now, struct regional* region);
+ struct comm_reply* repinfo, time_t now, struct regional* region,
+ struct cookie_secrets* cookie_secrets);
/**
* Calculate hash value for rrset in packet.
return COOKIE_STATUS_VALID_RENEW;
return COOKIE_STATUS_VALID;
}
+
+struct cookie_secrets*
+cookie_secrets_create(void)
+{
+ struct cookie_secrets* cookie_secrets = calloc(1,
+ sizeof(*cookie_secrets));
+ if(!cookie_secrets)
+ return NULL;
+ lock_basic_init(&cookie_secrets->lock);
+ lock_protect(&cookie_secrets->lock, &cookie_secrets->cookie_count,
+ sizeof(cookie_secrets->cookie_count));
+ lock_protect(&cookie_secrets->lock, cookie_secrets->cookie_secrets,
+ sizeof(cookie_secret_type)*UNBOUND_COOKIE_HISTORY_SIZE);
+ return cookie_secrets;
+}
+
+void
+cookie_secrets_delete(struct cookie_secrets* cookie_secrets)
+{
+ if(!cookie_secrets)
+ return;
+ lock_basic_destroy(&cookie_secrets->lock);
+ explicit_bzero(cookie_secrets->cookie_secrets,
+ sizeof(cookie_secret_type)*UNBOUND_COOKIE_HISTORY_SIZE);
+ free(cookie_secrets);
+}
+
+/** Read the cookie secret file */
+static int
+cookie_secret_file_read(struct cookie_secrets* cookie_secrets,
+ char* cookie_secret_file)
+{
+ char secret[UNBOUND_COOKIE_SECRET_SIZE * 2 + 2/*'\n' and '\0'*/];
+ FILE* f;
+ int corrupt = 0;
+ size_t count;
+
+ log_assert(cookie_secret_file != NULL);
+ cookie_secrets->cookie_count = 0;
+ f = fopen(cookie_secret_file, "r");
+ /* a non-existing cookie file is not an error */
+ if( f == NULL ) {
+ if(errno != EPERM) {
+ log_err("Could not read cookie-secret-file '%s': %s",
+ cookie_secret_file, strerror(errno));
+ return 0;
+ }
+ return 1;
+ }
+ /* cookie secret file exists and is readable */
+ for( count = 0; count < UNBOUND_COOKIE_HISTORY_SIZE; count++ ) {
+ size_t secret_len = 0;
+ ssize_t decoded_len = 0;
+ if( fgets(secret, sizeof(secret), f) == NULL ) { break; }
+ secret_len = strlen(secret);
+ if( secret_len == 0 ) { break; }
+ log_assert( secret_len <= sizeof(secret) );
+ secret_len = secret[secret_len - 1] == '\n' ? secret_len - 1 : secret_len;
+ if( secret_len != UNBOUND_COOKIE_SECRET_SIZE * 2 ) { corrupt++; break; }
+ /* needed for `hex_pton`; stripping potential `\n` */
+ secret[secret_len] = '\0';
+ decoded_len = hex_pton(secret, cookie_secrets->cookie_secrets[count].cookie_secret,
+ UNBOUND_COOKIE_SECRET_SIZE);
+ if( decoded_len != UNBOUND_COOKIE_SECRET_SIZE ) { corrupt++; break; }
+ cookie_secrets->cookie_count++;
+ }
+ fclose(f);
+ return corrupt == 0;
+}
+
+int
+cookie_secrets_apply_cfg(struct cookie_secrets* cookie_secrets,
+ char* cookie_secret_file)
+{
+ if(!cookie_secrets) {
+ if(!cookie_secret_file || !cookie_secret_file[0])
+ return 1; /* There is nothing to read anyway */
+ log_err("Could not read cookie secrets, no structure alloced");
+ return 0;
+ }
+ if(!cookie_secret_file_read(cookie_secrets, cookie_secret_file))
+ return 0;
+ return 1;
+}
+
+enum edns_cookie_val_status
+cookie_secrets_server_validate(const uint8_t* cookie, size_t cookie_len,
+ struct cookie_secrets* cookie_secrets, int v4,
+ const uint8_t* hash_input, uint32_t now)
+{
+ size_t i;
+ enum edns_cookie_val_status cookie_val_status,
+ last = COOKIE_STATUS_INVALID;
+ if(!cookie_secrets)
+ return COOKIE_STATUS_INVALID; /* There are no cookie secrets.*/
+ lock_basic_lock(&cookie_secrets->lock);
+ if(cookie_secrets->cookie_count == 0) {
+ lock_basic_unlock(&cookie_secrets->lock);
+ return COOKIE_STATUS_INVALID; /* There are no cookie secrets.*/
+ }
+ for(i=0; i<cookie_secrets->cookie_count; i++) {
+ cookie_val_status = edns_cookie_server_validate(cookie,
+ cookie_len,
+ cookie_secrets->cookie_secrets[i].cookie_secret,
+ UNBOUND_COOKIE_SECRET_SIZE, v4, hash_input, now);
+ if(cookie_val_status == COOKIE_STATUS_VALID ||
+ cookie_val_status == COOKIE_STATUS_VALID_RENEW) {
+ lock_basic_unlock(&cookie_secrets->lock);
+ /* For staging cookies, write a fresh cookie. */
+ if(i != 0)
+ return COOKIE_STATUS_VALID_RENEW;
+ return cookie_val_status;
+ }
+ if(last == COOKIE_STATUS_INVALID)
+ last = cookie_val_status; /* Store more interesting
+ failure to return. */
+ }
+ lock_basic_unlock(&cookie_secrets->lock);
+ return last;
+}
+
+void add_cookie_secret(struct cookie_secrets* cookie_secrets,
+ uint8_t* secret, size_t secret_len)
+{
+ log_assert(secret_len == UNBOUND_COOKIE_SECRET_SIZE);
+ (void)secret_len;
+ if(!cookie_secrets)
+ return;
+
+ /* New cookie secret becomes the staging secret (position 1)
+ * unless there is no active cookie yet, then it becomes the active
+ * secret. If the UNBOUND_COOKIE_HISTORY_SIZE > 2 then all staging cookies
+ * are moved one position down.
+ */
+ if(cookie_secrets->cookie_count == 0) {
+ memcpy( cookie_secrets->cookie_secrets->cookie_secret
+ , secret, UNBOUND_COOKIE_SECRET_SIZE);
+ cookie_secrets->cookie_count = 1;
+ explicit_bzero(secret, UNBOUND_COOKIE_SECRET_SIZE);
+ return;
+ }
+#if UNBOUND_COOKIE_HISTORY_SIZE > 2
+ memmove( &cookie_secrets->cookie_secrets[2], &cookie_secrets->cookie_secrets[1]
+ , sizeof(struct cookie_secret) * (UNBOUND_COOKIE_HISTORY_SIZE - 2));
+#endif
+ memcpy( cookie_secrets->cookie_secrets[1].cookie_secret
+ , secret, UNBOUND_COOKIE_SECRET_SIZE);
+ cookie_secrets->cookie_count = cookie_secrets->cookie_count < UNBOUND_COOKIE_HISTORY_SIZE
+ ? cookie_secrets->cookie_count + 1 : UNBOUND_COOKIE_HISTORY_SIZE;
+ explicit_bzero(secret, UNBOUND_COOKIE_SECRET_SIZE);
+}
+
+void activate_cookie_secret(struct cookie_secrets* cookie_secrets)
+{
+ uint8_t active_secret[UNBOUND_COOKIE_SECRET_SIZE];
+ if(!cookie_secrets)
+ return;
+ /* The staging secret becomes the active secret.
+ * The active secret becomes a staging secret.
+ * If the UNBOUND_COOKIE_HISTORY_SIZE > 2 then all staging secrets are moved
+ * one position up and the previously active secret becomes the last
+ * staging secret.
+ */
+ if(cookie_secrets->cookie_count < 2)
+ return;
+ memcpy( active_secret, cookie_secrets->cookie_secrets[0].cookie_secret
+ , UNBOUND_COOKIE_SECRET_SIZE);
+ memmove( &cookie_secrets->cookie_secrets[0], &cookie_secrets->cookie_secrets[1]
+ , sizeof(struct cookie_secret) * (UNBOUND_COOKIE_HISTORY_SIZE - 1));
+ memcpy( cookie_secrets->cookie_secrets[cookie_secrets->cookie_count - 1].cookie_secret
+ , active_secret, UNBOUND_COOKIE_SECRET_SIZE);
+ explicit_bzero(active_secret, UNBOUND_COOKIE_SECRET_SIZE);
+}
+
+void drop_cookie_secret(struct cookie_secrets* cookie_secrets)
+{
+ if(!cookie_secrets)
+ return;
+ /* Drops a staging cookie secret. If there are more than one, it will
+ * drop the last staging secret. */
+ if(cookie_secrets->cookie_count < 2)
+ return;
+ explicit_bzero( cookie_secrets->cookie_secrets[cookie_secrets->cookie_count - 1].cookie_secret
+ , UNBOUND_COOKIE_SECRET_SIZE);
+ cookie_secrets->cookie_count -= 1;
+}
#define UTIL_EDNS_H
#include "util/storage/dnstree.h"
+#include "util/locks.h"
struct edns_data;
struct config_file;
size_t string_len;
};
+#define UNBOUND_COOKIE_HISTORY_SIZE 2
+#define UNBOUND_COOKIE_SECRET_SIZE 16
+
+typedef struct cookie_secret cookie_secret_type;
+struct cookie_secret {
+ /** cookie secret */
+ uint8_t cookie_secret[UNBOUND_COOKIE_SECRET_SIZE];
+};
+
+/**
+ * The cookie secrets from the cookie-secret-file.
+ */
+struct cookie_secrets {
+ /** lock on the structure, in case there are modifications
+ * from remote control, this avoids race conditions. */
+ lock_basic_type lock;
+
+ /** how many cookies are there in the cookies array */
+ size_t cookie_count;
+
+ /* keep track of the last `UNBOUND_COOKIE_HISTORY_SIZE`
+ * cookies as per rfc requirement .*/
+ cookie_secret_type cookie_secrets[UNBOUND_COOKIE_HISTORY_SIZE];
+};
+
enum edns_cookie_val_status {
COOKIE_STATUS_CLIENT_ONLY = -3,
COOKIE_STATUS_FUTURE = -2,
size_t cookie_len, const uint8_t* secret, size_t secret_len, int v4,
const uint8_t* hash_input, uint32_t now);
+/**
+ * Create the cookie secrets structure.
+ * @return the structure or NULL on failure.
+ */
+struct cookie_secrets* cookie_secrets_create(void);
+
+/**
+ * Delete the cookie secrets.
+ * @param cookie_secrets: the cookie secrets.
+ */
+void cookie_secrets_delete(struct cookie_secrets* cookie_secrets);
+
+/**
+ * Apply configuration to cookie secrets, read them from file.
+ * @param cookie_secrets: the cookie secrets structure.
+ * @param cookie_secret_file: the file name, it is read.
+ * @return false on failure.
+ */
+int cookie_secrets_apply_cfg(struct cookie_secrets* cookie_secrets,
+ char* cookie_secret_file);
+
+/**
+ * Validate the cookie secrets, try all of them.
+ * @param cookie: pointer to the cookie data.
+ * @param cookie_len: the length of the cookie data.
+ * @param cookie_secrets: struct of cookie secrets.
+ * @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 edns_cookie_val_status with the cookie validation status i.e.,
+ * <=0 for invalid, else valid.
+ */
+enum edns_cookie_val_status cookie_secrets_server_validate(
+ const uint8_t* cookie, size_t cookie_len,
+ struct cookie_secrets* cookie_secrets, int v4,
+ const uint8_t* hash_input, uint32_t now);
+
+/**
+ * Add a cookie secret. If there are no secrets yet, the secret will become
+ * the active secret. Otherwise it will become the staging secret.
+ * Active secrets are used to both verify and create new DNS Cookies.
+ * Staging secrets are only used to verify DNS Cookies. Caller has to lock.
+ */
+void add_cookie_secret(struct cookie_secrets* cookie_secrets, uint8_t* secret,
+ size_t secret_len);
+
+/**
+ * Makes the staging cookie secret active and the active secret staging.
+ * Caller has to lock.
+ */
+void activate_cookie_secret(struct cookie_secrets* cookie_secrets);
+
+/**
+ * Drop a cookie secret. Drops the staging secret. An active secret will not
+ * be dropped. Caller has to lock.
+ */
+void drop_cookie_secret(struct cookie_secrets* cookie_secrets);
+
#endif
#ifdef HAVE_NETIOAPI_H
#include <netioapi.h>
#endif
+#include <ctype.h>
#include "util/net_help.h"
#include "util/log.h"
#include "util/data/dname.h"
closesocket(socket);
}
# endif /* USE_WINSOCK */
+
+ssize_t
+hex_ntop(uint8_t const *src, size_t srclength, char *target, size_t targsize)
+{
+ static char hexdigits[] = {
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
+ };
+ size_t i;
+
+ if (targsize < srclength * 2 + 1) {
+ return -1;
+ }
+
+ for (i = 0; i < srclength; ++i) {
+ *target++ = hexdigits[src[i] >> 4U];
+ *target++ = hexdigits[src[i] & 0xfU];
+ }
+ *target = '\0';
+ return 2 * srclength;
+}
+
+ssize_t
+hex_pton(const char* src, uint8_t* target, size_t targsize)
+{
+ uint8_t *t = target;
+ if(strlen(src) % 2 != 0 || strlen(src)/2 > targsize) {
+ return -1;
+ }
+ while(*src) {
+ if(!isxdigit((unsigned char)src[0]) ||
+ !isxdigit((unsigned char)src[1]))
+ return -1;
+ *t++ = sldns_hexdigit_to_int(src[0]) * 16 +
+ sldns_hexdigit_to_int(src[1]) ;
+ src += 2;
+ }
+ return t-target;
+}
/** close the socket with close, or wsa closesocket */
void sock_close(int socket);
+/**
+ * Convert binary data to a string of hexadecimal characters.
+ */
+ssize_t hex_ntop(uint8_t const *src, size_t srclength, char *target,
+ size_t targsize);
+
+/** Convert hexadecimal data to binary. */
+ssize_t hex_pton(const char* src, uint8_t* target, size_t targsize);
+
#endif /* NET_HELP_H */