Update NSEC3 iterations handling to conform with RFC 9276.

Signed-off-by: DL6ER <dl6er@dl6er.de>
This commit is contained in:
Simon Kelley 2023-12-31 23:28:11 +00:00 committed by DL6ER
parent bf17dd3c04
commit 70b0431919
No known key found for this signature in database
GPG Key ID: 00135ACBD90B28DD
2 changed files with 59 additions and 52 deletions

View File

@ -763,6 +763,8 @@ struct dyndir {
#define DNSSEC_FAIL_NONSEC 0x0040 /* No NSEC */
#define DNSSEC_FAIL_NODSSUP 0x0080 /* no supported DS algo. */
#define DNSSEC_FAIL_NOKEY 0x0100 /* no DNSKEY */
#define DNSSEC_FAIL_NSEC3_ITERS 0x0200 /* too many iterations in NSEC3 */
#define DNSSEC_FAIL_BADPACKET 0x0400 /* bad packet */
#define STAT_ISEQUAL(a, b) (((a) & 0xffff0000) == (b))

View File

@ -1179,6 +1179,7 @@ static int hostname_cmp(const char *a, const char *b)
}
}
/* returns 0 on success, or DNSSEC_FAIL_* value on failure. */
static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, unsigned char **labels, int nsec_count,
char *workspace1_in, char *workspace2, char *name, int type, int *nons)
{
@ -1203,7 +1204,7 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
GETSHORT(rdlen, p);
psave = p;
if (!extract_name(header, plen, &p, workspace2, 1, 10))
return 0;
return DNSSEC_FAIL_BADPACKET;
/* If NSEC comes from wildcard expansion, use original wildcard
as name for computation. */
@ -1231,7 +1232,7 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
{
/* 4035 para 5.4. Last sentence */
if (type == T_NSEC || type == T_RRSIG)
return 1;
return 0;
/* NSEC with the same name as the RR we're testing, check
that the type in question doesn't appear in the type map */
@ -1247,25 +1248,25 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
/* A CNAME answer would also be valid, so if there's a CNAME is should
have been returned. */
if ((p[2] & (0x80 >> T_CNAME)) != 0)
return 0;
return DNSSEC_FAIL_NONSEC;
/* If the SOA bit is set for a DS record, then we have the
DS from the wrong side of the delegation. For the root DS,
this is expected. */
if (name_labels != 0 && type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0)
return 0;
return DNSSEC_FAIL_NONSEC;
}
while (rdlen >= 2)
{
if (!CHECK_LEN(header, p, plen, rdlen))
return 0;
return DNSSEC_FAIL_BADPACKET;
if (p[0] == type >> 8)
{
/* Does the NSEC say our type exists? */
if (offset < p[1] && (p[offset+2] & mask) != 0)
return 0;
return DNSSEC_FAIL_NONSEC;
break; /* finished checking */
}
@ -1281,17 +1282,17 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
/* Normal case, name falls between NSEC name and next domain name,
wrap around case, name falls between NSEC name (rc == -1) and end */
if (hostname_cmp(workspace2, name) >= 0 || hostname_cmp(workspace1, workspace2) >= 0)
return 1;
return 0;
}
else
{
/* wrap around case, name falls between start and next domain name */
if (hostname_cmp(workspace1, workspace2) >= 0 && hostname_cmp(workspace2, name) >=0 )
return 1;
return 0;
}
}
return 0;
return DNSSEC_FAIL_NONSEC;
}
/* return digest length, or zero on error */
@ -1464,6 +1465,7 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
return 0;
}
/* returns 0 on success, or DNSSEC_FAIL_* value on failure. */
static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count,
char *workspace1, char *workspace2, char *name, int type, char *wildname, int *nons)
{
@ -1485,9 +1487,9 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
for (i = 0; i < nsec_count; i++)
{
if (!(p = skip_name(nsecs[i], header, plen, 15)))
return 0; /* bad packet */
return DNSSEC_FAIL_BADPACKET; /* bad packet */
p += 10; /* type, class, TTL, rdlen */
p += 10; /* type, class, TTL, rdlen */
algo = *p++;
if ((hash = hash_find(nsec3_digest_name(algo))))
@ -1496,22 +1498,19 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
/* No usable NSEC3s */
if (i == nsec_count)
return 0;
return DNSSEC_FAIL_NONSEC;
p++; /* flags */
GETSHORT (iterations, p);
/* Upper-bound iterations, to avoid DoS.
Strictly, there are lower bounds for small keys, but
since we don't have key size info here, at least limit
to the largest bound, for 4096-bit keys. RFC 5155 10.3 */
if (iterations > 2500)
return 0;
/* Upper-bound iterations, to avoid DoS. RFC 9276 refers. */
if (iterations > 150)
return DNSSEC_FAIL_NSEC3_ITERS;
salt_len = *p++;
salt = p;
if (!CHECK_LEN(header, salt, plen, salt_len))
return 0; /* bad packet */
return DNSSEC_FAIL_BADPACKET; /* bad packet */
/* Now prune so we only have NSEC3 records with same iterations, salt and algo */
for (i = 0; i < nsec_count; i++)
@ -1543,7 +1542,7 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
continue;
if (!CHECK_LEN(header, p, plen, salt_len))
return 0; /* bad packet */
return DNSSEC_FAIL_BADPACKET; /* bad packet */
if (memcmp(p, salt, salt_len) != 0)
continue;
@ -1553,10 +1552,10 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
}
if ((digest_len = hash_name(name, &digest, hash, salt, salt_len, iterations)) == 0)
return 0;
return DNSSEC_FAIL_NONSEC;
if (check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, nons, count_labels(name)))
return 1;
return 0;
/* Can't find an NSEC3 which covers the name directly, we need the "closest encloser NSEC3"
or an answer inferred from a wildcard record. */
@ -1572,14 +1571,16 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
break;
if ((digest_len = hash_name(closest_encloser, &digest, hash, salt, salt_len, iterations)) == 0)
return 0;
return DNSSEC_FAIL_NONSEC;
for (i = 0; i < nsec_count; i++)
if ((p = nsecs[i]))
{
if (!extract_name(header, plen, &p, workspace1, 1, 0) ||
!(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
return 0;
if (!extract_name(header, plen, &p, workspace1, 1, 0))
return DNSSEC_FAIL_BADPACKET;
if (!(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
return DNSSEC_FAIL_NONSEC;
if (digest_len == base32_len &&
memcmp(digest, workspace2, digest_len) == 0)
@ -1594,14 +1595,14 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
while ((closest_encloser = strchr(closest_encloser, '.')));
if (!closest_encloser || !next_closest)
return 0;
return DNSSEC_FAIL_NONSEC;
/* Look for NSEC3 that proves the non-existence of the next-closest encloser */
if ((digest_len = hash_name(next_closest, &digest, hash, salt, salt_len, iterations)) == 0)
return 0;
return DNSSEC_FAIL_NONSEC;
if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL, 1))
return 0;
return DNSSEC_FAIL_NONSEC;
/* Finally, check that there's no seat of wildcard synthesis */
if (!wildname)
@ -1613,15 +1614,16 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
*wildcard = '*';
if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, iterations)) == 0)
return 0;
return DNSSEC_FAIL_NONSEC;
if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL, 1))
return 0;
return DNSSEC_FAIL_NONSEC;
}
return 1;
return 0;
}
/* returns 0 on success, or DNSSEC_FAIL_* value on failure. */
static int prove_non_existence(struct dns_header *header, size_t plen, char *keyname, char *name, int qtype, int qclass, char *wildname, int *nons, int *nsec_ttl)
{
static unsigned char **nsecset = NULL, **rrsig_labels = NULL;
@ -1634,7 +1636,7 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
/* Move to NS section */
if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen)))
return 0;
return DNSSEC_FAIL_BADPACKET;
auth_start = p;
@ -1643,7 +1645,7 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
unsigned char *pstart = p;
if (!extract_name(header, plen, &p, daemon->workspacename, 1, 10))
return 0;
return DNSSEC_FAIL_BADPACKET;
GETSHORT(type, p);
GETSHORT(class, p);
@ -1662,12 +1664,12 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
/* No mixed NSECing 'round here, thankyouverymuch */
if (type_found != 0 && type_found != type)
return 0;
return DNSSEC_FAIL_NONSEC;
type_found = type;
if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found))
return 0;
return DNSSEC_FAIL_BADPACKET;
if (type == T_NSEC)
{
@ -1682,14 +1684,14 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
int res, j, rdlen1, type1, class1;
if (!expand_workspace(&rrsig_labels, &rrsig_labels_sz, nsecs_found))
return 0;
return DNSSEC_FAIL_BADPACKET;
rrsig_labels[nsecs_found] = NULL;
for (j = ntohs(header->nscount); j != 0; j--)
{
if (!(res = extract_name(header, plen, &p1, daemon->workspacename, 0, 10)))
return 0;
return DNSSEC_FAIL_BADPACKET;
GETSHORT(type1, p1);
GETSHORT(class1, p1);
@ -1697,7 +1699,7 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
GETSHORT(rdlen1, p1);
if (!CHECK_LEN(header, p1, plen, rdlen1))
return 0;
return DNSSEC_FAIL_BADPACKET;
if (res == 1 && class1 == qclass && type1 == T_RRSIG)
{
@ -1705,7 +1707,7 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
unsigned char *psav = p1;
if (rdlen1 < 18)
return 0; /* bad packet */
return DNSSEC_FAIL_BADPACKET; /* bad packet */
GETSHORT(type_covered, p1);
@ -1717,25 +1719,25 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
if (!rrsig_labels[nsecs_found])
rrsig_labels[nsecs_found] = p1;
else if (*rrsig_labels[nsecs_found] != *p1) /* algo */
return 0;
return DNSSEC_FAIL_NONSEC;
}
p1 = psav;
}
if (!ADD_RDLEN(header, p1, plen, rdlen1))
return 0;
return DNSSEC_FAIL_BADPACKET;
}
/* Must have found at least one sig. */
if (!rrsig_labels[nsecs_found])
return 0;
return DNSSEC_FAIL_NONSEC;
}
nsecset[nsecs_found++] = pstart;
}
if (!ADD_RDLEN(header, p, plen, rdlen))
return 0;
return DNSSEC_FAIL_BADPACKET;
}
if (type_found == T_NSEC)
@ -1743,7 +1745,7 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
else if (type_found == T_NSEC3)
return prove_non_existence_nsec3(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, wildname, nons);
else
return 0;
return DNSSEC_FAIL_NONSEC;
}
/* Check signing status of name.
@ -1857,7 +1859,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
int type1, class1, rdlen1 = 0, type2, class2, rdlen2, qclass, qtype, targetidx;
int i, j, rc = STAT_INSECURE;
int secure = STAT_SECURE;
int rc_nsec;
/* extend rr_status if necessary */
if (daemon->rr_status_sz < ntohs(header->ancount) + ntohs(header->nscount))
{
@ -2059,8 +2061,8 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
That's not a problem since if the RRsets later fail
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 | DNSSEC_FAIL_NONSEC;
((rc_nsec = prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL, NULL))) != 0)
return STAT_BOGUS | rc_nsec;
rc = STAT_SECURE;
}
@ -2085,20 +2087,21 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
/* For anything other than a DS record, this situation is OK if either
the answer is in an unsigned zone, or there's a NSEC records. */
if (!prove_non_existence(header, plen, keyname, name, qtype, qclass, NULL, nons, nsec_ttl))
if ((rc_nsec = prove_non_existence(header, plen, keyname, name, qtype, qclass, NULL, nons, nsec_ttl)) != 0)
{
/* Empty DS without NSECS */
if (qtype == T_DS)
return STAT_BOGUS | DNSSEC_FAIL_NONSEC;
return STAT_BOGUS | rc_nsec;
if (!STAT_ISEQUAL((rc = zone_status(name, qclass, keyname, now)), STAT_SECURE))
if ((rc_nsec & (DNSSEC_FAIL_NONSEC | DNSSEC_FAIL_NSEC3_ITERS)) &&
!STAT_ISEQUAL((rc = zone_status(name, qclass, keyname, now)), STAT_SECURE))
{
if (class)
*class = qclass; /* Class for NEED_DS or NEED_KEY */
return rc;
}
return STAT_BOGUS | DNSSEC_FAIL_NONSEC; /* signed zone, no NSECs */
return STAT_BOGUS | rc_nsec; /* signed zone, no NSECs */
}
}
@ -2180,6 +2183,8 @@ int errflags_to_ede(int status)
return EDE_NO_DNSKEY;
else if (status & DNSSEC_FAIL_NODSSUP)
return EDE_USUPDS;
else if (status & DNSSEC_FAIL_NSEC3_ITERS)
return EDE_UNS_NS3_ITER;
else if (status & DNSSEC_FAIL_NONSEC)
return EDE_NO_NSEC;
else if (status & DNSSEC_FAIL_INDET)