Update NSEC3 iterations handling to conform with RFC 9276.
Signed-off-by: DL6ER <dl6er@dl6er.de>
This commit is contained in:
parent
bf17dd3c04
commit
70b0431919
|
@ -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))
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue