Commit 9a31b68b authored by Simon Kelley's avatar Simon Kelley

Major rationalisation of DNSSEC validation.

Much gnarly special-case code removed and replaced with correct
general implementaion. Checking of zone-status moved to DNSSEC code,
where it should be, vastly simplifying query-forwarding code.
parent 0007ee90
...@@ -586,12 +586,8 @@ struct hostsfile { ...@@ -586,12 +586,8 @@ struct hostsfile {
#define STAT_NEED_KEY 5 #define STAT_NEED_KEY 5
#define STAT_TRUNCATED 6 #define STAT_TRUNCATED 6
#define STAT_SECURE_WILDCARD 7 #define STAT_SECURE_WILDCARD 7
#define STAT_NO_SIG 8 #define STAT_OK 8
#define STAT_NO_DS 9 #define STAT_ABANDONED 9
#define STAT_NO_NS 10
#define STAT_NEED_DS_NEG 11
#define STAT_CHASE_CNAME 12
#define STAT_INSECURE_DS 13
#define FREC_NOREBIND 1 #define FREC_NOREBIND 1
#define FREC_CHECKING_DISABLED 2 #define FREC_CHECKING_DISABLED 2
...@@ -601,8 +597,7 @@ struct hostsfile { ...@@ -601,8 +597,7 @@ struct hostsfile {
#define FREC_AD_QUESTION 32 #define FREC_AD_QUESTION 32
#define FREC_DO_QUESTION 64 #define FREC_DO_QUESTION 64
#define FREC_ADDED_PHEADER 128 #define FREC_ADDED_PHEADER 128
#define FREC_CHECK_NOSIGN 256 #define FREC_TEST_PKTSZ 256
#define FREC_TEST_PKTSZ 512
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
#define HASH_SIZE 20 /* SHA-1 digest size */ #define HASH_SIZE 20 /* SHA-1 digest size */
...@@ -626,9 +621,7 @@ struct frec { ...@@ -626,9 +621,7 @@ struct frec {
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
int class, work_counter; int class, work_counter;
struct blockdata *stash; /* Saved reply, whilst we validate */ struct blockdata *stash; /* Saved reply, whilst we validate */
struct blockdata *orig_domain; /* domain of original query, whilst size_t stash_len;
we're seeing is if in unsigned domain */
size_t stash_len, name_start, name_len;
struct frec *dependent; /* Query awaiting internally-generated DNSKEY or DS query */ struct frec *dependent; /* Query awaiting internally-generated DNSKEY or DS query */
struct frec *blocking_query; /* Query which is blocking us. */ struct frec *blocking_query; /* Query which is blocking us. */
#endif #endif
...@@ -1162,8 +1155,8 @@ int in_zone(struct auth_zone *zone, char *name, char **cut); ...@@ -1162,8 +1155,8 @@ int in_zone(struct auth_zone *zone, char *name, char **cut);
size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr, int edns_pktsz); size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr, int edns_pktsz);
int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t n, char *name, char *keyname, int class); int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t n, char *name, char *keyname, int class);
int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class); int dnssec_validate_ds(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, int *nons); int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class,
int dnssec_chase_cname(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname); int check_unsigned, int *neganswer, int *nons);
int dnskey_keytag(int alg, int flags, unsigned char *rdata, int rdlen); int dnskey_keytag(int alg, int flags, unsigned char *rdata, int rdlen);
size_t filter_rrsigs(struct dns_header *header, size_t plen); size_t filter_rrsigs(struct dns_header *header, size_t plen);
unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name); unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name);
......
...@@ -65,8 +65,10 @@ static char *algo_digest_name(int algo) ...@@ -65,8 +65,10 @@ static char *algo_digest_name(int algo)
case 8: return "sha256"; case 8: return "sha256";
case 10: return "sha512"; case 10: return "sha512";
case 12: return "gosthash94"; case 12: return "gosthash94";
#ifndef NO_NETTLE_ECC
case 13: return "sha256"; case 13: return "sha256";
case 14: return "sha384"; case 14: return "sha384";
#endif
default: return NULL; default: return NULL;
} }
} }
...@@ -592,30 +594,30 @@ static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end, ...@@ -592,30 +594,30 @@ static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end,
} }
} }
static int expand_workspace(unsigned char ***wkspc, int *sz, int new) static int expand_workspace(unsigned char ***wkspc, int *szp, int new)
{ {
unsigned char **p; unsigned char **p;
int new_sz = *sz; int old = *szp;
if (new_sz > new) if (old >= new+1)
return 1; return 1;
if (new >= 100) if (new >= 100)
return 0; return 0;
new_sz += 5; new += 5;
if (!(p = whine_malloc((new_sz) * sizeof(unsigned char **)))) if (!(p = whine_malloc(new * sizeof(unsigned char **))))
return 0; return 0;
if (*wkspc) if (old != 0 && *wkspc)
{ {
memcpy(p, *wkspc, *sz * sizeof(unsigned char **)); memcpy(p, *wkspc, old * sizeof(unsigned char **));
free(*wkspc); free(*wkspc);
} }
*wkspc = p; *wkspc = p;
*sz = new_sz; *szp = new;
return 1; return 1;
} }
...@@ -706,47 +708,28 @@ static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int ...@@ -706,47 +708,28 @@ static void sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int
} while (swap); } while (swap);
} }
/* Validate a single RRset (class, type, name) in the supplied DNS reply static unsigned char **rrset = NULL, **sigs = NULL;
Return code:
STAT_SECURE if it validates.
STAT_SECURE_WILDCARD if it validates and is the result of wildcard expansion.
(In this case *wildcard_out points to the "body" of the wildcard within name.)
STAT_NO_SIG no RRsigs found.
STAT_INSECURE RRset empty.
STAT_BOGUS signature is wrong, bad packet.
STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname)
if key is non-NULL, use that key, which has the algo and tag given in the params of those names,
otherwise find the key in the cache.
name is unchanged on exit. keyname is used as workspace and trashed. /* Get pointers to RRset menbers and signature(s) for same.
*/ Check signatures, and return keyname associated in keyname. */
static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, static int explore_rrset(struct dns_header *header, size_t plen, int class, int type,
char *name, char *keyname, char **wildcard_out, struct blockdata *key, int keylen, int algo_in, int keytag_in) char *name, char *keyname, int *sigcnt, int *rrcnt)
{ {
static unsigned char **rrset = NULL, **sigs = NULL; static int rrset_sz = 0, sig_sz = 0;
static int rrset_sz = 0, sig_sz = 0;
unsigned char *p; unsigned char *p;
int rrsetidx, sigidx, res, rdlen, j, name_labels; int rrsetidx, sigidx, j, rdlen, res;
struct crec *crecp = NULL; int name_labels = count_labels(name); /* For 4035 5.3.2 check */
int type_covered, algo, labels, orig_ttl, sig_expiration, sig_inception, key_tag; int gotkey = 0;
u16 *rr_desc = get_desc(type);
if (wildcard_out)
*wildcard_out = NULL;
if (!(p = skip_questions(header, plen))) if (!(p = skip_questions(header, plen)))
return STAT_BOGUS; return STAT_BOGUS;
name_labels = count_labels(name); /* For 4035 5.3.2 check */
/* look for RRSIGs for this RRset and get pointers to each RR in the set. */ /* look for RRSIGs for this RRset and get pointers to each RR in the set. */
for (rrsetidx = 0, sigidx = 0, j = ntohs(header->ancount) + ntohs(header->nscount); for (rrsetidx = 0, sigidx = 0, j = ntohs(header->ancount) + ntohs(header->nscount);
j != 0; j--) j != 0; j--)
{ {
unsigned char *pstart, *pdata; unsigned char *pstart, *pdata;
int stype, sclass; int stype, sclass, algo, type_covered, labels, sig_expiration, sig_inception;
pstart = p; pstart = p;
...@@ -762,14 +745,14 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in ...@@ -762,14 +745,14 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
GETSHORT(rdlen, p); GETSHORT(rdlen, p);
if (!CHECK_LEN(header, p, plen, rdlen)) if (!CHECK_LEN(header, p, plen, rdlen))
return STAT_BOGUS; return 0;
if (res == 1 && sclass == class) if (res == 1 && sclass == class)
{ {
if (stype == type) if (stype == type)
{ {
if (!expand_workspace(&rrset, &rrset_sz, rrsetidx)) if (!expand_workspace(&rrset, &rrset_sz, rrsetidx))
return STAT_BOGUS; return 0;
rrset[rrsetidx++] = pstart; rrset[rrsetidx++] = pstart;
} }
...@@ -777,14 +760,54 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in ...@@ -777,14 +760,54 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
if (stype == T_RRSIG) if (stype == T_RRSIG)
{ {
if (rdlen < 18) if (rdlen < 18)
return STAT_BOGUS; /* bad packet */ return 0; /* bad packet */
GETSHORT(type_covered, p); GETSHORT(type_covered, p);
algo = *p++;
labels = *p++;
p += 4; /* orig_ttl */
GETLONG(sig_expiration, p);
GETLONG(sig_inception, p);
p += 2; /* key_tag */
if (type_covered == type) if (gotkey)
{
/* If there's more than one SIG, ensure they all have same keyname */
if (extract_name(header, plen, &p, keyname, 0, 0) != 1)
return 0;
}
else
{
gotkey = 1;
if (!extract_name(header, plen, &p, keyname, 1, 0))
return 0;
/* RFC 4035 5.3.1 says that the Signer's Name field MUST equal
the name of the zone containing the RRset. We can't tell that
for certain, but we can check that the RRset name is equal to
or encloses the signers name, which should be enough to stop
an attacker using signatures made with the key of an unrelated
zone he controls. Note that the root key is always allowed. */
if (*keyname != 0)
{
char *name_start;
for (name_start = name; !hostname_isequal(name_start, keyname); )
if ((name_start = strchr(name_start, '.')))
name_start++; /* chop a label off and try again */
else
return 0;
}
}
/* Don't count signatures for algos we don't support */
if (check_date_range(sig_inception, sig_expiration) &&
labels <= name_labels &&
type_covered == type &&
algo_digest_name(algo))
{ {
if (!expand_workspace(&sigs, &sig_sz, sigidx)) if (!expand_workspace(&sigs, &sig_sz, sigidx))
return STAT_BOGUS; return 0;
sigs[sigidx++] = pdata; sigs[sigidx++] = pdata;
} }
...@@ -794,17 +817,45 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in ...@@ -794,17 +817,45 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
} }
if (!ADD_RDLEN(header, p, plen, rdlen)) if (!ADD_RDLEN(header, p, plen, rdlen))
return STAT_BOGUS; return 0;
} }
/* RRset empty */ *sigcnt = sigidx;
if (rrsetidx == 0) *rrcnt = rrsetidx;
return STAT_INSECURE;
return 1;
}
/* Validate a single RRset (class, type, name) in the supplied DNS reply
Return code:
STAT_SECURE if it validates.
STAT_SECURE_WILDCARD if it validates and is the result of wildcard expansion.
(In this case *wildcard_out points to the "body" of the wildcard within name.)
STAT_BOGUS signature is wrong, bad packet.
STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname)
STAT_NEED_DS need DS to complete validation (name is returned in keyname)
if key is non-NULL, use that key, which has the algo and tag given in the params of those names,
otherwise find the key in the cache.
/* no RRSIGs */ name is unchanged on exit. keyname is used as workspace and trashed.
if (sigidx == 0)
return STAT_NO_SIG; Call explore_rrset first to find and count RRs and sigs.
*/
static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, int sigidx, int rrsetidx,
char *name, char *keyname, char **wildcard_out, struct blockdata *key, int keylen, int algo_in, int keytag_in)
{
unsigned char *p;
int rdlen, j, name_labels;
struct crec *crecp = NULL;
int algo, labels, orig_ttl, key_tag;
u16 *rr_desc = get_desc(type);
if (wildcard_out)
*wildcard_out = NULL;
name_labels = count_labels(name); /* For 4035 5.3.2 check */
/* Sort RRset records into canonical order. /* Sort RRset records into canonical order.
Note that at this point keyname and daemon->workspacename buffs are Note that at this point keyname and daemon->workspacename buffs are
unused, and used as workspace by the sort. */ unused, and used as workspace by the sort. */
...@@ -828,44 +879,16 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in ...@@ -828,44 +879,16 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
algo = *p++; algo = *p++;
labels = *p++; labels = *p++;
GETLONG(orig_ttl, p); GETLONG(orig_ttl, p);
GETLONG(sig_expiration, p); p += 8; /* sig_expiration, sig_inception already checked */
GETLONG(sig_inception, p);
GETSHORT(key_tag, p); GETSHORT(key_tag, p);
if (!extract_name(header, plen, &p, keyname, 1, 0)) if (!extract_name(header, plen, &p, keyname, 1, 0))
return STAT_BOGUS; return STAT_BOGUS;
/* RFC 4035 5.3.1 says that the Signer's Name field MUST equal if (!(hash = hash_find(algo_digest_name(algo))) ||
the name of the zone containing the RRset. We can't tell that
for certain, but we can check that the RRset name is equal to
or encloses the signers name, which should be enough to stop
an attacker using signatures made with the key of an unrelated
zone he controls. Note that the root key is always allowed. */
if (*keyname != 0)
{
int failed = 0;
for (name_start = name; !hostname_isequal(name_start, keyname); )
if ((name_start = strchr(name_start, '.')))
name_start++; /* chop a label off and try again */
else
{
failed = 1;
break;
}
/* Bad sig, try another */
if (failed)
continue;
}
/* Other 5.3.1 checks */
if (!check_date_range(sig_inception, sig_expiration) ||
labels > name_labels ||
!(hash = hash_find(algo_digest_name(algo))) ||
!hash_init(hash, &ctx, &digest)) !hash_init(hash, &ctx, &digest))
continue; continue;
/* OK, we have the signature record, see if the relevant DNSKEY is in the cache. */ /* 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))) if (!key && !(crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY)))
return STAT_NEED_KEY; return STAT_NEED_KEY;
...@@ -971,10 +994,11 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in ...@@ -971,10 +994,11 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
/* The DNS packet is expected to contain the answer to a DNSKEY query. /* The DNS packet is expected to contain the answer to a DNSKEY query.
Put all DNSKEYs in the answer which are valid into the cache. Put all DNSKEYs in the answer which are valid into the cache.
return codes: return codes:
STAT_SECURE At least one valid DNSKEY found and in cache. STAT_OK Done, key(s) in cache.
STAT_BOGUS No DNSKEYs found, which can be validated with DS, STAT_BOGUS No DNSKEYs found, which can be validated with DS,
or self-sign for DNSKEY RRset is not valid, bad packet. or self-sign for DNSKEY RRset is not valid, bad packet.
STAT_NEED_DS DS records to validate a key not found, name in keyname STAT_NEED_DS DS records to validate a key not found, name in keyname
STAT_NEED_DNSKEY DNSKEY records to validate a key not found, name in keyname
*/ */
int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class) int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
{ {
...@@ -1001,23 +1025,6 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch ...@@ -1001,23 +1025,6 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
return STAT_NEED_DS; return STAT_NEED_DS;
} }
/* If we've cached that DS provably doesn't exist, result must be INSECURE */
if (crecp->flags & F_NEG)
return STAT_INSECURE_DS;
/* 4035 5.2
If the validator does not support any of the algorithms listed in an
authenticated DS RRset, then the resolver has no supported
authentication path leading from the parent to the child. The
resolver should treat this case as it would the case of an
authenticated NSEC RRset proving that no DS RRset exists, */
for (recp1 = crecp; recp1; recp1 = cache_find_by_name(recp1, name, now, F_DS))
if (hash_find(ds_digest_name(recp1->addr.ds.digest)))
break;
if (!recp1)
return STAT_INSECURE_DS;
/* NOTE, we need to find ONE DNSKEY which matches the DS */ /* NOTE, we need to find ONE DNSKEY which matches the DS */
for (valid = 0, j = ntohs(header->ancount); j != 0 && !valid; j--) for (valid = 0, j = ntohs(header->ancount); j != 0 && !valid; j--)
{ {
...@@ -1070,7 +1077,8 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch ...@@ -1070,7 +1077,8 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
void *ctx; void *ctx;
unsigned char *digest, *ds_digest; unsigned char *digest, *ds_digest;
const struct nettle_hash *hash; const struct nettle_hash *hash;
int sigcnt, rrcnt;
if (recp1->addr.ds.algo == algo && if (recp1->addr.ds.algo == algo &&
recp1->addr.ds.keytag == keytag && recp1->addr.ds.keytag == keytag &&
recp1->uid == (unsigned int)class && recp1->uid == (unsigned int)class &&
...@@ -1088,10 +1096,14 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch ...@@ -1088,10 +1096,14 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
from_wire(name); from_wire(name);
if (recp1->addr.ds.keylen == (int)hash->digest_size && if (!(recp1->flags & F_NEG) &&
recp1->addr.ds.keylen == (int)hash->digest_size &&
(ds_digest = blockdata_retrieve(recp1->addr.key.keydata, recp1->addr.ds.keylen, NULL)) && (ds_digest = blockdata_retrieve(recp1->addr.key.keydata, recp1->addr.ds.keylen, NULL)) &&
memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0 && memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0 &&
validate_rrset(now, header, plen, class, T_DNSKEY, name, keyname, NULL, key, rdlen - 4, algo, keytag) == STAT_SECURE) explore_rrset(header, plen, class, T_DNSKEY, name, keyname, &sigcnt, &rrcnt) &&
sigcnt != 0 && rrcnt != 0 &&
validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname,
NULL, key, rdlen - 4, algo, keytag) == STAT_SECURE)
{ {
valid = 1; valid = 1;
break; break;
...@@ -1112,7 +1124,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch ...@@ -1112,7 +1124,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
{ {
/* Ensure we have type, class TTL and length */ /* Ensure we have type, class TTL and length */
if (!(rc = extract_name(header, plen, &p, name, 0, 10))) if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
return STAT_INSECURE; /* bad packet */ return STAT_BOGUS; /* bad packet */
GETSHORT(qtype, p); GETSHORT(qtype, p);
GETSHORT(qclass, p); GETSHORT(qclass, p);
...@@ -1198,7 +1210,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch ...@@ -1198,7 +1210,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
/* commit cache insert. */ /* commit cache insert. */
cache_end_insert(); cache_end_insert();
return STAT_SECURE; return STAT_OK;
} }
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DNSKEY"); log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DNSKEY");
...@@ -1207,12 +1219,14 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch ...@@ -1207,12 +1219,14 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
/* The DNS packet is expected to contain the answer to a DS query /* The DNS packet is expected to contain the answer to a DS query
Put all DSs in the answer which are valid into the cache. Put all DSs in the answer which are valid into the cache.
Also handles replies which prove that there's no DS at this location,
either because the zone is unsigned or this isn't a zone cut. These are
cached too.
return codes: return codes:
STAT_SECURE At least one valid DS found and in cache. STAT_OK At least one valid DS found and in cache.
STAT_NO_DS It's proved there's no DS here.
STAT_NO_NS It's proved there's no DS _or_ NS here.
STAT_BOGUS no DS in reply or not signed, fails validation, bad packet. STAT_BOGUS no DS in reply or not signed, fails validation, bad packet.
STAT_NEED_KEY DNSKEY records to validate a DS not found, name in keyname STAT_NEED_KEY DNSKEY records to validate a DS not found, name in keyname
STAT_NEED_DS DS record needed.
*/ */
int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class) int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
...@@ -1230,7 +1244,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char ...@@ -1230,7 +1244,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
if (qtype != T_DS || qclass != class) if (qtype != T_DS || qclass != class)
val = STAT_BOGUS; val = STAT_BOGUS;
else else
val = dnssec_validate_reply(now, header, plen, name, keyname, NULL, &neganswer, &nons); val = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons);
/* Note dnssec_validate_reply() will have cached positive answers */ /* Note dnssec_validate_reply() will have cached positive answers */
if (val == STAT_INSECURE) if (val == STAT_INSECURE)
...@@ -1242,22 +1256,21 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char ...@@ -1242,22 +1256,21 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
if (!(p = skip_section(p, ntohs(header->ancount), header, plen))) if (!(p = skip_section(p, ntohs(header->ancount), header, plen)))
val = STAT_BOGUS; val = STAT_BOGUS;
/* If we return STAT_NO_SIG, name contains the name of the DS query */
if (val == STAT_NO_SIG)
return val;
/* If the key needed to validate the DS is on the same domain as the DS, we'll /* 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 loop getting nowhere. Stop that now. This can happen of the DS answer comes
from the DS's zone, and not the parent zone. */ from the DS's zone, and not the parent zone. */
if (val == STAT_BOGUS || (val == STAT_NEED_KEY && hostname_isequal(name, keyname))) if (val == STAT_BOGUS || (val == STAT_NEED_KEY && hostname_isequal(name, keyname)))
{ {
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS"); log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS");
return STAT_BOGUS; return STAT_BOGUS;
} }
if (val != STAT_SECURE)
return val;
/* By here, the answer is proved secure, and a positive answer has been cached. */ /* By here, the answer is proved secure, and a positive answer has been cached. */
if (val == STAT_SECURE && neganswer) if (neganswer)
{ {
int rdlen, flags = F_FORWARD | F_DS | F_NEG | F_DNSSECOK; int rdlen, flags = F_FORWARD | F_DS | F_NEG | F_DNSSECOK;
unsigned long ttl, minttl = ULONG_MAX; unsigned long ttl, minttl = ULONG_MAX;
...@@ -1317,15 +1330,14 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char ...@@ -1317,15 +1330,14 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
cache_end_insert(); cache_end_insert();
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, nons ? "no delegation" : "no DS"); log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "no DS");
} }
return nons ? STAT_NO_NS : STAT_NO_DS;
} }
return val; return STAT_OK;
} }
/* 4034 6.1 */ /* 4034 6.1 */
static int hostname_cmp(const char *a, const char *b) static int hostname_cmp(const char *a, const char *b)
{ {
...@@ -1452,7 +1464,7 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi ...@@ -1452,7 +1464,7 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
int mask = 0x80 >> (type & 0x07); int mask = 0x80 >> (type & 0x07);
if (nons) if (nons)
*nons = 0; *nons = 1;
/* Find NSEC record that proves name doesn't exist */ /* Find NSEC record that proves name doesn't exist */
for (i = 0; i < nsec_count; i++) for (i = 0; i < nsec_count; i++)
...@@ -1480,9 +1492,22 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi ...@@ -1480,9 +1492,22 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
/* rdlen is now length of type map, and p points to it */ /* rdlen is now length of type map, and p points to it */
/* If we can prove that there's no NS record, return that information. */ /* If we can prove that there's no NS record, return that information. */
if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) == 0) if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) != 0)
*nons = 1; *nons = 0;
if (rdlen >= 2 && p[0] == 0)
{
/* 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 STAT_BOGUS;
/* If the SOA bit is set for a DS record, then we have the
DS from the wrong side of the delegation. */
if (type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0)
return STAT_BOGUS;
}
while (rdlen >= 2) while (rdlen >= 2)
{ {
if (!CHECK_LEN(header, p, plen, rdlen)) if (!CHECK_LEN(header, p, plen, rdlen))
...@@ -1586,7 +1611,7 @@ static int base32_decode(char *in, unsigned char *out) ...@@ -1586,7 +1611,7 @@ static int base32_decode(char *in, unsigned char *out)
static int check_nsec3_coverage(struct dns_header *header, size_t plen, int digest_len, unsigned char *digest, int type, static int check_nsec3_coverage(struct dns_header *header, size_t plen, int digest_len, unsigned char *digest, int type,
char *workspace1, char *workspace2, unsigned char **nsecs, int nsec_count, int *nons) char *workspace1, char *workspace2, unsigned char **nsecs, int nsec_count, int *nons)
{ {
int i, hash_len, salt_len, base32_len, rdlen; int i, hash_len, salt_len, base32_len, rdlen, flags;
unsigned char *p, *psave; unsigned char *p, *psave;
for (i = 0; i < nsec_count; i++) for (i = 0; i < nsec_count; i++)
...@@ -1599,7 +1624,9 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige ...@@ -1599,7 +1624,9 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
p += 8; /* class, type, TTL */ p += 8; /* class, type, TTL */
GETSHORT(rdlen, p); GETSHORT(rdlen, p);
psave = p; psave = p;
p += 4; /* algo, flags, iterations */ p++; /* algo */
flags = *p++; /* flags */
p += 2; /* iterations */
salt_len = *p++; /* salt_len */ salt_len = *p++; /* salt_len */
p += salt_len; /* salt */ p += salt_len; /* salt */
hash_len = *p++; /* p now points to next hashed name */ hash_len = *p++; /* p now points to next hashed name */
...@@ -1626,16 +1653,29 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige ...@@ -1626,16 +1653,29 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
return 0; return 0;
/* If we can prove that there's no NS record, return that information. */ /* If we can prove that there's no NS record, return that information. */
if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) == 0) if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) != 0)
*nons = 1; *nons = 0;
if (rdlen >= 2 && p[0] == 0)
{
/* 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;
/* If the SOA bit is set for a DS record, then we have the
DS from the wrong side of the delegation. */
if (type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0)
return 0;
}
while (rdlen >= 2) while (rdlen >= 2)
{ {
if (p[0] == type >> 8) if (p[0] == type >> 8)
{ {
/* Does the NSEC3 say our type exists? */ /* Does the NSEC3 say our type exists? */
if (offset < p[1] && (p[offset+2] & mask) != 0) if (offset < p[1] && (p[offset+2] & mask) != 0)
return STAT_BOGUS; return 0;
break; /* finshed checking */ break; /* finshed checking */
} }
...@@ -1643,7 +1683,7 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige ...@@ -1643,7 +1683,7 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
rdlen -= p[1]; rdlen -= p[1];
p += p[1]; p += p[1];
} }
return 1; return 1;
} }
else if (rc < 0) else if (rc < 0)
...@@ -1651,16 +1691,27 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige ...@@ -1651,16 +1691,27 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
/* Normal case, hash falls between NSEC3 name-hash and next domain name-hash, /* Normal case, hash falls between NSEC3 name-hash and next domain name-hash,
wrap around case, name-hash falls between NSEC3 name-hash and end */ wrap around case, name-hash falls between NSEC3 name-hash and end */
if (memcmp(p, digest, digest_len) >= 0 || memcmp(workspace2, p, digest_len) >= 0) if (memcmp(p, digest, digest_len) >= 0 || memcmp(workspace2, p, digest_len) >= 0)
return 1; {
if ((flags & 0x01) && nons) /* opt out */
*nons = 0;
return 1;
}
} }
else else
{ {
/* wrap around case, name falls between start and next domain name */ /* wrap around case, name falls between start and next domain name */
if (memcmp(workspace2, p, digest_len) >= 0 && memcmp(p, digest, digest_len) >= 0) if (memcmp(workspace2, p, digest_len) >= 0 && memcmp(p, digest, digest_len) >= 0)
return 1; {
if ((flags & 0x01) && nons) /* opt out */
*nons = 0;
return 1;
}
} }
} }
} }
return 0; return 0;
} }
...@@ -1673,7 +1724,7 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns ...@@ -1673,7 +1724,7 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
char *closest_encloser, *next_closest, *wildcard; char *closest_encloser, *next_closest, *wildcard;
if (nons) if (nons)
*nons = 0; *nons = 1;
/* Look though the NSEC3 records to find the first one with /* Look though the NSEC3 records to find the first one with
an algorithm we support (currently only algo == 1). an algorithm we support (currently only algo == 1).
...@@ -1813,16 +1864,81 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns ...@@ -1813,16 +1864,81 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
return STAT_SECURE; return STAT_SECURE;
} }
/* Validate all the RRsets in the answer and authority sections of the reply (4035:3.2.3) */ /* Check signing status of name.
/* Returns are the same as validate_rrset, plus the class if the missing key is in *class */ returns:
STAT_SECURE zone is signed.
STAT_INSECURE zone proved unsigned.
STAT_NEED_DS require DS record of name returned in keyname.
name returned unaltered.
*/
static int zone_status(char *name, int class, char *keyname, time_t now)
{
int name_start = strlen(name);
struct crec *crecp;
char *p;
while (1)
{
strcpy(keyname, &name[name_start]);
if (!(crecp = cache_find_by_name(NULL, keyname, now, F_DS)))
return STAT_NEED_DS;
else
do
{
if (crecp->uid == (unsigned int)class)
{
/* F_DNSSECOK misused in DS cache records to non-existance of NS record.
F_NEG && !F_DNSSECOK implies that we've proved there's no DS record here,
but that's because there's no NS record either, ie this isn't the start
of a zone. We only prove that the DNS tree below a node is unsigned when
we prove that we're at a zone cut AND there's no DS record.
*/
if (crecp->flags & F_NEG)
{
if (crecp->flags & F_DNSSECOK)
return STAT_INSECURE; /* proved no DS here */
}
else if (!ds_digest_name(crecp->addr.ds.digest) || !algo_digest_name(crecp->addr.ds.algo))
return STAT_INSECURE; /* algo we can't use - insecure */
}
}
while ((crecp = cache_find_by_name(crecp, keyname, now, F_DS)));
if (name_start == 0)
break;
for (p = &name[name_start-2]; (*p != '.') && (p != name); p--);
if (p != name)
p++;
name_start = p - name;
}
return STAT_SECURE;
}
/* Validate all the RRsets in the answer and authority sections of the reply (4035:3.2.3)
Return code:
STAT_SECURE if it validates.
STAT_INSECURE at least one RRset not validated, because in unsigned zone.
STAT_BOGUS signature is wrong, bad packet, no validation where there should be.
STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname, class in *class)
STAT_NEED_DS need DS to complete validation (name is returned in keyname)
*/
int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname,
int *class, int *neganswer, int *nons) int *class, int check_unsigned, int *neganswer, int *nons)
{ {
unsigned char *ans_start, *qname, *p1, *p2, **nsecs; static unsigned char **targets = NULL;
int type1, class1, rdlen1, type2, class2, rdlen2, qclass, qtype; static int target_sz = 0;
int i, j, rc, nsec_count, cname_count = CNAME_CHAIN;
int nsec_type = 0, have_answer = 0; unsigned char *ans_start, *p1, *p2, **nsecs;
int type1, class1, rdlen1, type2, class2, rdlen2, qclass, qtype, targetidx;
int i, j, rc, nsec_count;
int nsec_type;
if (neganswer) if (neganswer)
*neganswer = 0; *neganswer = 0;
...@@ -1833,70 +1949,51 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch ...@@ -1833,70 +1949,51 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
if (RCODE(header) != NXDOMAIN && RCODE(header) != NOERROR) if (RCODE(header) != NXDOMAIN && RCODE(header) != NOERROR)
return STAT_INSECURE; return STAT_INSECURE;
qname = p1 = (unsigned char *)(header+1); p1 = (unsigned char *)(header+1);
/* Find all the targets we're looking for answers to.
The zeroth array element is for the query, subsequent ones
for CNAME targets, unless the query is for a CNAME. */
if (!expand_workspace(&targets, &target_sz, 0))
return STAT_BOGUS;
targets[0] = p1;
targetidx = 1;
if (!extract_name(header, plen, &p1, name, 1, 4)) if (!extract_name(header, plen, &p1, name, 1, 4))
return STAT_BOGUS; return STAT_BOGUS;
GETSHORT(qtype, p1); GETSHORT(qtype, p1);
GETSHORT(qclass, p1); GETSHORT(qclass, p1);
ans_start = p1; ans_start = p1;
if (qtype == T_ANY)
have_answer = 1;
/* Can't validate an RRISG query */ /* Can't validate an RRSIG query */
if (qtype == T_RRSIG) if (qtype == T_RRSIG)
return STAT_INSECURE; 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_BOGUS; /* 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_BOGUS;
p1 = ans_start;
goto cname_loop;
}
}
if (!ADD_RDLEN(header, p1, plen, rdlen2))
return STAT_BOGUS;
}
if (neganswer && !have_answer)
*neganswer = 1;
/* No data, therefore no sigs */ if (qtype != T_CNAME)
if (ntohs(header->ancount) + ntohs(header->nscount) == 0) for (j = ntohs(header->ancount); j != 0; j--)
{ {
*keyname = 0; if (!(p1 = skip_name(p1, header, plen, 10)))
return STAT_NO_SIG; return STAT_BOGUS; /* bad packet */
}
GETSHORT(type2, p1);
p1 += 6; /* class, TTL */
GETSHORT(rdlen2, p1);
if (type2 == T_CNAME)
{
if (!expand_workspace(&targets, &target_sz, targetidx))
return STAT_BOGUS;
targets[targetidx++] = p1; /* pointer to target name */
}
if (!ADD_RDLEN(header, p1, plen, rdlen2))
return STAT_BOGUS;
}
for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++) for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++)
{ {
if (!extract_name(header, plen, &p1, name, 1, 10)) if (!extract_name(header, plen, &p1, name, 1, 10))
...@@ -1931,7 +2028,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch ...@@ -1931,7 +2028,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
/* Not done, validate now */ /* Not done, validate now */
if (j == i) if (j == i)
{ {
int ttl, keytag, algo, digest, type_covered; int ttl, keytag, algo, digest, type_covered, sigcnt, rrcnt;
unsigned char *psave; unsigned char *psave;
struct all_addr a; struct all_addr a;
struct blockdata *key; struct blockdata *key;
...@@ -1939,143 +2036,186 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch ...@@ -1939,143 +2036,186 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
char *wildname; char *wildname;
int have_wildcard = 0; int have_wildcard = 0;
rc = validate_rrset(now, header, plen, class1, type1, name, keyname, &wildname, NULL, 0, 0, 0); if (!explore_rrset(header, plen, class1, type1, name, keyname, &sigcnt, &rrcnt))
return STAT_BOGUS;
if (rc == STAT_SECURE_WILDCARD)
{
have_wildcard = 1;
/* An attacker replay a wildcard answer with a different
answer and overlay a genuine RR. To prove this
hasn't happened, the answer must prove that
the gennuine record doesn't exist. Check that here. */
if (!nsec_type && !(nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, class1)))
return STAT_BOGUS; /* No NSECs or bad packet */
if (nsec_type == T_NSEC)
rc = prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1, NULL);
else
rc = prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename,
keyname, name, type1, wildname, NULL);
if (rc != STAT_SECURE)
return rc;
}
else if (rc != STAT_SECURE)
{
if (class)
*class = class1; /* Class for DS or DNSKEY */
if (rc == STAT_NO_SIG) /* No signatures for RRset. We can be configured to assume this is OK and return a INSECURE result. */
if (sigcnt == 0)
{
if (check_unsigned)
{ {
/* If we dropped off the end of a CNAME chain, return rc = zone_status(name, class1, keyname, now);
STAT_NO_SIG and the last name is keyname. This is used for proving non-existence if (rc == STAT_SECURE)
if DS records in CNAME chains. */ rc = STAT_BOGUS;
if (cname_count == CNAME_CHAIN || i < ntohs(header->ancount)) if (class)
/* No CNAME chain, or no sig in answer section, return empty name. */ *class = class1; /* Class for NEED_DS or NEED_DNSKEY */
*keyname = 0;
else if (!extract_name(header, plen, &qname, keyname, 1, 0))
return STAT_BOGUS;
} }
else
rc = STAT_INSECURE;
return rc; return rc;
} }
/* Cache RRsigs in answer section, and if we just validated a DS RRset, cache it */ /* explore_rrset() gives us key name from sigs in keyname.
cache_start_insert(); Can't overwrite name here. */
strcpy(daemon->workspacename, keyname);
rc = zone_status(daemon->workspacename, class1, keyname, now);
if (rc != STAT_SECURE)
{
/* Zone is insecure, don't need to validate RRset */
if (class)
*class = class1; /* Class for NEED_DS or NEED_DNSKEY */
return rc;
}
rc = validate_rrset(now, header, plen, class1, type1, sigcnt, rrcnt, name, keyname, &wildname, NULL, 0, 0, 0);
for (p2 = ans_start, j = 0; j < ntohs(header->ancount); j++) if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS)
{ {
if (!(rc = extract_name(header, plen, &p2, name, 0, 10))) if (class)
return STAT_BOGUS; /* bad packet */ *class = class1; /* Class for DS or DNSKEY */
return rc;
}
else
{
/* rc is now STAT_SECURE or STAT_SECURE_WILDCARD */
/* Note if we've validated either the answer to the question
or the target of a CNAME. Any not noted will need NSEC or
to be in unsigned space. */
for (j = 0; j <targetidx; j++)
if ((p2 = targets[j]))
{
if (!(rc = extract_name(header, plen, &p2, name, 0, 10)))
return STAT_BOGUS; /* bad packet */
if (class1 == qclass && rc == 1 && (type1 == T_CNAME || type1 == qtype || qtype == T_ANY ))
targets[j] = NULL;
}
if (rc == STAT_SECURE_WILDCARD)
{
have_wildcard = 1;
GETSHORT(type2, p2); /* An attacker replay a wildcard answer with a different
GETSHORT(class2, p2); answer and overlay a genuine RR. To prove this
GETLONG(ttl, p2); hasn't happened, the answer must prove that
GETSHORT(rdlen2, p2); the gennuine record doesn't exist. Check that here. */
if (!(nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, class1)))
if (!CHECK_LEN(header, p2, plen, rdlen2)) return STAT_BOGUS; /* No NSECs or bad packet */
return STAT_BOGUS; /* bad packet */
/* Note that we may not yet have validated the NSEC/NSEC3 RRsets. Since the check
if (class2 == class1 && rc == 1) below returns either SECURE or BOGUS, that's not a problem. If the RRsets later fail
{ we'll return BOGUS then. */
psave = p2;
if (type1 == T_DS && type2 == T_DS) if (nsec_type == T_NSEC)
{ rc = prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1, NULL);
if (rdlen2 < 4) else
return STAT_BOGUS; /* bad packet */ rc = prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename,
keyname, name, type1, wildname, NULL);
GETSHORT(keytag, p2);
algo = *p2++; if (rc == STAT_BOGUS)
digest = *p2++; return rc;
}
/* Cache needs to known class for DNSSEC stuff */
a.addr.dnssec.class = class2; /* Cache RRsigs in answer section, and if we just validated a DS RRset, cache it */
/* Also note if the RRset is the answer to the question, or the target of a CNAME */
if ((key = blockdata_alloc((char*)p2, rdlen2 - 4))) cache_start_insert();
{
if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DS | F_DNSSECOK))) for (p2 = ans_start, j = 0; j < ntohs(header->ancount); j++)
blockdata_free(key); {
else if (!(rc = extract_name(header, plen, &p2, name, 0, 10)))
{ return STAT_BOGUS; /* bad packet */
a.addr.keytag = keytag;
log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %u"); GETSHORT(type2, p2);
crecp->addr.ds.digest = digest; GETSHORT(class2, p2);
crecp->addr.ds.keydata = key; GETLONG(ttl, p2);
crecp->addr.ds.algo = algo; GETSHORT(rdlen2, p2);
crecp->addr.ds.keytag = keytag;
crecp->addr.ds.keylen = rdlen2 - 4; if (!CHECK_LEN(header, p2, plen, rdlen2))
} return STAT_BOGUS; /* bad packet */
}
} if (class2 == class1 && rc == 1)
else if (type2 == T_RRSIG) {
{ psave = p2;
if (rdlen2 < 18)
return STAT_BOGUS; /* bad packet */
GETSHORT(type_covered, p2); if (type1 == T_DS && type2 == T_DS)
if (type_covered == type1 &&
(type_covered == T_A || type_covered == T_AAAA ||
type_covered == T_CNAME || type_covered == T_DS ||
type_covered == T_DNSKEY || type_covered == T_PTR))
{ {
a.addr.dnssec.type = type_covered; if (rdlen2 < 4)
a.addr.dnssec.class = class1; return STAT_BOGUS; /* bad packet */
algo = *p2++;
p2 += 13; /* labels, orig_ttl, expiration, inception */
GETSHORT(keytag, p2); GETSHORT(keytag, p2);
algo = *p2++;
digest = *p2++;
/* Cache needs to known class for DNSSEC stuff */
a.addr.dnssec.class = class2;
/* We don't cache sigs for wildcard answers, because to reproduce the if ((key = blockdata_alloc((char*)p2, rdlen2 - 4)))
answer from the cache will require one or more NSEC/NSEC3 records
which we don't cache. The lack of the RRSIG ensures that a query for
this RRset asking for a secure answer will always be forwarded. */
if (!have_wildcard && (key = blockdata_alloc((char*)psave, rdlen2)))
{ {
if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DS))) if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DS | F_DNSSECOK)))
blockdata_free(key); blockdata_free(key);
else else
{ {
crecp->addr.sig.keydata = key; a.addr.keytag = keytag;
crecp->addr.sig.keylen = rdlen2; log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %u");
crecp->addr.sig.keytag = keytag; crecp->addr.ds.digest = digest;
crecp->addr.sig.type_covered = type_covered; crecp->addr.ds.keydata = key;
crecp->addr.sig.algo = algo; crecp->addr.ds.algo = algo;
crecp->addr.ds.keytag = keytag;
crecp->addr.ds.keylen = rdlen2 - 4;
}
}
}
else if (type2 == T_RRSIG)
{
if (rdlen2 < 18)
return STAT_BOGUS; /* bad packet */
GETSHORT(type_covered, p2);
if (type_covered == type1 &&
(type_covered == T_A || type_covered == T_AAAA ||
type_covered == T_CNAME || type_covered == T_DS ||
type_covered == T_DNSKEY || type_covered == T_PTR))
{
a.addr.dnssec.type = type_covered;
a.addr.dnssec.class = class1;
algo = *p2++;
p2 += 13; /* labels, orig_ttl, expiration, inception */
GETSHORT(keytag, p2);
/* We don't cache sigs for wildcard answers, because to reproduce the
answer from the cache will require one or more NSEC/NSEC3 records
which we don't cache. The lack of the RRSIG ensures that a query for
this RRset asking for a secure answer will always be forwarded. */
if (!have_wildcard && (key = blockdata_alloc((char*)psave, rdlen2)))
{
if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DS)))
blockdata_free(key);
else
{
crecp->addr.sig.keydata = key;
crecp->addr.sig.keylen = rdlen2;
crecp->addr.sig.keytag = keytag;
crecp->addr.sig.type_covered = type_covered;
crecp->addr.sig.algo = algo;
}
} }
} }
} }
p2 = psave;
} }
p2 = psave; if (!ADD_RDLEN(header, p2, plen, rdlen2))
return STAT_BOGUS; /* bad packet */
} }
if (!ADD_RDLEN(header, p2, plen, rdlen2)) cache_end_insert();
return STAT_BOGUS; /* bad packet */
} }
cache_end_insert();
} }
} }
...@@ -2083,143 +2223,49 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch ...@@ -2083,143 +2223,49 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
return STAT_BOGUS; return STAT_BOGUS;
} }
/* OK, all the RRsets validate, now see if we have a NODATA or NXDOMAIN reply */ /* OK, all the RRsets validate, now see if we have a missing answer or CNAME target. */
if (have_answer) for (j = 0; j <targetidx; j++)
return STAT_SECURE; if ((p2 = targets[j]))
{
/* NXDOMAIN or NODATA reply, prove that (name, class1, type1) can't exist */ if (neganswer)
/* First marshall the NSEC records, if we've not done it previously */ *neganswer = 1;
if (!nsec_type && !(nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, qclass)))
{
/* No NSEC records. If we dropped off the end of a CNAME chain, return
STAT_NO_SIG and the last name is keyname. This is used for proving non-existence
if DS records in CNAME chains. */
if (cname_count == CNAME_CHAIN) /* No CNAME chain, return empty name. */
*keyname = 0;
else if (!extract_name(header, plen, &qname, keyname, 1, 0))
return STAT_BOGUS;
return STAT_NO_SIG; /* No NSECs, this is probably a dangling CNAME pointing into
an unsigned zone. Return STAT_NO_SIG to cause this to be proved. */
}
/* Get name of missing answer */
if (!extract_name(header, plen, &qname, name, 1, 0))
return STAT_BOGUS;
if (nsec_type == T_NSEC)
return prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype, nons);
else
return prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype, NULL, nons);
}
/* 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, qclass, rdlen, j, rc;
int cname_count = CNAME_CHAIN;
char *wildname;
/* Get question */
if (!extract_name(header, plen, &p, name, 1, 4))
return STAT_BOGUS;
p +=2; /* type */
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, &wildname, NULL, 0, 0, 0);
if (rc == STAT_SECURE_WILDCARD)
{
int nsec_type, nsec_count, i;
unsigned char **nsecs;
/* An attacker can replay a wildcard answer with a different
answer and overlay a genuine RR. To prove this
hasn't happened, the answer must prove that
the genuine record doesn't exist. Check that here. */
if (!(nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, class)))
return STAT_BOGUS; /* No NSECs or bad packet */
/* Note that we're called here because something didn't validate in validate_reply,
so we can't assume that any NSEC records have been validated. We do them by steam here */
for (i = 0; i < nsec_count; i++)
{
unsigned char *p1 = nsecs[i];
if (!extract_name(header, plen, &p1, daemon->workspacename, 1, 0))
return STAT_BOGUS;
rc = validate_rrset(now, header, plen, class, nsec_type, daemon->workspacename, keyname, NULL, NULL, 0, 0, 0);
/* NSECs can't be wildcards. */ if (!extract_name(header, plen, &p2, name, 1, 10))
if (rc == STAT_SECURE_WILDCARD) return STAT_BOGUS; /* bad packet */
rc = STAT_BOGUS;
/* NXDOMAIN or NODATA reply, unanswered question is (name, qclass, qtype) */
if (rc != STAT_SECURE) /* 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 (!(nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, qclass)))
{
/* Empty DS without NSECS */
if (qtype == T_DS)
return STAT_BOGUS;
else
{
rc = zone_status(name, qclass, keyname, now);
if (rc != STAT_SECURE)
{
if (class)
*class = qclass; /* Class for NEED_DS or NEED_DNSKEY */
return rc; return rc;
} }
if (nsec_type == T_NSEC) return STAT_BOGUS; /* signed zone, no NSECs */
rc = prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type, NULL); }
else }
rc = prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename,
keyname, name, type, wildname, NULL);
if (rc != STAT_SECURE)
return rc;
}
if (rc != STAT_SECURE)
{
if (rc == STAT_NO_SIG)
rc = STAT_INSECURE;
return rc;
}
/* Loop down CNAME chain/ */ if (nsec_type == T_NSEC)
if (!cname_count-- || rc = prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype, nons);
!extract_name(header, plen, &p, name, 1, 0) || else
!(p = skip_questions(header, plen))) rc = prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype, NULL, nons);
return STAT_BOGUS;
break;
}
/* End of CNAME chain */ if (rc != STAT_SECURE)
return STAT_INSECURE; return rc;
} }
return STAT_SECURE;
} }
......
...@@ -23,15 +23,6 @@ static struct frec *lookup_frec_by_sender(unsigned short id, ...@@ -23,15 +23,6 @@ static struct frec *lookup_frec_by_sender(unsigned short id,
static unsigned short get_id(void); static unsigned short get_id(void);
static void free_frec(struct frec *f); static void free_frec(struct frec *f);
#ifdef HAVE_DNSSEC
static int tcp_key_recurse(time_t now, int status, struct dns_header *header, size_t n,
int class, char *name, char *keyname, struct server *server, int *keycount);
static int do_check_sign(struct frec *forward, int status, time_t now, char *name, char *keyname);
static int send_check_sign(struct frec *forward, time_t now, struct dns_header *header, size_t plen,
char *name, char *keyname);
#endif
/* Send a UDP packet with its source address set as "source" /* Send a UDP packet with its source address set as "source"
unless nowild is true, when we just send it with the kernel default */ unless nowild is true, when we just send it with the kernel default */
int send_from(int fd, int nowild, char *packet, size_t len, int send_from(int fd, int nowild, char *packet, size_t len,
...@@ -825,236 +816,142 @@ void reply_query(int fd, int family, time_t now) ...@@ -825,236 +816,142 @@ void reply_query(int fd, int family, time_t now)
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
if (server && option_bool(OPT_DNSSEC_VALID) && !(forward->flags & FREC_CHECKING_DISABLED)) if (server && option_bool(OPT_DNSSEC_VALID) && !(forward->flags & FREC_CHECKING_DISABLED))
{ {
int status; int status = 0;
/* We've had a reply already, which we're validating. Ignore this duplicate */ /* We've had a reply already, which we're validating. Ignore this duplicate */
if (forward->blocking_query) if (forward->blocking_query)
return; return;
if (header->hb3 & HB3_TC) /* Truncated answer can't be validated.
{
/* Truncated answer can't be validated.
If this is an answer to a DNSSEC-generated query, we still If this is an answer to a DNSSEC-generated query, we still
need to get the client to retry over TCP, so return need to get the client to retry over TCP, so return
an answer with the TC bit set, even if the actual answer fits. an answer with the TC bit set, even if the actual answer fits.
*/ */
status = STAT_TRUNCATED; if (header->hb3 & HB3_TC)
} status = STAT_TRUNCATED;
else if (forward->flags & FREC_DNSKEY_QUERY)
status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class); while (1)
else if (forward->flags & FREC_DS_QUERY)
{
status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
/* Provably no DS, everything below is insecure, even if signatures are offered */
if (status == STAT_NO_DS)
/* We only cache sigs when we've validated a reply.
Avoid caching a reply with sigs if there's a vaildated break in the
DS chain, so we don't return replies from cache missing sigs. */
status = STAT_INSECURE_DS;
else if (status == STAT_NO_SIG)
{
if (option_bool(OPT_DNSSEC_NO_SIGN))
{
status = send_check_sign(forward, now, header, n, daemon->namebuff, daemon->keyname);
if (status == STAT_INSECURE)
status = STAT_INSECURE_DS;
}
else
status = STAT_INSECURE_DS;
}
else if (status == STAT_NO_NS)
status = STAT_BOGUS;
}
else if (forward->flags & FREC_CHECK_NOSIGN)
{
status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
if (status != STAT_NEED_KEY)
status = do_check_sign(forward, status, now, daemon->namebuff, daemon->keyname);
}
else
{ {
status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, NULL, NULL); /* As soon as anything returns BOGUS, we stop and unwind, to do otherwise
if (status == STAT_NO_SIG) would invite infinite loops, since the answers to DNSKEY and DS queries
will not be cached, so they'll be repeated. */
if (status != STAT_BOGUS && status != STAT_TRUNCATED && status != STAT_ABANDONED)
{ {
if (option_bool(OPT_DNSSEC_NO_SIGN)) if (forward->flags & FREC_DNSKEY_QUERY)
status = send_check_sign(forward, now, header, n, daemon->namebuff, daemon->keyname); status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
else if (forward->flags & FREC_DS_QUERY)
status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
else else
status = STAT_INSECURE; status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class,
option_bool(OPT_DNSSEC_NO_SIGN), NULL, NULL);
} }
}
/* Can't validate, as we're missing key data. Put this
answer aside, whilst we get that. */
if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG || status == STAT_NEED_KEY)
{
struct frec *new, *orig;
/* Free any saved query */
if (forward->stash)
blockdata_free(forward->stash);
/* Now save reply pending receipt of key data */
if (!(forward->stash = blockdata_alloc((char *)header, n)))
return;
forward->stash_len = n;
anotherkey: /* Can't validate, as we're missing key data. Put this
/* Find the original query that started it all.... */ answer aside, whilst we get that. */
for (orig = forward; orig->dependent; orig = orig->dependent); if (status == STAT_NEED_DS || status == STAT_NEED_KEY)
if (--orig->work_counter == 0 || !(new = get_new_frec(now, NULL, 1)))
status = STAT_INSECURE;
else
{ {
int fd; struct frec *new, *orig;
struct frec *next = new->next;
*new = *forward; /* copy everything, then overwrite */
new->next = next;
new->blocking_query = NULL;
new->sentto = server;
new->rfd4 = NULL;
new->orig_domain = NULL;
#ifdef HAVE_IPV6
new->rfd6 = NULL;
#endif
new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_CHECK_NOSIGN);
new->dependent = forward; /* to find query awaiting new one. */ /* Free any saved query */
forward->blocking_query = new; /* for garbage cleaning */ if (forward->stash)
/* validate routines leave name of required record in daemon->keyname */ blockdata_free(forward->stash);
if (status == STAT_NEED_KEY)
{ /* Now save reply pending receipt of key data */
new->flags |= FREC_DNSKEY_QUERY; if (!(forward->stash = blockdata_alloc((char *)header, n)))
nn = dnssec_generate_query(header, ((char *) header) + daemon->packet_buff_sz,
daemon->keyname, forward->class, T_DNSKEY, &server->addr, server->edns_pktsz);
}
else
{
if (status == STAT_NEED_DS_NEG)
new->flags |= FREC_CHECK_NOSIGN;
else
new->flags |= FREC_DS_QUERY;
nn = dnssec_generate_query(header,((char *) header) + daemon->packet_buff_sz,
daemon->keyname, forward->class, T_DS, &server->addr, server->edns_pktsz);
}
if ((hash = hash_questions(header, nn, daemon->namebuff)))
memcpy(new->hash, hash, HASH_SIZE);
new->new_id = get_id();
header->id = htons(new->new_id);
/* Save query for retransmission */
if (!(new->stash = blockdata_alloc((char *)header, nn)))
return; return;
forward->stash_len = n;
new->stash_len = nn;
/* Don't resend this. */ /* Find the original query that started it all.... */
daemon->srv_save = NULL; for (orig = forward; orig->dependent; orig = orig->dependent);
if (server->sfd) if (--orig->work_counter == 0 || !(new = get_new_frec(now, NULL, 1)))
fd = server->sfd->fd; status = STAT_ABANDONED;
else else
{ {
fd = -1; int fd;
struct frec *next = new->next;
*new = *forward; /* copy everything, then overwrite */
new->next = next;
new->blocking_query = NULL;
new->sentto = server;
new->rfd4 = NULL;
#ifdef HAVE_IPV6 #ifdef HAVE_IPV6
if (server->addr.sa.sa_family == AF_INET6) new->rfd6 = NULL;
#endif
new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY);
new->dependent = forward; /* to find query awaiting new one. */
forward->blocking_query = new; /* for garbage cleaning */
/* validate routines leave name of required record in daemon->keyname */
if (status == STAT_NEED_KEY)
{
new->flags |= FREC_DNSKEY_QUERY;
nn = dnssec_generate_query(header, ((char *) header) + daemon->packet_buff_sz,
daemon->keyname, forward->class, T_DNSKEY, &server->addr, server->edns_pktsz);
}
else
{ {
if (new->rfd6 || (new->rfd6 = allocate_rfd(AF_INET6))) new->flags |= FREC_DS_QUERY;
fd = new->rfd6->fd; nn = dnssec_generate_query(header,((char *) header) + daemon->packet_buff_sz,
daemon->keyname, forward->class, T_DS, &server->addr, server->edns_pktsz);
} }
if ((hash = hash_questions(header, nn, daemon->namebuff)))
memcpy(new->hash, hash, HASH_SIZE);
new->new_id = get_id();
header->id = htons(new->new_id);
/* Save query for retransmission */
new->stash = blockdata_alloc((char *)header, nn);
new->stash_len = nn;
/* Don't resend this. */
daemon->srv_save = NULL;
if (server->sfd)
fd = server->sfd->fd;
else else
{
fd = -1;
#ifdef HAVE_IPV6
if (server->addr.sa.sa_family == AF_INET6)
{
if (new->rfd6 || (new->rfd6 = allocate_rfd(AF_INET6)))
fd = new->rfd6->fd;
}
else
#endif #endif
{
if (new->rfd4 || (new->rfd4 = allocate_rfd(AF_INET)))
fd = new->rfd4->fd;
}
}
if (fd != -1)
{ {
if (new->rfd4 || (new->rfd4 = allocate_rfd(AF_INET))) while (retry_send(sendto(fd, (char *)header, nn, 0,
fd = new->rfd4->fd; &server->addr.sa,
sa_len(&server->addr))));
server->queries++;
} }
} }
if (fd != -1)
{
while (retry_send(sendto(fd, (char *)header, nn, 0,
&server->addr.sa,
sa_len(&server->addr))));
server->queries++;
}
return; return;
} }
}
/* Ok, we reached far enough up the chain-of-trust that we can validate something. /* Validated original answer, all done. */
Now wind back down, pulling back answers which wouldn't previously validate if (!forward->dependent)
and validate them with the new data. Note that if an answer needs multiple break;
keys to validate, we may find another key is needed, in which case we set off
down another branch of the tree. Once we get to the original answer /* validated subsdiary query, (and cached result)
(FREC_DNSSEC_QUERY not set) and it validates, return it to the original requestor. */ pop that and return to the previous query we were working on. */
while (forward->dependent)
{
struct frec *prev = forward->dependent; struct frec *prev = forward->dependent;
free_frec(forward); free_frec(forward);
forward = prev; forward = prev;
forward->blocking_query = NULL; /* already gone */ forward->blocking_query = NULL; /* already gone */
blockdata_retrieve(forward->stash, forward->stash_len, (void *)header); blockdata_retrieve(forward->stash, forward->stash_len, (void *)header);
n = forward->stash_len; n = forward->stash_len;
if (status == STAT_SECURE)
{
if (forward->flags & FREC_DNSKEY_QUERY)
status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
else if (forward->flags & FREC_DS_QUERY)
{
status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
/* Provably no DS, everything below is insecure, even if signatures are offered */
if (status == STAT_NO_DS)
/* We only cache sigs when we've validated a reply.
Avoid caching a reply with sigs if there's a vaildated break in the
DS chain, so we don't return replies from cache missing sigs. */
status = STAT_INSECURE_DS;
else if (status == STAT_NO_SIG)
{
if (option_bool(OPT_DNSSEC_NO_SIGN))
{
status = send_check_sign(forward, now, header, n, daemon->namebuff, daemon->keyname);
if (status == STAT_INSECURE)
status = STAT_INSECURE_DS;
}
else
status = STAT_INSECURE_DS;
}
else if (status == STAT_NO_NS)
status = STAT_BOGUS;
}
else if (forward->flags & FREC_CHECK_NOSIGN)
{
status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
if (status != STAT_NEED_KEY)
status = do_check_sign(forward, status, now, daemon->namebuff, daemon->keyname);
}
else
{
status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, NULL, NULL);
if (status == STAT_NO_SIG)
{
if (option_bool(OPT_DNSSEC_NO_SIGN))
status = send_check_sign(forward, now, header, n, daemon->namebuff, daemon->keyname);
else
status = STAT_INSECURE;
}
}
if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG || status == STAT_NEED_KEY)
goto anotherkey;
}
} }
no_cache_dnssec = 0; no_cache_dnssec = 0;
if (status == STAT_INSECURE_DS)
{
/* We only cache sigs when we've validated a reply.
Avoid caching a reply with sigs if there's a vaildated break in the
DS chain, so we don't return replies from cache missing sigs. */
status = STAT_INSECURE;
no_cache_dnssec = 1;
}
if (status == STAT_TRUNCATED) if (status == STAT_TRUNCATED)
header->hb3 |= HB3_TC; header->hb3 |= HB3_TC;
...@@ -1062,7 +959,7 @@ void reply_query(int fd, int family, time_t now) ...@@ -1062,7 +959,7 @@ void reply_query(int fd, int family, time_t now)
{ {
char *result, *domain = "result"; char *result, *domain = "result";
if (forward->work_counter == 0) if (status == STAT_ABANDONED)
{ {
result = "ABANDONED"; result = "ABANDONED";
status = STAT_BOGUS; status = STAT_BOGUS;
...@@ -1072,7 +969,7 @@ void reply_query(int fd, int family, time_t now) ...@@ -1072,7 +969,7 @@ void reply_query(int fd, int family, time_t now)
if (status == STAT_BOGUS && extract_request(header, n, daemon->namebuff, NULL)) if (status == STAT_BOGUS && extract_request(header, n, daemon->namebuff, NULL))
domain = daemon->namebuff; domain = daemon->namebuff;
log_query(F_KEYTAG | F_SECSTAT, domain, NULL, result); log_query(F_KEYTAG | F_SECSTAT, domain, NULL, result);
} }
...@@ -1415,315 +1312,49 @@ void receive_query(struct listener *listen, time_t now) ...@@ -1415,315 +1312,49 @@ void receive_query(struct listener *listen, time_t now)
} }
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
/* UDP: we've got an unsigned answer, return STAT_INSECURE if we can prove there's no DS
and therefore the answer shouldn't be signed, or STAT_BOGUS if it should be, or
STAT_NEED_DS_NEG and keyname if we need to do the query. */
static int send_check_sign(struct frec *forward, time_t now, struct dns_header *header, size_t plen,
char *name, char *keyname)
{
int status = dnssec_chase_cname(now, header, plen, name, keyname);
if (status != STAT_INSECURE)
return status;
/* Store the domain we're trying to check. */
forward->name_start = strlen(name);
forward->name_len = forward->name_start + 1;
if (!(forward->orig_domain = blockdata_alloc(name, forward->name_len)))
return STAT_BOGUS;
return do_check_sign(forward, 0, now, name, keyname);
}
/* We either have a a reply (header non-NULL, or we need to start by looking in the cache */
static int do_check_sign(struct frec *forward, int status, time_t now, char *name, char *keyname)
{
/* get domain we're checking back from blockdata store, it's stored on the original query. */
while (forward->dependent && !forward->orig_domain)
forward = forward->dependent;
blockdata_retrieve(forward->orig_domain, forward->name_len, name);
while (1)
{
char *p;
if (status == 0)
{
struct crec *crecp;
/* Haven't received answer, see if in cache */
if (!(crecp = cache_find_by_name(NULL, &name[forward->name_start], now, F_DS)))
{
/* put name of DS record we're missing into keyname */
strcpy(keyname, &name[forward->name_start]);
/* and wait for reply to arrive */
return STAT_NEED_DS_NEG;
}
/* F_DNSSECOK misused in DS cache records to non-existance of NS record */
if (!(crecp->flags & F_NEG))
status = STAT_SECURE;
else if (crecp->flags & F_DNSSECOK)
status = STAT_NO_DS;
else
status = STAT_NO_NS;
}
/* Have entered non-signed part of DNS tree. */
if (status == STAT_NO_DS)
return forward->dependent ? STAT_INSECURE_DS : STAT_INSECURE;
if (status == STAT_BOGUS)
return STAT_BOGUS;
if (status == STAT_NO_SIG && *keyname != 0)
{
/* There is a validated CNAME chain that doesn't end in a DS record. Start
the search again in that domain. */
blockdata_free(forward->orig_domain);
forward->name_start = strlen(keyname);
forward->name_len = forward->name_start + 1;
if (!(forward->orig_domain = blockdata_alloc(keyname, forward->name_len)))
return STAT_BOGUS;
strcpy(name, keyname);
status = 0; /* force to cache when we iterate. */
continue;
}
/* There's a proven DS record, or we're within a zone, where there doesn't need
to be a DS record. Add a name and try again.
If we've already tried the whole name, then fail */
if (forward->name_start == 0)
return STAT_BOGUS;
for (p = &name[forward->name_start-2]; (*p != '.') && (p != name); p--);
if (p != name)
p++;
forward->name_start = p - name;
status = 0; /* force to cache when we iterate. */
}
}
/* Move down from the root, until we find a signed non-existance of a DS, in which case
an unsigned answer is OK, or we find a signed DS, in which case there should be
a signature, and the answer is BOGUS */
static int tcp_check_for_unsigned_zone(time_t now, struct dns_header *header, size_t plen, int class, char *name,
char *keyname, struct server *server, int *keycount)
{
size_t m;
unsigned char *packet, *payload;
u16 *length;
int status, name_len;
struct blockdata *block;
char *name_start;
/* Get first insecure entry in CNAME chain */
status = tcp_key_recurse(now, STAT_CHASE_CNAME, header, plen, class, name, keyname, server, keycount);
if (status == STAT_BOGUS)
return STAT_BOGUS;
if (!(packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16))))
return STAT_BOGUS;
payload = &packet[2];
header = (struct dns_header *)payload;
length = (u16 *)packet;
/* Stash the name away, since the buffer will be trashed when we recurse */
name_len = strlen(name) + 1;
name_start = name + name_len - 1;
if (!(block = blockdata_alloc(name, name_len)))
{
free(packet);
return STAT_BOGUS;
}
while (1)
{
unsigned char c1, c2;
struct crec *crecp;
if (--(*keycount) == 0)
{
free(packet);
blockdata_free(block);
return STAT_BOGUS;
}
while ((crecp = cache_find_by_name(NULL, name_start, now, F_DS)))
{
if ((crecp->flags & F_NEG) && (crecp->flags & F_DNSSECOK))
{
/* Found a secure denial of DS - delegation is indeed insecure */
free(packet);
blockdata_free(block);
return STAT_INSECURE;
}
/* Here, either there's a secure DS, or no NS and no DS, and therefore no delegation.
Add another label and continue. */
if (name_start == name)
{
free(packet);
blockdata_free(block);
return STAT_BOGUS; /* run out of labels */
}
name_start -= 2;
while (*name_start != '.' && name_start != name)
name_start--;
if (name_start != name)
name_start++;
}
/* Can't find it in the cache, have to send a query */
m = dnssec_generate_query(header, ((char *) header) + 65536, name_start, class, T_DS, &server->addr, server->edns_pktsz);
*length = htons(m);
if (read_write(server->tcpfd, packet, m + sizeof(u16), 0) &&
read_write(server->tcpfd, &c1, 1, 1) &&
read_write(server->tcpfd, &c2, 1, 1) &&
read_write(server->tcpfd, payload, (c1 << 8) | c2, 1))
{
m = (c1 << 8) | c2;
/* Note this trashes all three name workspaces */
status = tcp_key_recurse(now, STAT_NEED_DS_NEG, header, m, class, name, keyname, server, keycount);
if (status == STAT_NO_DS)
{
/* Found a secure denial of DS - delegation is indeed insecure */
free(packet);
blockdata_free(block);
return STAT_INSECURE;
}
if (status == STAT_NO_SIG && *keyname != 0)
{
/* There is a validated CNAME chain that doesn't end in a DS record. Start
the search again in that domain. */
blockdata_free(block);
name_len = strlen(keyname) + 1;
name_start = name + name_len - 1;
if (!(block = blockdata_alloc(keyname, name_len)))
return STAT_BOGUS;
strcpy(name, keyname);
continue;
}
if (status == STAT_BOGUS)
{
free(packet);
blockdata_free(block);
return STAT_BOGUS;
}
/* Here, either there's a secure DS, or no NS and no DS, and therefore no delegation.
Add another label and continue. */
/* Get name we're checking back. */
blockdata_retrieve(block, name_len, name);
if (name_start == name)
{
free(packet);
blockdata_free(block);
return STAT_BOGUS; /* run out of labels */
}
name_start -= 2;
while (*name_start != '.' && name_start != name)
name_start--;
if (name_start != name)
name_start++;
}
else
{
/* IO failure */
free(packet);
blockdata_free(block);
return STAT_BOGUS; /* run out of labels */
}
}
}
static int tcp_key_recurse(time_t now, int status, struct dns_header *header, size_t n, static int tcp_key_recurse(time_t now, int status, struct dns_header *header, size_t n,
int class, char *name, char *keyname, struct server *server, int *keycount) int class, char *name, char *keyname, struct server *server, int *keycount)
{ {
/* Recurse up the key heirarchy */ /* Recurse up the key heirarchy */
int new_status; int new_status;
unsigned char *packet = NULL;
size_t m;
unsigned char *payload = NULL;
struct dns_header *new_header = NULL;
u16 *length = NULL;
unsigned char c1, c2;
/* limit the amount of work we do, to avoid cycling forever on loops in the DNS */ while (1)
if (--(*keycount) == 0)
return STAT_INSECURE;
if (status == STAT_NEED_KEY)
new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class);
else if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG)
{ {
new_status = dnssec_validate_ds(now, header, n, name, keyname, class); /* limit the amount of work we do, to avoid cycling forever on loops in the DNS */
if (status == STAT_NEED_DS) if (--(*keycount) == 0)
new_status = STAT_ABANDONED;
else if (status == STAT_NEED_KEY)
new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class);
else if (status == STAT_NEED_DS)
new_status = dnssec_validate_ds(now, header, n, name, keyname, class);
else
new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, option_bool(OPT_DNSSEC_NO_SIGN), NULL, NULL);
if (new_status != STAT_NEED_DS && new_status != STAT_NEED_KEY)
break;
/* Can't validate because we need a key/DS whose name now in keyname.
Make query for same, and recurse to validate */
if (!packet)
{ {
if (new_status == STAT_NO_DS) packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16));
new_status = STAT_INSECURE_DS; payload = &packet[2];
if (new_status == STAT_NO_SIG) new_header = (struct dns_header *)payload;
{ length = (u16 *)packet;
if (option_bool(OPT_DNSSEC_NO_SIGN))
{
new_status = tcp_check_for_unsigned_zone(now, header, n, class, name, keyname, server, keycount);
if (new_status == STAT_INSECURE)
new_status = STAT_INSECURE_DS;
}
else
new_status = STAT_INSECURE_DS;
}
else if (new_status == STAT_NO_NS)
new_status = STAT_BOGUS;
} }
}
else if (status == STAT_CHASE_CNAME)
new_status = dnssec_chase_cname(now, header, n, name, keyname);
else
{
new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, NULL, NULL);
if (new_status == STAT_NO_SIG) if (!packet)
{ {
if (option_bool(OPT_DNSSEC_NO_SIGN)) new_status = STAT_ABANDONED;
new_status = tcp_check_for_unsigned_zone(now, header, n, class, name, keyname, server, keycount); break;
else
new_status = STAT_INSECURE;
} }
}
/* Can't validate because we need a key/DS whose name now in keyname.
Make query for same, and recurse to validate */
if (new_status == STAT_NEED_DS || new_status == STAT_NEED_KEY)
{
size_t m;
unsigned char *packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16));
unsigned char *payload = &packet[2];
struct dns_header *new_header = (struct dns_header *)payload;
u16 *length = (u16 *)packet;
unsigned char c1, c2;
if (!packet)
return STAT_INSECURE;
another_tcp_key:
m = dnssec_generate_query(new_header, ((char *) new_header) + 65536, keyname, class, m = dnssec_generate_query(new_header, ((char *) new_header) + 65536, keyname, class,
new_status == STAT_NEED_KEY ? T_DNSKEY : T_DS, &server->addr, server->edns_pktsz); new_status == STAT_NEED_KEY ? T_DNSKEY : T_DS, &server->addr, server->edns_pktsz);
...@@ -1733,65 +1364,22 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si ...@@ -1733,65 +1364,22 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
!read_write(server->tcpfd, &c1, 1, 1) || !read_write(server->tcpfd, &c1, 1, 1) ||
!read_write(server->tcpfd, &c2, 1, 1) || !read_write(server->tcpfd, &c2, 1, 1) ||
!read_write(server->tcpfd, payload, (c1 << 8) | c2, 1)) !read_write(server->tcpfd, payload, (c1 << 8) | c2, 1))
new_status = STAT_INSECURE;
else
{ {
m = (c1 << 8) | c2; new_status = STAT_ABANDONED;
break;
new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, keycount);
if (new_status == STAT_SECURE)
{
/* Reached a validated record, now try again at this level.
Note that we may get ANOTHER NEED_* if an answer needs more than one key.
If so, go round again. */
if (status == STAT_NEED_KEY)
new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class);
else if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG)
{
new_status = dnssec_validate_ds(now, header, n, name, keyname, class);
if (status == STAT_NEED_DS)
{
if (new_status == STAT_NO_DS)
new_status = STAT_INSECURE_DS;
else if (new_status == STAT_NO_SIG)
{
if (option_bool(OPT_DNSSEC_NO_SIGN))
{
new_status = tcp_check_for_unsigned_zone(now, header, n, class, name, keyname, server, keycount);
if (new_status == STAT_INSECURE)
new_status = STAT_INSECURE_DS;
}
else
new_status = STAT_INSECURE_DS;
}
else if (new_status == STAT_NO_NS)
new_status = STAT_BOGUS;
}
}
else if (status == STAT_CHASE_CNAME)
new_status = dnssec_chase_cname(now, header, n, name, keyname);
else
{
new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, NULL, NULL);
if (new_status == STAT_NO_SIG)
{
if (option_bool(OPT_DNSSEC_NO_SIGN))
new_status = tcp_check_for_unsigned_zone(now, header, n, class, name, keyname, server, keycount);
else
new_status = STAT_INSECURE;
}
}
if (new_status == STAT_NEED_DS || new_status == STAT_NEED_KEY)
goto another_tcp_key;
}
} }
m = (c1 << 8) | c2;
free(packet); new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, keycount);
if (new_status != STAT_OK)
break;
} }
if (packet)
free(packet);
return new_status; return new_status;
} }
#endif #endif
...@@ -2075,19 +1663,10 @@ unsigned char *tcp_request(int confd, time_t now, ...@@ -2075,19 +1663,10 @@ unsigned char *tcp_request(int confd, time_t now,
if (option_bool(OPT_DNSSEC_VALID) && !checking_disabled) if (option_bool(OPT_DNSSEC_VALID) && !checking_disabled)
{ {
int keycount = DNSSEC_WORK; /* Limit to number of DNSSEC questions, to catch loops and avoid filling cache. */ int keycount = DNSSEC_WORK; /* Limit to number of DNSSEC questions, to catch loops and avoid filling cache. */
int status = tcp_key_recurse(now, STAT_TRUNCATED, header, m, 0, daemon->namebuff, daemon->keyname, last_server, &keycount); int status = tcp_key_recurse(now, STAT_OK, header, m, 0, daemon->namebuff, daemon->keyname, last_server, &keycount);
char *result, *domain = "result"; char *result, *domain = "result";
if (status == STAT_INSECURE_DS)
{
/* We only cache sigs when we've validated a reply.
Avoid caching a reply with sigs if there's a vaildated break in the
DS chain, so we don't return replies from cache missing sigs. */
status = STAT_INSECURE;
no_cache_dnssec = 1;
}
if (keycount == 0) if (status == STAT_ABANDONED)
{ {
result = "ABANDONED"; result = "ABANDONED";
status = STAT_BOGUS; status = STAT_BOGUS;
...@@ -2179,7 +1758,6 @@ static struct frec *allocate_frec(time_t now) ...@@ -2179,7 +1758,6 @@ static struct frec *allocate_frec(time_t now)
f->dependent = NULL; f->dependent = NULL;
f->blocking_query = NULL; f->blocking_query = NULL;
f->stash = NULL; f->stash = NULL;
f->orig_domain = NULL;
#endif #endif
daemon->frec_list = f; daemon->frec_list = f;
} }
...@@ -2248,12 +1826,6 @@ static void free_frec(struct frec *f) ...@@ -2248,12 +1826,6 @@ static void free_frec(struct frec *f)
f->stash = NULL; f->stash = NULL;
} }
if (f->orig_domain)
{
blockdata_free(f->orig_domain);
f->orig_domain = NULL;
}
/* Anything we're waiting on is pointless now, too */ /* Anything we're waiting on is pointless now, too */
if (f->blocking_query) if (f->blocking_query)
free_frec(f->blocking_query); free_frec(f->blocking_query);
...@@ -2281,14 +1853,23 @@ struct frec *get_new_frec(time_t now, int *wait, int force) ...@@ -2281,14 +1853,23 @@ struct frec *get_new_frec(time_t now, int *wait, int force)
target = f; target = f;
else else
{ {
if (difftime(now, f->time) >= 4*TIMEOUT) #ifdef HAVE_DNSSEC
{ /* Don't free DNSSEC sub-queries here, as we may end up with
free_frec(f); dangling references to them. They'll go when their "real" query
target = f; is freed. */
} if (!f->dependent)
#endif
if (!oldest || difftime(f->time, oldest->time) <= 0) {
oldest = f; if (difftime(now, f->time) >= 4*TIMEOUT)
{
free_frec(f);
target = f;
}
if (!oldest || difftime(f->time, oldest->time) <= 0)
oldest = f;
}
} }
if (target) if (target)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment