diff options
Diffstat (limited to 'src/dnssec.c')
-rw-r--r-- | src/dnssec.c | 276 |
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; |