Initial implementation of RFC-8914 extended DNS errors.

This commit is contained in:
Simon Kelley
2021-06-26 00:38:01 +01:00
parent 11c52d032b
commit 6261aba026
7 changed files with 259 additions and 83 deletions

View File

@@ -215,14 +215,6 @@ static int is_check_date(unsigned long curtime)
return !daemon->dnssec_no_time_check;
}
/* Check whether today/now is between date_start and date_end */
static int check_date_range(unsigned long curtime, u32 date_start, u32 date_end)
{
/* We must explicitly check against wanted values, because of SERIAL_UNDEF */
return serial_compare_32(curtime, date_start) == SERIAL_GT
&& serial_compare_32(curtime, date_end) == SERIAL_LT;
}
/* Return bytes of canonicalised rrdata one by one.
Init state->ip with the RR, and state->end with the end of same.
Init state->op to NULL.
@@ -534,7 +526,8 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
struct crec *crecp = NULL;
u16 *rr_desc = rrfilter_desc(type);
u32 sig_expiration, sig_inception;
int failflags = DNSSEC_FAIL_NOSIG | DNSSEC_FAIL_NYV | DNSSEC_FAIL_EXP | DNSSEC_FAIL_NOKEYSUP;
unsigned long curtime = time(0);
int time_check = is_check_date(curtime);
@@ -557,6 +550,8 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
void *ctx;
char *name_start;
u32 nsigttl, ttl, orig_ttl;
failflags &= ~DNSSEC_FAIL_NOSIG;
p = sigs[j];
GETLONG(ttl, p);
@@ -574,12 +569,31 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
if (!extract_name(header, plen, &p, keyname, 1, 0))
return STAT_BOGUS;
if ((time_check && !check_date_range(curtime, sig_inception, sig_expiration)) ||
labels > name_labels ||
!(hash = hash_find(algo_digest_name(algo))) ||
if (!time_check)
failflags &= ~(DNSSEC_FAIL_NYV | DNSSEC_FAIL_EXP);
else
{
/* We must explicitly check against wanted values, because of SERIAL_UNDEF */
if (serial_compare_32(curtime, sig_inception) == SERIAL_LT)
continue;
else
failflags &= ~DNSSEC_FAIL_NYV;
if (serial_compare_32(curtime, sig_expiration) == SERIAL_GT)
continue;
else
failflags &= ~DNSSEC_FAIL_EXP;
}
if (!(hash = hash_find(algo_digest_name(algo))))
continue;
else
failflags &= ~DNSSEC_FAIL_NOKEYSUP;
if (labels > name_labels ||
!hash_init(hash, &ctx, &digest))
continue;
/* OK, we have the signature record, see if the relevant DNSKEY is in the cache. */
if (!key && !(crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY)))
return STAT_NEED_KEY;
@@ -730,7 +744,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
}
}
return STAT_BOGUS;
return STAT_BOGUS | failflags;
}
@@ -751,17 +765,18 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
unsigned long ttl, sig_ttl;
struct blockdata *key;
union all_addr a;
int failflags = DNSSEC_FAIL_NOSIG | DNSSEC_FAIL_NODSSUP | DNSSEC_FAIL_NOZONE | DNSSEC_FAIL_NOKEY;
if (ntohs(header->qdcount) != 1 ||
RCODE(header) == SERVFAIL || RCODE(header) == REFUSED ||
!extract_name(header, plen, &p, name, 1, 4))
return STAT_BOGUS;
return STAT_BOGUS | DNSSEC_FAIL_NOKEY;
GETSHORT(qtype, p);
GETSHORT(qclass, p);
if (qtype != T_DNSKEY || qclass != class || ntohs(header->ancount) == 0)
return STAT_BOGUS;
return STAT_BOGUS | DNSSEC_FAIL_NOKEY;
/* See if we have cached a DS record which validates this key */
if (!(crecp = cache_find_by_name(NULL, name, now, F_DS)))
@@ -795,14 +810,17 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
GETSHORT(flags, p);
if (*p++ != 3)
return STAT_BOGUS;
return STAT_BOGUS | DNSSEC_FAIL_NOKEY;
algo = *p++;
keytag = dnskey_keytag(algo, flags, p, rdlen - 4);
key = NULL;
/* key must have zone key flag set */
if (flags & 0x100)
key = blockdata_alloc((char*)p, rdlen - 4);
{
key = blockdata_alloc((char*)p, rdlen - 4);
failflags &= ~DNSSEC_FAIL_NOZONE;
}
p = psave;
@@ -823,15 +841,23 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
unsigned char *digest, *ds_digest;
const struct nettle_hash *hash;
int sigcnt, rrcnt;
int wire_len;
if (recp1->addr.ds.algo == algo &&
recp1->addr.ds.keytag == keytag &&
recp1->uid == (unsigned int)class &&
(hash = hash_find(ds_digest_name(recp1->addr.ds.digest))) &&
hash_init(hash, &ctx, &digest))
recp1->uid == (unsigned int)class)
{
int wire_len = to_wire(name);
failflags &= ~DNSSEC_FAIL_NOKEY;
if (!(hash = hash_find(ds_digest_name(recp1->addr.ds.digest))))
continue;
else
failflags &= ~DNSSEC_FAIL_NODSSUP;
if (!hash_init(hash, &ctx, &digest))
continue;
wire_len = to_wire(name);
/* Note that digest may be different between DSs, so
we can't move this outside the loop. */
@@ -846,13 +872,23 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
(ds_digest = blockdata_retrieve(recp1->addr.ds.keydata, recp1->addr.ds.keylen, NULL)) &&
memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0 &&
explore_rrset(header, plen, class, T_DNSKEY, name, keyname, &sigcnt, &rrcnt) &&
sigcnt != 0 && rrcnt != 0 &&
STAT_ISEQUAL(validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname,
NULL, key, rdlen - 4, algo, keytag, &sig_ttl),
STAT_SECURE))
rrcnt != 0)
{
valid = 1;
break;
if (sigcnt == 0)
continue;
else
failflags &= ~DNSSEC_FAIL_NOSIG;
rc = validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname,
NULL, key, rdlen - 4, algo, keytag, &sig_ttl);
failflags &= rc;
if (STAT_ISEQUAL(rc, STAT_SECURE))
{
valid = 1;
break;
}
}
}
}
@@ -937,7 +973,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
}
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DNSKEY");
return STAT_BOGUS;
return STAT_BOGUS | failflags;
}
/* The DNS packet is expected to contain the answer to a DS query
@@ -975,7 +1011,8 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
if (STAT_ISEQUAL(rc, STAT_INSECURE))
{
my_syslog(LOG_WARNING, _("Insecure DS reply received for %s, check domain configuration and upstream DNS server DNSSEC support"), name);
rc = STAT_BOGUS;
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS - not secure");
return STAT_BOGUS | DNSSEC_FAIL_INDET;
}
p = (unsigned char *)(header+1);
@@ -985,7 +1022,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
/* If the key needed to validate the DS is on the same domain as the DS, we'll
loop getting nowhere. Stop that now. This can happen of the DS answer comes
from the DS's zone, and not the parent zone. */
if (STAT_ISEQUAL(rc, STAT_BOGUS) || (STAT_ISEQUAL(rc, STAT_NEED_KEY) && hostname_isequal(name, keyname)))
if (STAT_ISEQUAL(rc, STAT_NEED_KEY) && hostname_isequal(name, keyname))
{
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS");
return STAT_BOGUS;
@@ -1457,7 +1494,7 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
if (!(p = skip_name(nsecs[i], header, plen, 15)))
return 0; /* bad packet */
p += 10; /* type, class, TTL, rdlen */
p += 10; /* type, class, TTL, rdlen */
algo = *p++;
if ((hash = hash_find(nsec3_digest_name(algo))))
@@ -1961,11 +1998,11 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
{
rc = zone_status(name, class1, keyname, now);
if (STAT_ISEQUAL(rc, STAT_SECURE))
rc = STAT_BOGUS;
rc = STAT_BOGUS | DNSSEC_FAIL_NOSIG;
if (class)
*class = class1; /* Class for NEED_DS or NEED_KEY */
}
else
else
rc = STAT_INSECURE;
if (!STAT_ISEQUAL(rc, STAT_INSECURE))
@@ -2028,7 +2065,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
we'll return BOGUS then. */
if (STAT_ISEQUAL(rc, STAT_SECURE_WILDCARD) &&
!prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL, NULL))
return STAT_BOGUS;
return STAT_BOGUS | DNSSEC_FAIL_NONSEC;
rc = STAT_SECURE;
}
@@ -2058,7 +2095,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
{
/* Empty DS without NSECS */
if (qtype == T_DS)
return STAT_BOGUS;
return STAT_BOGUS | DNSSEC_FAIL_NONSEC;
if (STAT_ISEQUAL((rc = zone_status(name, qclass, keyname, now)), STAT_SECURE))
{
@@ -2067,7 +2104,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
return rc;
}
return STAT_BOGUS; /* signed zone, no NSECs */
return STAT_BOGUS | DNSSEC_FAIL_NONSEC; /* signed zone, no NSECs */
}
}
@@ -2131,4 +2168,31 @@ size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char
return ret;
}
int errflags_to_ede(int status)
{
/* We can end up with more than one flag set for some errors,
so this encodes a rough priority so the (eg) No sig is reported
before no-unexpired-sig. */
if (status & DNSSEC_FAIL_NYV)
return EDE_SIG_NYV;
else if (status & DNSSEC_FAIL_EXP)
return EDE_SIG_EXP;
else if (status & DNSSEC_FAIL_NOKEYSUP)
return EDE_USUPDNSKEY;
else if (status & DNSSEC_FAIL_NOZONE)
return EDE_NO_ZONEKEY;
else if (status & DNSSEC_FAIL_NOKEY)
return EDE_NO_DNSKEY;
else if (status & DNSSEC_FAIL_NODSSUP)
return EDE_USUPDS;
else if (status & DNSSEC_FAIL_NONSEC)
return EDE_NO_NSEC;
else if (status & DNSSEC_FAIL_INDET)
return EDE_DNSSEC_IND;
else if (status & DNSSEC_FAIL_NOSIG)
return EDE_NO_RRSIG;
else
return -1;
}
#endif /* HAVE_DNSSEC */