mirror of
https://github.com/NLnetLabs/unbound.git
synced 2024-09-21 06:37:08 +00:00
Cookie secret file (#1090)
* - 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>
This commit is contained in:
parent
50cf55bdac
commit
ad21dbd1c2
@ -1298,7 +1298,7 @@ remote.lo remote.o: $(srcdir)/daemon/remote.c config.h $(srcdir)/daemon/remote.h
|
||||
$(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 \
|
||||
|
@ -735,6 +735,14 @@ daemon_fork(struct daemon* daemon)
|
||||
"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");
|
||||
@ -929,6 +937,7 @@ daemon_delete(struct daemon* daemon)
|
||||
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);
|
||||
|
@ -58,6 +58,7 @@ struct ub_randstate;
|
||||
struct daemon_remote;
|
||||
struct respip_set;
|
||||
struct shm_main_info;
|
||||
struct cookie_secrets;
|
||||
|
||||
#include "dnstap/dnstap_config.h"
|
||||
#ifdef USE_DNSTAP
|
||||
@ -148,6 +149,8 @@ struct daemon {
|
||||
#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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
214
daemon/remote.c
214
daemon/remote.c
@ -88,6 +88,7 @@
|
||||
#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
|
||||
@ -3195,6 +3196,210 @@ do_rpz_disable(RES* ssl, struct worker* worker, char* arg)
|
||||
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)
|
||||
@ -3327,6 +3532,9 @@ execute_cmd(struct daemon_remote* rc, RES* ssl, char* cmd,
|
||||
} 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
|
||||
@ -3391,6 +3599,12 @@ execute_cmd(struct daemon_remote* rc, RES* ssl, char* cmd,
|
||||
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);
|
||||
}
|
||||
|
@ -1573,7 +1573,8 @@ worker_handle_request(struct comm_point* c, void* arg, int error,
|
||||
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,
|
||||
|
@ -1044,6 +1044,11 @@ server:
|
||||
# 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
|
||||
|
||||
|
@ -350,6 +350,41 @@ Remove a list of \fIlocal_data\fR for given view from stdin. Like local_datas_re
|
||||
.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"
|
||||
|
@ -1983,6 +1983,20 @@ Useful to explicitly set for servers in an anycast deployment that need to
|
||||
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
|
||||
|
@ -186,6 +186,10 @@ usage(void)
|
||||
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);
|
||||
|
@ -1117,7 +1117,7 @@ static void edns_ede_encode_encodedecode(struct query_info* qinfo,
|
||||
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,
|
||||
|
19
testdata/cookie_file.tdir/cookie_file.conf
vendored
Normal file
19
testdata/cookie_file.tdir/cookie_file.conf
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
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
|
16
testdata/cookie_file.tdir/cookie_file.dsc
vendored
Normal file
16
testdata/cookie_file.tdir/cookie_file.dsc
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
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:
|
10
testdata/cookie_file.tdir/cookie_file.post
vendored
Normal file
10
testdata/cookie_file.tdir/cookie_file.post
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
# #-- 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
|
24
testdata/cookie_file.tdir/cookie_file.pre
vendored
Normal file
24
testdata/cookie_file.tdir/cookie_file.pre
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# #-- 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
|
248
testdata/cookie_file.tdir/cookie_file.test
vendored
Normal file
248
testdata/cookie_file.tdir/cookie_file.test
vendored
Normal file
@ -0,0 +1,248 @@
|
||||
# #-- 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
|
@ -387,6 +387,7 @@ config_create(void)
|
||||
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;
|
||||
@ -839,6 +840,8 @@ int config_set_option(struct config_file* cfg, const char* opt,
|
||||
{ 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)
|
||||
@ -1336,6 +1339,8 @@ config_get_option(struct config_file* cfg, const char* opt,
|
||||
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)
|
||||
@ -1721,6 +1726,7 @@ config_delete(struct config_file* cfg)
|
||||
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);
|
||||
|
@ -750,6 +750,8 @@ struct config_file {
|
||||
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
|
||||
|
@ -582,6 +582,7 @@ udp-upstream-without-downstream{COLON} { YDVAR(1, VAR_UDP_UPSTREAM_WITHOUT_DOWNS
|
||||
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 ) }
|
||||
|
@ -205,6 +205,7 @@ extern struct config_parser_state* cfg_parser;
|
||||
%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 ;
|
||||
@ -342,7 +343,7 @@ content_server: server_num_threads | server_verbosity | server_port |
|
||||
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
|
||||
{
|
||||
@ -3998,6 +3999,13 @@ server_cookie_secret: VAR_COOKIE_SECRET STRING_ARG
|
||||
free($2);
|
||||
}
|
||||
;
|
||||
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;
|
||||
}
|
||||
;
|
||||
ipsetstart: VAR_IPSET
|
||||
{
|
||||
OUTYY(("\nP(ipset:)\n"));
|
||||
|
@ -947,7 +947,8 @@ parse_packet(sldns_buffer* pkt, struct msg_parse* msg, struct regional* region)
|
||||
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
|
||||
@ -1070,13 +1071,24 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len,
|
||||
&((struct sockaddr_in6*)&repinfo->remote_addr)->sin6_addr, 16);
|
||||
}
|
||||
|
||||
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(
|
||||
@ -1093,12 +1105,28 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len,
|
||||
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:
|
||||
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)) {
|
||||
@ -1240,7 +1268,8 @@ skip_pkt_rrs(sldns_buffer* pkt, int num)
|
||||
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;
|
||||
@ -1286,7 +1315,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, cookie_secrets);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -73,6 +73,7 @@ struct edns_option;
|
||||
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
|
||||
@ -322,12 +323,14 @@ int skip_pkt_rrs(struct sldns_buffer* pkt, int num);
|
||||
* @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.
|
||||
|
186
util/edns.c
186
util/edns.c
@ -187,3 +187,189 @@ edns_cookie_server_validate(const uint8_t* cookie, size_t cookie_len,
|
||||
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;
|
||||
}
|
||||
|
85
util/edns.h
85
util/edns.h
@ -43,6 +43,7 @@
|
||||
#define UTIL_EDNS_H
|
||||
|
||||
#include "util/storage/dnstree.h"
|
||||
#include "util/locks.h"
|
||||
|
||||
struct edns_data;
|
||||
struct config_file;
|
||||
@ -75,6 +76,31 @@ struct edns_string_addr {
|
||||
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,
|
||||
@ -165,4 +191,63 @@ enum edns_cookie_val_status 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);
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
@ -47,6 +47,7 @@
|
||||
#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"
|
||||
@ -1871,3 +1872,42 @@ sock_close(int socket)
|
||||
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;
|
||||
}
|
||||
|
@ -572,4 +572,13 @@ char* sock_strerror(int errn);
|
||||
/** 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 */
|
||||
|
Loading…
Reference in New Issue
Block a user