Commit ee415867 authored by Simon Kelley's avatar Simon Kelley

Use DS records as trust anchors, not DNSKEYs.

This allows us to query for the root zone DNSKEY RRset and validate
it, thus automatically handling KSK rollover.
parent 83349b8a
...@@ -13,7 +13,10 @@ Dnsmasq accepts DNS queries and either answers them from a small, local, ...@@ -13,7 +13,10 @@ Dnsmasq accepts DNS queries and either answers them from a small, local,
cache or forwards them to a real, recursive, DNS server. It loads the cache or forwards them to a real, recursive, DNS server. It loads the
contents of /etc/hosts so that local hostnames contents of /etc/hosts so that local hostnames
which do not appear in the global DNS can be resolved and also answers which do not appear in the global DNS can be resolved and also answers
DNS queries for DHCP configured hosts. It can also act as the authoritative DNS server for one or more domains, allowing local names to appear in the global DNS. DNS queries for DHCP configured hosts. It can also act as the
authoritative DNS server for one or more domains, allowing local names
to appear in the global DNS. It can be configured to do DNSSEC
validation.
.PP .PP
The dnsmasq DHCP server supports static address assignments and multiple The dnsmasq DHCP server supports static address assignments and multiple
networks. It automatically networks. It automatically
...@@ -587,12 +590,15 @@ validation by clients more efficient. Note that validation by clients is the mos ...@@ -587,12 +590,15 @@ validation by clients more efficient. Note that validation by clients is the mos
clients unable to do validation, use of the AD bit set by dnsmasq is useful, provided that the network between clients unable to do validation, use of the AD bit set by dnsmasq is useful, provided that the network between
the dnsmasq server and the client is trusted. Dnsmasq must be compiled with HAVE_DNSSEC enabled, and DNSSEC the dnsmasq server and the client is trusted. Dnsmasq must be compiled with HAVE_DNSSEC enabled, and DNSSEC
trust anchors provided, see trust anchors provided, see
.B --dnsskey. .B --trust-anchor.
Because the DNSSEC validation process uses the cache, it is not permitted to reduce the cache size below the default when DNSSEC is enabled. Because the DNSSEC validation process uses the cache, it is not permitted to reduce the cache size below the default when DNSSEC is enabled.
.TP .TP
.B --dnskey=[<class>],<domain>,<flags>,<algorithm>,<base64-key> .B --trust-anchor=[<class>],<domain>,<key-tag>,<algorithm>,<digest-type>,<digest>
Provide DNSKEY records to act a trust anchors for DNSSEC validation. Typically these will be the keys for root zone, Provide DS records to act a trust anchors for DNSSEC
but trust anchors for limited domains are also possible. validation. Typically these will be the DS record(s) for Zone Signing
key(s) of the root zone,
but trust anchors for limited domains are also possible. The current
root-zone trust anchors may be donwloaded from https://data.iana.org/root-anchors/root-anchors.xml
.TP .TP
.B --proxy-dnssec .B --proxy-dnssec
Copy the DNSSEC Authenticated Data bit from upstream servers to downstream clients and cache it. This is an Copy the DNSSEC Authenticated Data bit from upstream servers to downstream clients and cache it. This is an
...@@ -601,7 +607,10 @@ dnsmasq and the upstream servers, and the trustworthiness of the upstream server ...@@ -601,7 +607,10 @@ dnsmasq and the upstream servers, and the trustworthiness of the upstream server
.TP .TP
.B --dnssec-debug .B --dnssec-debug
Set debugging mode for the DNSSEC validation, set the Checking Disabled bit on upstream queries, Set debugging mode for the DNSSEC validation, set the Checking Disabled bit on upstream queries,
and don't convert BOGUS replies to SERVFAIL responses. and don't convert replies which do not validate to responses with
a return code of SERVFAIL. Note that
setting this may affect DNS behaviour in bad ways, it is not an
extra-logging flag and should not be set in production.
.TP .TP
.B --auth-zone=<domain>[,<subnet>[/<prefix length>][,<subnet>[/<prefix length>].....]] .B --auth-zone=<domain>[,<subnet>[/<prefix length>][,<subnet>[/<prefix length>].....]]
Define a DNS zone for which dnsmasq acts as authoritative server. Locally defined DNS records which are in the domain Define a DNS zone for which dnsmasq acts as authoritative server. Locally defined DNS records which are in the domain
......
...@@ -985,7 +985,7 @@ void cache_reload(void) ...@@ -985,7 +985,7 @@ void cache_reload(void)
struct cname *a; struct cname *a;
struct interface_name *intr; struct interface_name *intr;
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
struct dnskey *key; struct ds_config *ds;
#endif #endif
cache_inserted = cache_live_freed = 0; cache_inserted = cache_live_freed = 0;
...@@ -1031,17 +1031,17 @@ void cache_reload(void) ...@@ -1031,17 +1031,17 @@ void cache_reload(void)
} }
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
for (key = daemon->dnskeys; key; key = key->next) for (ds = daemon->ds; ds; ds = ds->next)
if ((cache = whine_malloc(sizeof(struct crec))) && if ((cache = whine_malloc(sizeof(struct crec))) &&
(cache->addr.key.keydata = blockdata_alloc(key->key, key->keylen))) (cache->addr.ds.keydata = blockdata_alloc(ds->digest, ds->digestlen)))
{ {
cache->flags = F_FORWARD | F_IMMORTAL | F_DNSKEY | F_CONFIG | F_NAMEP; cache->flags = F_FORWARD | F_IMMORTAL | F_DS | F_CONFIG | F_NAMEP;
cache->name.namep = key->name; cache->name.namep = ds->name;
cache->addr.key.keylen = key->keylen; cache->addr.ds.keylen = ds->digestlen;
cache->addr.key.algo = key->algo; cache->addr.ds.algo = ds->algo;
cache->addr.key.flags = key->flags; cache->addr.ds.keytag = ds->keytag;
cache->addr.key.keytag = dnskey_keytag(key->algo, key->flags, (unsigned char *)key->key, key->keylen); cache->addr.ds.digest = ds->digest_type;
cache->uid = key->class; cache->uid = ds->class;
cache_hash(cache); cache_hash(cache);
} }
#endif #endif
......
...@@ -144,7 +144,7 @@ int main (int argc, char **argv) ...@@ -144,7 +144,7 @@ int main (int argc, char **argv)
if (option_bool(OPT_DNSSEC_VALID)) if (option_bool(OPT_DNSSEC_VALID))
{ {
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
if (!daemon->dnskeys) if (!daemon->ds)
die(_("No trust anchors provided for DNSSEC"), NULL, EC_BADCONF); die(_("No trust anchors provided for DNSSEC"), NULL, EC_BADCONF);
if (daemon->cachesize < CACHESIZ) if (daemon->cachesize < CACHESIZ)
......
...@@ -295,10 +295,10 @@ struct cname { ...@@ -295,10 +295,10 @@ struct cname {
struct cname *next; struct cname *next;
}; };
struct dnskey { struct ds_config {
char *name, *key; char *name, *digest;
int keylen, class, algo, flags; int digestlen, class, algo, keytag, digest_type;
struct dnskey *next; struct ds_config *next;
}; };
#define ADDRLIST_LITERAL 1 #define ADDRLIST_LITERAL 1
...@@ -930,7 +930,7 @@ extern struct daemon { ...@@ -930,7 +930,7 @@ extern struct daemon {
struct prefix_class *prefix_classes; struct prefix_class *prefix_classes;
#endif #endif
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
struct dnskey *dnskeys; struct ds_config *ds;
#endif #endif
/* globally used stuff for DNS */ /* globally used stuff for DNS */
...@@ -1107,9 +1107,6 @@ void prettyprint_time(char *buf, unsigned int t); ...@@ -1107,9 +1107,6 @@ void prettyprint_time(char *buf, unsigned int t);
int prettyprint_addr(union mysockaddr *addr, char *buf); int prettyprint_addr(union mysockaddr *addr, char *buf);
int parse_hex(char *in, unsigned char *out, int maxlen, int parse_hex(char *in, unsigned char *out, int maxlen,
unsigned int *wildcard_mask, int *mac_type); unsigned int *wildcard_mask, int *mac_type);
#ifdef HAVE_DNSSEC
int parse_base64(char *in, char *out);
#endif
int memcmp_masked(unsigned char *a, unsigned char *b, int len, int memcmp_masked(unsigned char *a, unsigned char *b, int len,
unsigned int mask); unsigned int mask);
int expand_buf(struct iovec *iov, size_t size); int expand_buf(struct iovec *iov, size_t size);
......
...@@ -139,7 +139,7 @@ struct myoption { ...@@ -139,7 +139,7 @@ struct myoption {
#define LOPT_QUIET_DHCP6 327 #define LOPT_QUIET_DHCP6 327
#define LOPT_QUIET_RA 328 #define LOPT_QUIET_RA 328
#define LOPT_SEC_VALID 329 #define LOPT_SEC_VALID 329
#define LOPT_DNSKEY 330 #define LOPT_TRUST_ANCHOR 330
#define LOPT_DNSSEC_DEBUG 331 #define LOPT_DNSSEC_DEBUG 331
#ifdef HAVE_GETOPT_LONG #ifdef HAVE_GETOPT_LONG
...@@ -277,7 +277,7 @@ static const struct myoption opts[] = ...@@ -277,7 +277,7 @@ static const struct myoption opts[] =
{ "ipset", 1, 0, LOPT_IPSET }, { "ipset", 1, 0, LOPT_IPSET },
{ "synth-domain", 1, 0, LOPT_SYNTH }, { "synth-domain", 1, 0, LOPT_SYNTH },
{ "dnssec", 0, 0, LOPT_SEC_VALID }, { "dnssec", 0, 0, LOPT_SEC_VALID },
{ "dnskey", 1, 0, LOPT_DNSKEY }, { "trust-anchor", 1, 0, LOPT_TRUST_ANCHOR },
{ "dnssec-debug", 0, 0, LOPT_DNSSEC_DEBUG }, { "dnssec-debug", 0, 0, LOPT_DNSSEC_DEBUG },
#ifdef OPTION6_PREFIX_CLASS #ifdef OPTION6_PREFIX_CLASS
{ "dhcp-prefix-class", 1, 0, LOPT_PREF_CLSS }, { "dhcp-prefix-class", 1, 0, LOPT_PREF_CLSS },
...@@ -430,7 +430,7 @@ static struct { ...@@ -430,7 +430,7 @@ static struct {
{ LOPT_IPSET, ARG_DUP, "/<domain>/<ipset>[,<ipset>...]", gettext_noop("Specify ipsets to which matching domains should be added"), NULL }, { LOPT_IPSET, ARG_DUP, "/<domain>/<ipset>[,<ipset>...]", gettext_noop("Specify ipsets to which matching domains should be added"), NULL },
{ LOPT_SYNTH, ARG_DUP, "<domain>,<range>,[<prefix>]", gettext_noop("Specify a domain and address range for synthesised names"), NULL }, { LOPT_SYNTH, ARG_DUP, "<domain>,<range>,[<prefix>]", gettext_noop("Specify a domain and address range for synthesised names"), NULL },
{ LOPT_SEC_VALID, OPT_DNSSEC_VALID, NULL, gettext_noop("Activate DNSSEC validation"), NULL }, { LOPT_SEC_VALID, OPT_DNSSEC_VALID, NULL, gettext_noop("Activate DNSSEC validation"), NULL },
{ LOPT_DNSKEY, ARG_DUP, "<domain>,<algo>,<key>", gettext_noop("Specify trust anchor DNSKEY"), NULL }, { LOPT_TRUST_ANCHOR, ARG_DUP, "<domain>,[<class>],...", gettext_noop("Specify trust anchor key digest."), NULL },
{ LOPT_DNSSEC_DEBUG, OPT_DNSSEC_DEBUG, NULL, gettext_noop("Disable upstream checking for DNSSEC debugging."), NULL }, { LOPT_DNSSEC_DEBUG, OPT_DNSSEC_DEBUG, NULL, gettext_noop("Disable upstream checking for DNSSEC debugging."), NULL },
#ifdef OPTION6_PREFIX_CLASS #ifdef OPTION6_PREFIX_CLASS
{ LOPT_PREF_CLSS, ARG_DUP, "set:tag,<class>", gettext_noop("Specify DHCPv6 prefix class"), NULL }, { LOPT_PREF_CLSS, ARG_DUP, "set:tag,<class>", gettext_noop("Specify DHCPv6 prefix class"), NULL },
...@@ -590,6 +590,16 @@ static int atoi_check16(char *a, int *res) ...@@ -590,6 +590,16 @@ static int atoi_check16(char *a, int *res)
return 1; return 1;
} }
static int atoi_check8(char *a, int *res)
{
if (!(atoi_check(a, res)) ||
*res < 0 ||
*res > 0xff)
return 0;
return 1;
}
static void add_txt(char *name, char *txt) static void add_txt(char *name, char *txt)
{ {
...@@ -3675,10 +3685,11 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma ...@@ -3675,10 +3685,11 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
} }
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
case LOPT_DNSKEY: case LOPT_TRUST_ANCHOR:
{ {
struct dnskey *new = opt_malloc(sizeof(struct dnskey)); struct ds_config *new = opt_malloc(sizeof(struct ds_config));
char *key64, *algo = NULL; char *cp, *cp1, *keyhex, *digest, *algo = NULL;
int len;
new->class = C_IN; new->class = C_IN;
...@@ -3700,20 +3711,30 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma ...@@ -3700,20 +3711,30 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
} }
} }
if (!comma || !algo || !(key64 = split(algo)) || if (!comma || !algo || !(digest = split(algo)) || !(keyhex = split(digest)) ||
!atoi_check16(comma, &new->flags) || !atoi_check16(algo, &new->algo) || !atoi_check16(comma, &new->keytag) ||
!atoi_check8(algo, &new->algo) ||
!atoi_check8(digest, &new->digest_type) ||
!(new->name = canonicalise_opt(arg))) !(new->name = canonicalise_opt(arg)))
ret_err(_("bad DNSKEY")); ret_err(_("bad trust anchor"));
/* Upper bound on length */ /* Upper bound on length */
new->key = opt_malloc((3*strlen(key64)/4)+1); len = (2*strlen(keyhex))+1;
unhide_metas(key64); new->digest = opt_malloc(len);
if ((new->keylen = parse_base64(key64, new->key)) == -1) unhide_metas(keyhex);
ret_err(_("bad base64 in DNSKEY")); /* 4034: "Whitespace is allowed within digits" */
for (cp = keyhex; *cp; )
if (isspace(*cp))
for (cp1 = cp; *cp1; cp1++)
*cp1 = *(cp1+1);
else
cp++;
if ((new->digestlen = parse_hex(keyhex, (unsigned char *)new->digest, len, NULL, NULL)) == -1)
ret_err(_("bad HEX in trust anchor"));
new->next = daemon->ds;
daemon->ds = new;
new->next = daemon->dnskeys;
daemon->dnskeys = new;
break; break;
} }
#endif #endif
......
...@@ -1599,20 +1599,17 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, ...@@ -1599,20 +1599,17 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
while ((crecp = cache_find_by_name(crecp, name, now, F_DNSKEY))) while ((crecp = cache_find_by_name(crecp, name, now, F_DNSKEY)))
if (crecp->uid == qclass) if (crecp->uid == qclass)
{ {
if (!(crecp->flags & F_CONFIG)) /* Don't return configured keys - send upstream instead */ gotone = 1;
{ if (!dryrun && (keydata = blockdata_retrieve(crecp->addr.key.keydata, crecp->addr.key.keylen, NULL)))
gotone = 1; {
if (!dryrun && (keydata = blockdata_retrieve(crecp->addr.key.keydata, crecp->addr.key.keylen, NULL))) struct all_addr a;
{ a.addr.keytag = crecp->addr.key.keytag;
struct all_addr a; log_query(F_KEYTAG | (crecp->flags & F_CONFIG), name, &a, "DNSKEY keytag %u");
a.addr.keytag = crecp->addr.key.keytag; if (add_resource_record(header, limit, &trunc, nameoffset, &ansp,
log_query(F_KEYTAG | (crecp->flags & F_CONFIG), name, &a, "DNSKEY keytag %u"); crec_ttl(crecp, now), &nameoffset,
if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, T_DNSKEY, qclass, "sbbt",
crec_ttl(crecp, now), &nameoffset, crecp->addr.key.flags, 3, crecp->addr.key.algo, crecp->addr.key.keylen, keydata))
T_DNSKEY, qclass, "sbbt", anscount++;
crecp->addr.key.flags, 3, crecp->addr.key.algo, crecp->addr.key.keylen, keydata))
anscount++;
}
} }
} }
} }
......
...@@ -482,66 +482,6 @@ int parse_hex(char *in, unsigned char *out, int maxlen, ...@@ -482,66 +482,6 @@ int parse_hex(char *in, unsigned char *out, int maxlen,
return i; return i;
} }
#ifdef HAVE_DNSSEC
static int charval(char c)
{
if (c >= 'A' && c <= 'Z')
return c - 'A';
if (c >= 'a' && c <= 'z')
return c - 'a' + 26;
if (c >= '0' && c <= '9')
return c - '0' + 52;
if (c == '+')
return 62;
if (c == '/')
return 63;
if (c == '=')
return -1;
return -2;
}
int parse_base64(char *in, char *out)
{
char *p = out;
int i, val[4];
while (*in)
{
for (i = 0; i < 4; i++)
{
while (*in == ' ')
in++;
if (*in == 0)
return -1;
if ((val[i] = charval(*in++)) == -2)
return -1;
}
while (*in == ' ')
in++;
if (val[1] == -1)
return -1; /* too much padding */
*p++ = (val[0] << 2) | (val[1] >> 4);
if (val[2] != -1)
*p++ = (val[1] << 4) | ( val[2] >> 2);
if (val[3] != -1)
*p++ = (val[2] << 6) | val[3];
}
return p - out;
}
#endif
/* return 0 for no match, or (no matched octets) + 1 */ /* return 0 for no match, or (no matched octets) + 1 */
int memcmp_masked(unsigned char *a, unsigned char *b, int len, unsigned int mask) int memcmp_masked(unsigned char *a, unsigned char *b, int len, unsigned int mask)
{ {
......
# The root DNSSEC trust anchors, valid as at 30/01/2014 # The root DNSSEC trust anchor, valid as at 30/01/2014
# Note that this is a DS record (ie a hash of the root Zone Signing Key)
# If was downloaded from https://data.iana.org/root-anchors/root-anchors.xml
trust-anchor=.,19036,8,2,49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5
dnskey=.,257,8,AwEAAagAIKlVZrpC6Ia7gEzahOR+9W29euxhJhVVLOyQbSEW0O8gcCjF FVQUTf6v58fLjwBd0YI0EzrAcQqBGCzh/RStIoO8g0NfnfL2MTJRkxoX bfDaUeVPQuYEhg37NZWAJQ9VnMVDxP/VHL496M/QZxkjf5/Efucp2gaD X6RS6CXpoY68LsvPVjR0ZSwzz1apAzvN9dlzEheX7ICJBBtuA6G3LQpz W5hOA2hzCTMjJPJ8LbqF6dsV6DoBQzgul0sGIcGOYl7OyQdXfZ57relS Qageu+ipAdTTJ25AsRTAoub8ONGcLmqrAmRLKBP1dfwhYB4N7knNnulq QxA+Uk1ihz0=
dnskey=.,256,8,AwEAAb8sU6pbYMWRbkRnEuEZw9NSir707TkOcF+UL1XiK4NDJOvXRyX1 95Am5dQ7bRnnuySZ3daf37vvjUUhuIWUAQ4stht8nJfYxVQXDYjSpGH5 I6Hf/0CZEoNP6cNvrQ7AFmKkmv00xWExKQjbvnRPI4bqpMwtHVzn6Wyb BZ6kuqED
dnskey=.,256,8,AwEAAYRU41/8smgAvuSojEP4jaj5Yll7WPaUKpYvnz2pnX2VIvRn4jsy Jns80bloenG6X9ebJVy2CFtZQLKHP8DcKmIFotdgs2HolyocY1am/+33 4RtzusM2ojkhjn1FRGtuSE9s2TSz1ISv0yVnFyu+EP/ZkiWnDfWeVrJI SEWBEr4V
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment