summaryrefslogtreecommitdiff
path: root/src/dnssec.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/dnssec.c')
-rw-r--r--src/dnssec.c276
1 files changed, 191 insertions, 85 deletions
diff --git a/src/dnssec.c b/src/dnssec.c
index 1a1c0e4..3592691 100644
--- a/src/dnssec.c
+++ b/src/dnssec.c
@@ -496,6 +496,8 @@ static int expand_workspace(unsigned char ***wkspc, int *sz, int new)
*wkspc = p;
*sz = new_sz;
+
+ return 1;
}
/* Bubble sort the RRset into the canonical order.
@@ -588,6 +590,7 @@ static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int
Return code:
STAT_SECURE if it validates.
STAT_SECURE_WILDCARD if it validates and is the result of wildcard expansion.
+ STAT_NO_SIG no RRsigs found.
STAT_INSECURE can't validate (no RRSIG, bad packet).
STAT_BOGUS signature is wrong.
STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname)
@@ -670,9 +673,13 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
return STAT_INSECURE;
}
- /* RRset empty, no RRSIGs */
- if (rrsetidx == 0 || sigidx == 0)
+ /* RRset empty */
+ if (rrsetidx == 0)
return STAT_INSECURE;
+
+ /* no RRSIGs */
+ if (sigidx == 0)
+ return STAT_NO_SIG;
/* Sort RRset records into canonical order.
Note that at this point keyname and daemon->workspacename buffs are
@@ -1058,6 +1065,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
return codes:
STAT_INSECURE bad packet, no DS in reply, proven no DS in reply.
STAT_SECURE At least one valid DS found and in cache.
+ STAT_NO_DS It's proved there's no DS here.
STAT_BOGUS At least one DS found, which fails validation.
STAT_NEED_DNSKEY DNSKEY records to validate a DS not found, name in keyname
*/
@@ -1065,7 +1073,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
{
unsigned char *p = (unsigned char *)(header+1);
- int qtype, qclass, val, i;
+ int qtype, qclass, val, i, neganswer;
if (ntohs(header->qdcount) != 1 ||
!(p = skip_name(p, header, plen, 4)))
@@ -1077,25 +1085,36 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
if (qtype != T_DS || qclass != class)
val = STAT_BOGUS;
else
- val = dnssec_validate_reply(now, header, plen, name, keyname, NULL);
+ val = dnssec_validate_reply(now, header, plen, name, keyname, NULL, &neganswer);
+
+ if (val == STAT_NO_SIG)
+ val = STAT_INSECURE;
p = (unsigned char *)(header+1);
extract_name(header, plen, &p, name, 1, 4);
p += 4; /* qtype, qclass */
+ if (!(p = skip_section(p, ntohs(header->ancount), header, plen)))
+ return STAT_INSECURE;
+
if (val == STAT_BOGUS)
log_query(F_UPSTREAM, name, NULL, "BOGUS DS");
- /* proved that no DS exists, cache neg answer, can't validate */
- if (val == STAT_SECURE && ntohs(header->ancount) == 0)
+ if ((val == STAT_SECURE || val == STAT_INSECURE) && neganswer)
{
- int rdlen, rc;
+ int rdlen, flags = F_FORWARD | F_DS | F_NEG ;
unsigned long ttl, minttl = ULONG_MAX;
struct all_addr a;
+
+ if (RCODE(header) == NXDOMAIN)
+ flags |= F_NXDOMAIN;
+
+ if (val == STAT_SECURE)
+ flags |= F_DNSSECOK;
for (i = ntohs(header->nscount); i != 0; i--)
{
- if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
+ if (!(p = skip_name(p, header, plen, 0)))
return STAT_INSECURE;
GETSHORT(qtype, p);
@@ -1103,10 +1122,10 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
GETLONG(ttl, p);
GETSHORT(rdlen, p);
- if (!CHECK_LEN(header, p, plen, rdlen) || rdlen < 4)
+ if (!CHECK_LEN(header, p, plen, rdlen))
return STAT_INSECURE; /* bad packet */
-
- if (qclass != class || qtype != T_SOA || rc ==2)
+
+ if (qclass != class || qtype != T_SOA)
{
p += rdlen;
continue;
@@ -1126,16 +1145,21 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
GETLONG(ttl, p); /* minTTL */
if (ttl < minttl)
minttl = ttl;
+
+ break;
}
- cache_start_insert();
-
- a.addr.dnssec.class = class;
- cache_insert(name, &a, now, ttl, F_FORWARD | F_DS | F_DNSSECOK | F_NEG | (RCODE(header) == NXDOMAIN ? F_NXDOMAIN : 0));
-
- cache_end_insert();
+ if (i != 0)
+ {
+ cache_start_insert();
+
+ a.addr.dnssec.class = class;
+ cache_insert(name, &a, now, ttl, flags);
+
+ cache_end_insert();
+ }
- return STAT_INSECURE;
+ return (val == STAT_SECURE) ? STAT_NO_DS : STAT_INSECURE;
}
return val;
@@ -1624,22 +1648,76 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
/* Validate all the RRsets in the answer and authority sections of the reply (4035:3.2.3) */
/* Returns are the same as validate_rrset, plus the class if the missing key is in *class */
-int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class)
+int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class, int *neganswer)
{
- unsigned char *ans_start, *p1, *p2, **nsecs;
- int type1, class1, rdlen1, type2, class2, rdlen2;
+ unsigned char *ans_start, *qname, *p1, *p2, **nsecs;
+ int type1, class1, rdlen1, type2, class2, rdlen2, qclass, qtype;
int i, j, rc, nsec_count, cname_count = 10;
- int nsec_type = 0;
+ int nsec_type = 0, have_answer = 0;
+ if (neganswer)
+ *neganswer = 0;
+
if (RCODE(header) == SERVFAIL)
return STAT_BOGUS;
if ((RCODE(header) != NXDOMAIN && RCODE(header) != NOERROR) || ntohs(header->qdcount) != 1)
return STAT_INSECURE;
+
+ qname = p1 = (unsigned char *)(header+1);
- if (!(ans_start = skip_questions(header, plen)))
+ if (!extract_name(header, plen, &p1, name, 1, 4))
+ return STAT_INSECURE;
+
+ GETSHORT(qtype, p1);
+ GETSHORT(qclass, p1);
+ ans_start = p1;
+
+ /* Can't validate an RRISG query */
+ if (qtype == T_RRSIG)
return STAT_INSECURE;
+
+ cname_loop:
+ for (j = ntohs(header->ancount); j != 0; j--)
+ {
+ /* leave pointer to missing name in qname */
+
+ if (!(rc = extract_name(header, plen, &p1, name, 0, 10)))
+ return STAT_INSECURE; /* bad packet */
+
+ GETSHORT(type2, p1);
+ GETSHORT(class2, p1);
+ p1 += 4; /* TTL */
+ GETSHORT(rdlen2, p1);
+
+ if (rc == 1 && qclass == class2)
+ {
+ /* Do we have an answer for the question? */
+ if (type2 == qtype)
+ {
+ have_answer = 1;
+ break;
+ }
+ else if (type2 == T_CNAME)
+ {
+ qname = p1;
+
+ /* looped CNAMES */
+ if (!cname_count-- || !extract_name(header, plen, &p1, name, 1, 0))
+ return STAT_INSECURE;
+
+ p1 = ans_start;
+ goto cname_loop;
+ }
+ }
+
+ if (!ADD_RDLEN(header, p1, plen, rdlen2))
+ return STAT_INSECURE;
+ }
+ if (neganswer && !have_answer)
+ *neganswer = 1;
+
for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++)
{
if (!extract_name(header, plen, &p1, name, 1, 10))
@@ -1812,70 +1890,98 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
}
/* OK, all the RRsets validate, now see if we have a NODATA or NXDOMAIN reply */
-
- p1 = (unsigned char *)(header+1);
-
- if (!extract_name(header, plen, &p1, name, 1, 4))
- return STAT_INSECURE;
-
- GETSHORT(type1, p1);
- GETSHORT(class1, p1);
-
- /* Can't validate RRSIG query */
- if (type1 == T_RRSIG)
- return STAT_INSECURE;
-
- cname_loop:
- for (j = ntohs(header->ancount); j != 0; j--)
- {
- if (!(rc = extract_name(header, plen, &p1, name, 0, 10)))
- return STAT_INSECURE; /* bad packet */
-
- GETSHORT(type2, p1);
- GETSHORT(class2, p1);
- p1 += 4; /* TTL */
- GETSHORT(rdlen2, p1);
-
- if (rc == 1 && class1 == class2)
- {
- /* Do we have an answer for the question? */
- if (type1 == type2)
- return RCODE(header) == NXDOMAIN ? STAT_BOGUS : STAT_SECURE;
- else if (type2 == T_CNAME)
- {
- /* looped CNAMES */
- if (!cname_count-- ||
- !extract_name(header, plen, &p1, name, 1, 0) ||
- !(p1 = skip_questions(header, plen)))
- return STAT_INSECURE;
-
- goto cname_loop;
- }
- }
-
- if (!ADD_RDLEN(header, p1, plen, rdlen2))
- return STAT_INSECURE;
- }
-
+ if (have_answer)
+ return STAT_SECURE;
+
/* NXDOMAIN or NODATA reply, prove that (name, class1, type1) can't exist */
-
/* First marshall the NSEC records, if we've not done it previously */
if (!nsec_type)
{
- nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, class1);
+ nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, qclass);
if (nsec_type == 0)
return STAT_INSECURE; /* Bad packet */
if (nsec_type == -1)
return STAT_BOGUS; /* No NSECs */
}
+
+ /* Get name of missing answer */
+ if (!extract_name(header, plen, &qname, name, 1, 0))
+ return STAT_INSECURE;
if (nsec_type == T_NSEC)
- return prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1);
+ return prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype);
else
- return prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1);
+ return prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype);
}
+/* Chase the CNAME chain in the packet until the first record which _doesn't validate.
+ Needed for proving answer in unsigned space.
+ Return STAT_NEED_*
+ STAT_BOGUS - error
+ STAT_INSECURE - name of first non-secure record in name
+*/
+int dnssec_chase_cname(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname)
+{
+ unsigned char *p = (unsigned char *)(header+1);
+ int type, class, qtype, qclass, rdlen, j, rc;
+ int cname_count = 10;
+
+ /* Get question */
+ if (!extract_name(header, plen, &p, name, 1, 4))
+ return STAT_BOGUS;
+
+ GETSHORT(qtype, p);
+ GETSHORT(qclass, p);
+
+ while (1)
+ {
+ for (j = ntohs(header->ancount); j != 0; j--)
+ {
+ if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
+ return STAT_BOGUS; /* bad packet */
+
+ GETSHORT(type, p);
+ GETSHORT(class, p);
+ p += 4; /* TTL */
+ GETSHORT(rdlen, p);
+
+ /* Not target, loop */
+ if (rc == 2 || qclass != class)
+ {
+ if (!ADD_RDLEN(header, p, plen, rdlen))
+ return STAT_BOGUS;
+ continue;
+ }
+
+ /* Got to end of CNAME chain. */
+ if (type != T_CNAME)
+ return STAT_INSECURE;
+
+ /* validate CNAME chain, return if insecure or need more data */
+ rc = validate_rrset(now, header, plen, class, type, name, keyname, NULL, 0, 0, 0);
+ if (rc != STAT_SECURE)
+ {
+ if (rc == STAT_NO_SIG)
+ rc = STAT_INSECURE;
+ return rc;
+ }
+
+ /* Loop down CNAME chain/ */
+ if (!cname_count-- ||
+ !extract_name(header, plen, &p, name, 1, 0) ||
+ !(p = skip_questions(header, plen)))
+ return STAT_BOGUS;
+
+ break;
+ }
+
+ /* End of CNAME chain */
+ return STAT_INSECURE;
+ }
+}
+
+
/* Compute keytag (checksum to quickly index a key). See RFC4034 */
int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen)
{
@@ -1951,7 +2057,8 @@ static int check_name(unsigned char **namep, struct dns_header *header, size_t p
if (label_type == 0xc0)
{
/* pointer for compression. */
- unsigned int offset, i;
+ unsigned int offset;
+ int i;
unsigned char *p;
if (!CHECK_LEN(header, ansp, plen, 2))
@@ -1971,7 +2078,7 @@ static int check_name(unsigned char **namep, struct dns_header *header, size_t p
/* does the pointer end up in an elided RR? */
if (i & 1)
- return -1;
+ return 0;
/* No, scale the pointer */
if (fixup)
@@ -2023,27 +2130,26 @@ static int check_name(unsigned char **namep, struct dns_header *header, size_t p
static int check_rrs(unsigned char *p, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count)
{
int i, type, class, rdlen;
+ unsigned char *pp;
for (i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++)
{
- if (type != T_NSEC && type != T_NSEC3 && type != T_RRSIG)
- {
- if (!check_name(&p, header, plen, fixup, rrs, rr_count))
- return 0;
- }
- else
- {
- if (!(p = skip_name(p, header, plen, 10)))
- return 0;
- }
+ pp = p;
+
+ if (!(p = skip_name(p, header, plen, 10)))
+ return 0;
GETSHORT(type, p);
GETSHORT(class, p);
p += 4; /* TTL */
GETSHORT(rdlen, p);
-
+
if (type != T_NSEC && type != T_NSEC3 && type != T_RRSIG)
{
+ /* fixup name of RR */
+ if (!check_name(&pp, header, plen, fixup, rrs, rr_count))
+ return 0;
+
if (class == C_IN)
{
u16 *d;