"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "src/dnssec.c" between
dnsmasq-2.80.tar.gz and dnsmasq-2.81.tar.xz

About: Dnsmasq is a lightweight caching DNS forwarder and DHCP server.

dnssec.c  (dnsmasq-2.80):dnssec.c  (dnsmasq-2.81.tar.xz)
/* dnssec.c is Copyright (c) 2012 Giovanni Bajo <rasky@develer.com> /* dnssec.c is Copyright (c) 2012 Giovanni Bajo <rasky@develer.com>
and Copyright (c) 2012-2018 Simon Kelley and Copyright (c) 2012-2020 Simon Kelley
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 dated June, 1991, or the Free Software Foundation; version 2 dated June, 1991, or
(at your option) version 3 dated 29 June, 2007. (at your option) version 3 dated 29 June, 2007.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
skipping to change at line 189 skipping to change at line 189
tv[0].tv_usec = tv[1].tv_usec = 0; tv[0].tv_usec = tv[1].tv_usec = 0;
if (utimes(daemon->timestamp_file, tv) == 0) if (utimes(daemon->timestamp_file, tv) == 0)
goto check_and_exit; goto check_and_exit;
} }
} }
return -1; return -1;
} }
/* Check whether today/now is between date_start and date_end */ /* Check whether today/now is between date_start and date_end */
static int check_date_range(u32 date_start, u32 date_end) static int is_check_date(unsigned long curtime)
{ {
unsigned long curtime = time(0);
/* Checking timestamps may be temporarily disabled */ /* Checking timestamps may be temporarily disabled */
/* If the current time if _before_ the timestamp /* If the current time if _before_ the timestamp
on our persistent timestamp file, then assume the on our persistent timestamp file, then assume the
time if not yet correct, and don't check the time if not yet correct, and don't check the
key timestamps. As soon as the current time is key timestamps. As soon as the current time is
later then the timestamp, update the timestamp later then the timestamp, update the timestamp
and start checking keys */ and start checking keys */
if (daemon->timestamp_file) if (daemon->timestamp_file)
{ {
skipping to change at line 214 skipping to change at line 212
{ {
if (utimes(daemon->timestamp_file, NULL) != 0) if (utimes(daemon->timestamp_file, NULL) != 0)
my_syslog(LOG_ERR, _("failed to update mtime on %s: %s"), daemon->tim estamp_file, strerror(errno)); my_syslog(LOG_ERR, _("failed to update mtime on %s: %s"), daemon->tim estamp_file, strerror(errno));
my_syslog(LOG_INFO, _("system time considered valid, now checking DNSSE C signature timestamps.")); my_syslog(LOG_INFO, _("system time considered valid, now checking DNSSE C signature timestamps."));
daemon->back_to_the_future = 1; daemon->back_to_the_future = 1;
daemon->dnssec_no_time_check = 0; daemon->dnssec_no_time_check = 0;
queue_event(EVENT_RELOAD); /* purge cache */ queue_event(EVENT_RELOAD); /* purge cache */
} }
if (daemon->back_to_the_future == 0) return daemon->back_to_the_future;
return 1;
} }
else if (daemon->dnssec_no_time_check) else
return 1; return !daemon->dnssec_no_time_check;
}
/* Check whether today/now is between date_start and date_end */
static int check_date_range(unsigned long curtime, u32 date_start, u32 date_end)
{
/* We must explicitly check against wanted values, because of SERIAL_UNDEF */ /* We must explicitly check against wanted values, because of SERIAL_UNDEF */
return serial_compare_32(curtime, date_start) == SERIAL_GT return serial_compare_32(curtime, date_start) == SERIAL_GT
&& serial_compare_32(curtime, date_end) == SERIAL_LT; && serial_compare_32(curtime, date_end) == SERIAL_LT;
} }
/* Return bytes of canonicalised rdata, when the return value is zero, the remai ning /* Return bytes of canonicalised rdata, when the return value is zero, the remai ning
data, pointed to by *p, should be used raw. */ data, pointed to by *p, should be used raw. */
static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end, char *buff, int bufflen, static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end, char *buff, int bufflen,
unsigned char **p, u16 **desc) unsigned char **p, u16 **desc)
{ {
skipping to change at line 377 skipping to change at line 378
Check signatures, and return keyname associated in keyname. */ Check signatures, and return keyname associated in keyname. */
static int explore_rrset(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, int *sigcnt, int *rrcnt) char *name, char *keyname, int *sigcnt, int *rrcnt)
{ {
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, j, rdlen, res; int rrsetidx, sigidx, j, rdlen, res;
int gotkey = 0; int gotkey = 0;
if (!(p = skip_questions(header, plen))) if (!(p = skip_questions(header, plen)))
return STAT_BOGUS; return 0;
/* 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->nsco unt); for (rrsetidx = 0, sigidx = 0, j = ntohs(header->ancount) + ntohs(header->nsco unt);
j != 0; j--) j != 0; j--)
{ {
unsigned char *pstart, *pdata; unsigned char *pstart, *pdata;
int stype, sclass, type_covered; int stype, sclass, type_covered;
pstart = p; pstart = p;
if (!(res = extract_name(header, plen, &p, name, 0, 10))) if (!(res = extract_name(header, plen, &p, name, 0, 10)))
return STAT_BOGUS; /* bad packet */ return 0; /* bad packet */
GETSHORT(stype, p); GETSHORT(stype, p);
GETSHORT(sclass, p); GETSHORT(sclass, p);
p += 4; /* TTL */
pdata = p; pdata = p;
p += 4; /* TTL */
GETSHORT(rdlen, p); GETSHORT(rdlen, p);
if (!CHECK_LEN(header, p, plen, rdlen)) if (!CHECK_LEN(header, p, plen, rdlen))
return 0; 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))
skipping to change at line 458 skipping to change at line 459
} }
if (type_covered == type) if (type_covered == type)
{ {
if (!expand_workspace(&sigs, &sig_sz, sigidx)) if (!expand_workspace(&sigs, &sig_sz, sigidx))
return 0; return 0;
sigs[sigidx++] = pdata; sigs[sigidx++] = pdata;
} }
p = pdata + 2; /* restore for ADD_RDLEN */ p = pdata + 6; /* restore for ADD_RDLEN */
} }
} }
if (!ADD_RDLEN(header, p, plen, rdlen)) if (!ADD_RDLEN(header, p, plen, rdlen))
return 0; return 0;
} }
*sigcnt = sigidx; *sigcnt = sigidx;
*rrcnt = rrsetidx; *rrcnt = rrsetidx;
skipping to change at line 487 skipping to change at line 488
STAT_BOGUS signature is wrong, bad packet. STAT_BOGUS signature is wrong, bad packet.
STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname ) 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) 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 par ams of those names, If key is non-NULL, use that key, which has the algo and tag given in the par ams of those names,
otherwise find the key in the cache. otherwise find the key in the cache.
Name is unchanged on exit. keyname is used as workspace and trashed. Name is unchanged on exit. keyname is used as workspace and trashed.
Call explore_rrset first to find and count RRs and sigs. Call explore_rrset first to find and count RRs and sigs.
ttl_out is the floor on TTL, based on TTL and orig_ttl and expiration of sig
used to validate.
*/ */
static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in t class, int type, int sigidx, int rrsetidx, static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in t class, int type, int sigidx, int rrsetidx,
char *name, char *keyname, char **wildcard_out, struct char *name, char *keyname, char **wildcard_out, struct
blockdata *key, int keylen, int algo_in, int keytag_in) blockdata *key, int keylen,
int algo_in, int keytag_in, unsigned long *ttl_out)
{ {
unsigned char *p; unsigned char *p;
int rdlen, j, name_labels, algo, labels, orig_ttl, key_tag; int rdlen, j, name_labels, algo, labels, key_tag;
struct crec *crecp = NULL; struct crec *crecp = NULL;
u16 *rr_desc = rrfilter_desc(type); u16 *rr_desc = rrfilter_desc(type);
u32 sig_expiration, sig_inception u32 sig_expiration, sig_inception;
;
unsigned long curtime = time(0);
int time_check = is_check_date(curtime);
if (wildcard_out) if (wildcard_out)
*wildcard_out = NULL; *wildcard_out = NULL;
name_labels = count_labels(name); /* For 4035 5.3.2 check */ 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. */
rrsetidx = sort_rrset(header, plen, rr_desc, rrsetidx, rrset, daemon->workspac ename, keyname); rrsetidx = sort_rrset(header, plen, rr_desc, rrsetidx, rrset, daemon->workspac ename, keyname);
/* Now try all the sigs to try and find one which validates */ /* Now try all the sigs to try and find one which validates */
for (j = 0; j <sigidx; j++) for (j = 0; j <sigidx; j++)
{ {
unsigned char *psav, *sig, *digest; unsigned char *psav, *sig, *digest;
int i, wire_len, sig_len; int i, wire_len, sig_len;
const struct nettle_hash *hash; const struct nettle_hash *hash;
void *ctx; void *ctx;
char *name_start; char *name_start;
u32 nsigttl; u32 nsigttl, ttl, orig_ttl;
p = sigs[j]; p = sigs[j];
GETLONG(ttl, p);
GETSHORT(rdlen, p); /* rdlen >= 18 checked previously */ GETSHORT(rdlen, p); /* rdlen >= 18 checked previously */
psav = p; psav = p;
p += 2; /* type_covered - already checked */ p += 2; /* type_covered - already checked */
algo = *p++; algo = *p++;
labels = *p++; labels = *p++;
GETLONG(orig_ttl, p); GETLONG(orig_ttl, p);
GETLONG(sig_expiration, p); GETLONG(sig_expiration, p);
GETLONG(sig_inception, p); 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;
if (!check_date_range(sig_inception, sig_expiration) || if ((time_check && !check_date_range(curtime, sig_inception, sig_expiratio n)) ||
labels > name_labels || labels > name_labels ||
!(hash = hash_find(algo_digest_name(algo))) || !(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;
if (ttl_out)
{
/* 4035 5.3.3 rules on TTLs */
if (orig_ttl < ttl)
ttl = orig_ttl;
if (time_check && difftime(sig_expiration, curtime) < ttl)
ttl = difftime(sig_expiration, curtime);
*ttl_out = ttl;
}
sig = p; sig = p;
sig_len = rdlen - (p - psav); sig_len = rdlen - (p - psav);
nsigttl = htonl(orig_ttl); nsigttl = htonl(orig_ttl);
hash->update(ctx, 18, psav); hash->update(ctx, 18, psav);
wire_len = to_wire(keyname); wire_len = to_wire(keyname);
hash->update(ctx, (unsigned int)wire_len, (unsigned char*)keyname); hash->update(ctx, (unsigned int)wire_len, (unsigned char*)keyname);
from_wire(keyname); from_wire(keyname);
skipping to change at line 654 skipping to change at line 674
STAT_OK Done, key(s) 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_KEY DNSKEY records to validate a key not found, name in keyna me STAT_NEED_KEY DNSKEY records to validate a key not found, name in keyna me
*/ */
int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch ar *name, char *keyname, int class) int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch ar *name, char *keyname, int class)
{ {
unsigned char *psave, *p = (unsigned char *)(header+1); unsigned char *psave, *p = (unsigned char *)(header+1);
struct crec *crecp, *recp1; struct crec *crecp, *recp1;
int rc, j, qtype, qclass, ttl, rdlen, flags, algo, valid, keytag; int rc, j, qtype, qclass, rdlen, flags, algo, valid, keytag;
unsigned long ttl, sig_ttl;
struct blockdata *key; struct blockdata *key;
struct all_addr a; union all_addr a;
if (ntohs(header->qdcount) != 1 || if (ntohs(header->qdcount) != 1 ||
RCODE(header) == SERVFAIL || RCODE(header) == REFUSED ||
!extract_name(header, plen, &p, name, 1, 4)) !extract_name(header, plen, &p, name, 1, 4))
return STAT_BOGUS; return STAT_BOGUS;
GETSHORT(qtype, p); GETSHORT(qtype, p);
GETSHORT(qclass, p); GETSHORT(qclass, p);
if (qtype != T_DNSKEY || qclass != class || ntohs(header->ancount) == 0) if (qtype != T_DNSKEY || qclass != class || ntohs(header->ancount) == 0)
return STAT_BOGUS; return STAT_BOGUS;
/* See if we have cached a DS record which validates this key */ /* See if we have cached a DS record which validates this key */
skipping to change at line 748 skipping to change at line 770
/* Note that digest may be different between DSs, so /* Note that digest may be different between DSs, so
we can't move this outside the loop. */ we can't move this outside the loop. */
hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name); hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name);
hash->update(ctx, (unsigned int)rdlen, psave); hash->update(ctx, (unsigned int)rdlen, psave);
hash->digest(ctx, hash->digest_size, digest); hash->digest(ctx, hash->digest_size, digest);
from_wire(name); from_wire(name);
if (!(recp1->flags & F_NEG) && if (!(recp1->flags & F_NEG) &&
recp1->addr.ds.keylen == (int)hash->digest_size && 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.ds.keydata, recp1-> addr.ds.keylen, NULL)) &&
memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0 && memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0 &&
explore_rrset(header, plen, class, T_DNSKEY, name, keyname, &si gcnt, &rrcnt) && explore_rrset(header, plen, class, T_DNSKEY, name, keyname, &si gcnt, &rrcnt) &&
sigcnt != 0 && rrcnt != 0 && sigcnt != 0 && rrcnt != 0 &&
validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcn t, name, keyname, validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcn t, name, keyname,
NULL, key, rdlen - 4, algo, keytag) == STAT_SECU RE) NULL, key, rdlen - 4, algo, keytag, &sig_ttl) == STAT_SECURE)
{ {
valid = 1; valid = 1;
break; break;
} }
} }
} }
blockdata_free(key); blockdata_free(key);
} }
if (valid) if (valid)
skipping to change at line 781 skipping to change at line 803
{ {
/* 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_BOGUS; /* bad packet */ return STAT_BOGUS; /* bad packet */
GETSHORT(qtype, p); GETSHORT(qtype, p);
GETSHORT(qclass, p); GETSHORT(qclass, p);
GETLONG(ttl, p); GETLONG(ttl, p);
GETSHORT(rdlen, p); GETSHORT(rdlen, p);
/* TTL may be limited by sig. */
if (sig_ttl < ttl)
ttl = sig_ttl;
if (!CHECK_LEN(header, p, plen, rdlen)) if (!CHECK_LEN(header, p, plen, rdlen))
return STAT_BOGUS; /* bad packet */ return STAT_BOGUS; /* bad packet */
if (qclass == class && rc == 1) if (qclass == class && rc == 1)
{ {
psave = p; psave = p;
if (qtype == T_DNSKEY) if (qtype == T_DNSKEY)
{ {
if (rdlen < 4) if (rdlen < 4)
return STAT_BOGUS; /* bad packet */ return STAT_BOGUS; /* bad packet */
GETSHORT(flags, p); GETSHORT(flags, p);
if (*p++ != 3) if (*p++ != 3)
return STAT_BOGUS; return STAT_BOGUS;
algo = *p++; algo = *p++;
keytag = dnskey_keytag(algo, flags, p, rdlen - 4); keytag = dnskey_keytag(algo, flags, p, rdlen - 4);
/* Cache needs to known class for DNSSEC stuff */
a.addr.dnssec.class = class;
if ((key = blockdata_alloc((char*)p, rdlen - 4))) if ((key = blockdata_alloc((char*)p, rdlen - 4)))
{ {
if (!(recp1 = cache_insert(name, &a, now, ttl, F_FORWARD | a.key.keylen = rdlen - 4;
F_DNSKEY | F_DNSSECOK))) a.key.keydata = key;
a.key.algo = algo;
a.key.keytag = keytag;
a.key.flags = flags;
if (!cache_insert(name, &a, class, now, ttl, F_FORWARD | F_
DNSKEY | F_DNSSECOK))
{ {
blockdata_free(key); blockdata_free(key);
return STAT_BOGUS; return STAT_BOGUS;
} }
else else
{ {
a.addr.log.keytag = keytag; a.log.keytag = keytag;
a.addr.log.algo = algo; a.log.algo = algo;
if (algo_digest_name(algo)) if (algo_digest_name(algo))
log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a , "DNSKEY keytag %hu, algo %hu"); log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a , "DNSKEY keytag %hu, algo %hu");
else else
log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a , "DNSKEY keytag %hu, algo %hu (not supported)"); log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a , "DNSKEY keytag %hu, algo %hu (not supported)");
recp1->addr.key.keylen = rdlen - 4;
recp1->addr.key.keydata = key;
recp1->addr.key.algo = algo;
recp1->addr.key.keytag = keytag;
recp1->addr.key.flags = flags;
} }
} }
} }
p = psave; p = psave;
} }
if (!ADD_RDLEN(header, p, plen, rdlen)) if (!ADD_RDLEN(header, p, plen, rdlen))
return STAT_BOGUS; /* bad packet */ return STAT_BOGUS; /* bad packet */
} }
skipping to change at line 858 skipping to change at line 881
return codes: return codes:
STAT_OK At least one valid DS found and in cache. STAT_OK At least one valid DS found and in cache.
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. 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)
{ {
unsigned char *p = (unsigned char *)(header+1); unsigned char *p = (unsigned char *)(header+1);
int qtype, qclass, rc, i, neganswer, nons; int qtype, qclass, rc, i, neganswer, nons, neg_ttl = 0;
int aclass, atype, rdlen; int aclass, atype, rdlen;
unsigned long ttl; unsigned long ttl;
struct all_addr a; union all_addr a;
if (ntohs(header->qdcount) != 1 || if (ntohs(header->qdcount) != 1 ||
!(p = skip_name(p, header, plen, 4))) !(p = skip_name(p, header, plen, 4)))
return STAT_BOGUS; return STAT_BOGUS;
GETSHORT(qtype, p); GETSHORT(qtype, p);
GETSHORT(qclass, p); GETSHORT(qclass, p);
if (qtype != T_DS || qclass != class) if (qtype != T_DS || qclass != class)
rc = STAT_BOGUS; rc = STAT_BOGUS;
else else
rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &negan swer, &nons); rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &negan swer, &nons, &neg_ttl);
if (rc == STAT_INSECURE) if (rc == STAT_INSECURE)
{ {
my_syslog(LOG_WARNING, _("Insecure DS reply received, do upstream DNS serv ers support DNSSEC?")); my_syslog(LOG_WARNING, _("Insecure DS reply received for %s, check domain configuration and upstream DNS server DNSSEC support"), name);
rc = STAT_BOGUS; rc = STAT_BOGUS;
} }
p = (unsigned char *)(header+1); p = (unsigned char *)(header+1);
extract_name(header, plen, &p, name, 1, 4); extract_name(header, plen, &p, name, 1, 4);
p += 4; /* qtype, qclass */ p += 4; /* qtype, qclass */
/* 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. */
skipping to change at line 919 skipping to change at line 942
GETSHORT(rdlen, p); GETSHORT(rdlen, p);
if (!CHECK_LEN(header, p, plen, rdlen)) if (!CHECK_LEN(header, p, plen, rdlen))
return STAT_BOGUS; /* bad packet */ return STAT_BOGUS; /* bad packet */
if (aclass == class && atype == T_DS && rc == 1) if (aclass == class && atype == T_DS && rc == 1)
{ {
int algo, digest, keytag; int algo, digest, keytag;
unsigned char *psave = p; unsigned char *psave = p;
struct blockdata *key; struct blockdata *key;
struct crec *crecp;
if (rdlen < 4) if (rdlen < 4)
return STAT_BOGUS; /* bad packet */ return STAT_BOGUS; /* bad packet */
GETSHORT(keytag, p); GETSHORT(keytag, p);
algo = *p++; algo = *p++;
digest = *p++; digest = *p++;
/* Cache needs to known class for DNSSEC stuff */
a.addr.dnssec.class = class;
if ((key = blockdata_alloc((char*)p, rdlen - 4))) if ((key = blockdata_alloc((char*)p, rdlen - 4)))
{ {
if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DS a.ds.digest = digest;
| F_DNSSECOK))) a.ds.keydata = key;
a.ds.algo = algo;
a.ds.keytag = keytag;
a.ds.keylen = rdlen - 4;
if (!cache_insert(name, &a, class, now, ttl, F_FORWARD | F_DS |
F_DNSSECOK))
{ {
blockdata_free(key); blockdata_free(key);
return STAT_BOGUS; return STAT_BOGUS;
} }
else else
{ {
a.addr.log.keytag = keytag; a.log.keytag = keytag;
a.addr.log.algo = algo; a.log.algo = algo;
a.addr.log.digest = digest; a.log.digest = digest;
if (ds_digest_name(digest) && algo_digest_name(algo)) if (ds_digest_name(digest) && algo_digest_name(algo))
log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "D S keytag %hu, algo %hu, digest %hu"); log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "D S keytag %hu, algo %hu, digest %hu");
else else
log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "D S keytag %hu, algo %hu, digest %hu (not supported)"); log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "D S keytag %hu, algo %hu, digest %hu (not supported)");
crecp->addr.ds.digest = digest;
crecp->addr.ds.keydata = key;
crecp->addr.ds.algo = algo;
crecp->addr.ds.keytag = keytag;
crecp->addr.ds.keylen = rdlen - 4;
} }
} }
p = psave; p = psave;
} }
if (!ADD_RDLEN(header, p, plen, rdlen)) if (!ADD_RDLEN(header, p, plen, rdlen))
return STAT_BOGUS; /* bad packet */ return STAT_BOGUS; /* bad packet */
} }
cache_end_insert(); cache_end_insert();
} }
else else
{ {
int flags = F_FORWARD | F_DS | F_NEG | F_DNSSECOK; int flags = F_FORWARD | F_DS | F_NEG | F_DNSSECOK;
unsigned long minttl = ULONG_MAX;
if (!(p = skip_section(p, ntohs(header->ancount), header, plen)))
return STAT_BOGUS;
if (RCODE(header) == NXDOMAIN) if (RCODE(header) == NXDOMAIN)
flags |= F_NXDOMAIN; flags |= F_NXDOMAIN;
/* We only cache validated DS records, DNSSECOK flag hijacked /* We only cache validated DS records, DNSSECOK flag hijacked
to store presence/absence of NS. */ to store presence/absence of NS. */
if (nons) if (nons)
flags &= ~F_DNSSECOK; flags &= ~F_DNSSECOK;
for (i = ntohs(header->nscount); i != 0; i--) cache_start_insert();
{
if (!(p = skip_name(p, header, plen, 0)))
return STAT_BOGUS;
GETSHORT(atype, p);
GETSHORT(aclass, p);
GETLONG(ttl, p);
GETSHORT(rdlen, p);
if (!CHECK_LEN(header, p, plen, rdlen))
return STAT_BOGUS; /* bad packet */
if (aclass != class || atype != T_SOA)
{
p += rdlen;
continue;
}
if (ttl < minttl)
minttl = ttl;
/* MNAME */
if (!(p = skip_name(p, header, plen, 0)))
return STAT_BOGUS;
/* RNAME */
if (!(p = skip_name(p, header, plen, 20)))
return STAT_BOGUS;
p += 16; /* SERIAL REFRESH RETRY EXPIRE */
GETLONG(ttl, p); /* minTTL */
if (ttl < minttl)
minttl = ttl;
break;
}
if (i != 0)
{
cache_start_insert();
a.addr.dnssec.class = class; /* Use TTL from NSEC for negative cache entries */
if (!cache_insert(name, &a, now, ttl, flags)) if (!cache_insert(name, NULL, class, now, neg_ttl, flags))
return STAT_BOGUS; return STAT_BOGUS;
cache_end_insert(); cache_end_insert();
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "no DS"); log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, nons ? "no DS/cut" : "no DS"
} );
} }
return STAT_OK; 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)
{ {
char *sa, *ea, *ca, *sb, *eb, *cb; char *sa, *ea, *ca, *sb, *eb, *cb;
unsigned char ac, bc; unsigned char ac, bc;
skipping to change at line 1541 skipping to change at line 1516
if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, itera tions)) == 0) if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, itera tions)) == 0)
return 0; return 0;
if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspac e1, workspace2, nsecs, nsec_count, NULL, 1)) if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspac e1, workspace2, nsecs, nsec_count, NULL, 1))
return 0; return 0;
} }
return 1; return 1;
} }
static int prove_non_existence(struct dns_header *header, size_t plen, char *key name, char *name, int qtype, int qclass, char *wildname, int *nons) static int prove_non_existence(struct dns_header *header, size_t plen, char *key name, char *name, int qtype, int qclass, char *wildname, int *nons, int *nsec_tt l)
{ {
static unsigned char **nsecset = NULL, **rrsig_labels = NULL; static unsigned char **nsecset = NULL, **rrsig_labels = NULL;
static int nsecset_sz = 0, rrsig_labels_sz = 0; static int nsecset_sz = 0, rrsig_labels_sz = 0;
int type_found = 0; int type_found = 0;
unsigned char *auth_start, *p = skip_questions(header, plen); unsigned char *auth_start, *p = skip_questions(header, plen);
int type, class, rdlen, i, nsecs_found; int type, class, rdlen, i, nsecs_found;
unsigned long ttl;
/* Move to NS section */ /* Move to NS section */
if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen))) if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen)))
return 0; return 0;
auth_start = p; auth_start = p;
for (nsecs_found = 0, i = ntohs(header->nscount); i != 0; i--) for (nsecs_found = 0, i = 0; i < ntohs(header->nscount); i++)
{ {
unsigned char *pstart = p; unsigned char *pstart = p;
if (!extract_name(header, plen, &p, daemon->workspacename, 1, 10)) if (!extract_name(header, plen, &p, daemon->workspacename, 1, 10))
return 0; return 0;
GETSHORT(type, p); GETSHORT(type, p);
GETSHORT(class, p); GETSHORT(class, p);
p += 4; /* TTL */ GETLONG(ttl, p);
GETSHORT(rdlen, p); GETSHORT(rdlen, p);
if (class == qclass && (type == T_NSEC || type == T_NSEC3)) if (class == qclass && (type == T_NSEC || type == T_NSEC3))
{ {
if (nsec_ttl)
{
/* Limit TTL with sig TTL */
if (daemon->rr_status[ntohs(header->ancount) + i] < ttl)
ttl = daemon->rr_status[ntohs(header->ancount) + i];
*nsec_ttl = ttl;
}
/* No mixed NSECing 'round here, thankyouverymuch */ /* No mixed NSECing 'round here, thankyouverymuch */
if (type_found != 0 && type_found != type) if (type_found != 0 && type_found != type)
return 0; return 0;
type_found = type; type_found = type;
if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found)) if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found))
return 0; return 0;
if (type == T_NSEC) if (type == T_NSEC)
skipping to change at line 1750 skipping to change at line 1734
/* Validate all the RRsets in the answer and authority sections of the reply (40 35:3.2.3) /* Validate all the RRsets in the answer and authority sections of the reply (40 35:3.2.3)
Return code: Return code:
STAT_SECURE if it validates. STAT_SECURE if it validates.
STAT_INSECURE at least one RRset not validated, because in unsigned zone. STAT_INSECURE at least one RRset not validated, because in unsigned zone.
STAT_BOGUS signature is wrong, bad packet, no validation where there shoul d be. STAT_BOGUS signature is wrong, bad packet, no validation where there shoul d be.
STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname , class in *class) 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) STAT_NEED_DS need DS to complete validation (name is returned in keyname)
daemon->rr_status points to a char array which corressponds to the RRs in the daemon->rr_status points to a char array which corressponds to the RRs in the
answer section (only). This is set to 1 for each RR which is validated, and 0 answer and auth sections. This is set to 1 for each RR which is validated, an
for any which aren't. d 0 for any which aren't.
When validating replies to DS records, we're only interested in the NSEC{3} R
Rs in the auth section.
Other RRs in that section missing sigs will not cause am INSECURE reply. We d
etermine this mode
is the nons argument is non-NULL.
*/ */
int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch ar *name, char *keyname, int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch ar *name, char *keyname,
int *class, int check_unsigned, int *neganswer, int *no ns) int *class, int check_unsigned, int *neganswer, int *no ns, int *nsec_ttl)
{ {
static unsigned char **targets = NULL; static unsigned char **targets = NULL;
static int target_sz = 0; static int target_sz = 0;
unsigned char *ans_start, *p1, *p2; unsigned char *ans_start, *p1, *p2;
int type1, class1, rdlen1 = 0, type2, class2, rdlen2, qclass, qtype, targetidx ; int type1, class1, rdlen1 = 0, type2, class2, rdlen2, qclass, qtype, targetidx ;
int i, j, rc = STAT_INSECURE; int i, j, rc = STAT_INSECURE;
int secure = STAT_SECURE; int secure = STAT_SECURE;
/* extend rr_status if necessary */ /* extend rr_status if necessary */
if (daemon->rr_status_sz < ntohs(header->ancount)) if (daemon->rr_status_sz < ntohs(header->ancount) + ntohs(header->nscount))
{ {
char *new = whine_malloc(ntohs(header->ancount) + 64); unsigned long *new = whine_malloc(sizeof(*daemon->rr_status) * (ntohs(head er->ancount) + ntohs(header->nscount) + 64));
if (!new) if (!new)
return STAT_BOGUS; return STAT_BOGUS;
free(daemon->rr_status); free(daemon->rr_status);
daemon->rr_status = new; daemon->rr_status = new;
daemon->rr_status_sz = ntohs(header->ancount) + 64; daemon->rr_status_sz = ntohs(header->ancount) + ntohs(header->nscount) + 6 4;
} }
memset(daemon->rr_status, 0, ntohs(header->ancount)); memset(daemon->rr_status, 0, sizeof(*daemon->rr_status) * daemon->rr_status_sz );
if (neganswer) if (neganswer)
*neganswer = 0; *neganswer = 0;
if (RCODE(header) == SERVFAIL || ntohs(header->qdcount) != 1) if (RCODE(header) == SERVFAIL || ntohs(header->qdcount) != 1)
return STAT_BOGUS; return STAT_BOGUS;
if (RCODE(header) != NXDOMAIN && RCODE(header) != NOERROR) if (RCODE(header) != NXDOMAIN && RCODE(header) != NOERROR)
return STAT_INSECURE; return STAT_INSECURE;
skipping to change at line 1867 skipping to change at line 1855
p2 += 4; /* TTL */ p2 += 4; /* TTL */
GETSHORT(rdlen2, p2); GETSHORT(rdlen2, p2);
if (type2 == type1 && class2 == class1 && rc == 1) if (type2 == type1 && class2 == class1 && rc == 1)
break; /* Done it before: name, type, class all match. */ break; /* Done it before: name, type, class all match. */
if (!ADD_RDLEN(header, p2, plen, rdlen2)) if (!ADD_RDLEN(header, p2, plen, rdlen2))
return STAT_BOGUS; return STAT_BOGUS;
} }
/* Done already: copy the validation status */
if (j != i) if (j != i)
{ daemon->rr_status[i] = daemon->rr_status[j];
/* Done already: copy the validation status */
if (i < ntohs(header->ancount))
daemon->rr_status[i] = daemon->rr_status[j];
}
else else
{ {
/* Not done, validate now */ /* Not done, validate now */
int sigcnt, rrcnt; int sigcnt, rrcnt;
char *wildname; char *wildname;
if (!explore_rrset(header, plen, class1, type1, name, keyname, &sigcnt, &rrcnt)) if (!explore_rrset(header, plen, class1, type1, name, keyname, &sigcnt, &rrcnt))
return STAT_BOGUS; return STAT_BOGUS;
/* No signatures for RRset. We can be configured to assume this is OK a nd return an INSECURE result. */ /* No signatures for RRset. We can be configured to assume this is OK a nd return an INSECURE result. */
if (sigcnt == 0) if (sigcnt == 0)
{ {
if (check_unsigned) /* NSEC and NSEC3 records must be signed. We make this assumption e
{ lsewhere. */
rc = zone_status(name, class1, keyname, now); if (type1 == T_NSEC || type1 == T_NSEC3)
if (rc == STAT_SECURE)
rc = STAT_BOGUS;
if (class)
*class = class1; /* Class for NEED_DS or NEED_KEY */
}
else
rc = STAT_INSECURE; rc = STAT_INSECURE;
else if (nons && i >= ntohs(header->ancount))
/* If we're validating a DS reply, rather than looking for the va
lue of AD bit,
we only care that NSEC and NSEC3 RRs in the auth section are s
igned.
Return SECURE even if others (SOA....) are not. */
rc = STAT_SECURE;
else
{
/* unsigned RRsets in auth section are not BOGUS, but do make r
eply insecure. */
if (check_unsigned && i < ntohs(header->ancount))
{
rc = zone_status(name, class1, keyname, now);
if (rc == STAT_SECURE)
rc = STAT_BOGUS;
if (class)
*class = class1; /* Class for NEED_DS or NEED_KEY */
}
else
rc = STAT_INSECURE;
if (rc != STAT_INSECURE) if (rc != STAT_INSECURE)
return rc; return rc;
}
} }
else else
{ {
/* explore_rrset() gives us key name from sigs in keyname. /* explore_rrset() gives us key name from sigs in keyname.
Can't overwrite name here. */ Can't overwrite name here. */
strcpy(daemon->workspacename, keyname); strcpy(daemon->workspacename, keyname);
rc = zone_status(daemon->workspacename, class1, keyname, now); rc = zone_status(daemon->workspacename, class1, keyname, now);
if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS) if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS)
{ {
if (class) if (class)
*class = class1; /* Class for NEED_DS or NEED_KEY */ *class = class1; /* Class for NEED_DS or NEED_KEY */
return rc; return rc;
} }
/* Zone is insecure, don't need to validate RRset */ /* Zone is insecure, don't need to validate RRset */
if (rc == STAT_SECURE) if (rc == STAT_SECURE)
{ {
unsigned long sig_ttl;
rc = validate_rrset(now, header, plen, class1, type1, sigcnt, rc = validate_rrset(now, header, plen, class1, type1, sigcnt,
rrcnt, name, keyname, &wildname, NULL, 0, 0 , 0); rrcnt, name, keyname, &wildname, NULL, 0, 0 , 0, &sig_ttl);
if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_ DS) if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_ DS)
{ {
if (class) if (class)
*class = class1; /* Class for DS or DNSKEY */ *class = class1; /* Class for DS or DNSKEY */
return rc; return rc;
} }
/* rc is now STAT_SECURE or STAT_SECURE_WILDCARD */ /* rc is now STAT_SECURE or STAT_SECURE_WILDCARD */
/* Note that RR is validated */ /* Note that RR is validated */
if (i < ntohs(header->ancount)) daemon->rr_status[i] = sig_ttl;
daemon->rr_status[i] = 1;
/* Note if we've validated either the answer to the question /* Note if we've validated either the answer to the question
or the target of a CNAME. Any not noted will need NSEC or or the target of a CNAME. Any not noted will need NSEC or
to be in unsigned space. */ to be in unsigned space. */
for (j = 0; j <targetidx; j++) for (j = 0; j <targetidx; j++)
if ((p2 = targets[j])) if ((p2 = targets[j]))
{ {
int rc1; int rc1;
if (!(rc1 = extract_name(header, plen, &p2, name, 0, 10)) ) if (!(rc1 = extract_name(header, plen, &p2, name, 0, 10)) )
return STAT_BOGUS; /* bad packet */ return STAT_BOGUS; /* bad packet */
skipping to change at line 1954 skipping to change at line 1951
} }
/* An attacker replay a wildcard answer with a different /* An attacker replay a wildcard answer with a different
answer and overlay a genuine RR. To prove this answer and overlay a genuine RR. To prove this
hasn't happened, the answer must prove that hasn't happened, the answer must prove that
the genuine record doesn't exist. Check that here. the genuine record doesn't exist. Check that here.
Note that we may not yet have validated the NSEC/NSEC3 RRset s. Note that we may not yet have validated the NSEC/NSEC3 RRset s.
That's not a problem since if the RRsets later fail That's not a problem since if the RRsets later fail
we'll return BOGUS then. */ we'll return BOGUS then. */
if (rc == STAT_SECURE_WILDCARD && if (rc == STAT_SECURE_WILDCARD &&
!prove_non_existence(header, plen, keyname, name, type1, cl ass1, wildname, NULL)) !prove_non_existence(header, plen, keyname, name, type1, cl ass1, wildname, NULL, NULL))
return STAT_BOGUS; return STAT_BOGUS;
rc = STAT_SECURE; rc = STAT_SECURE;
} }
} }
} }
if (rc == STAT_INSECURE) if (rc == STAT_INSECURE)
secure = STAT_INSECURE; secure = STAT_INSECURE;
} }
skipping to change at line 1981 skipping to change at line 1978
if (neganswer) if (neganswer)
*neganswer = 1; *neganswer = 1;
if (!extract_name(header, plen, &p2, name, 1, 10)) if (!extract_name(header, plen, &p2, name, 1, 10))
return STAT_BOGUS; /* bad packet */ return STAT_BOGUS; /* bad packet */
/* NXDOMAIN or NODATA reply, unanswered question is (name, qclass, qtyp e) */ /* NXDOMAIN or NODATA reply, unanswered question is (name, qclass, qtyp e) */
/* For anything other than a DS record, this situation is OK if either /* 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. */ the answer is in an unsigned zone, or there's a NSEC records. */
if (!prove_non_existence(header, plen, keyname, name, qtype, qclass, NU LL, nons)) if (!prove_non_existence(header, plen, keyname, name, qtype, qclass, NU LL, nons, nsec_ttl))
{ {
/* Empty DS without NSECS */ /* Empty DS without NSECS */
if (qtype == T_DS) if (qtype == T_DS)
return STAT_BOGUS; return STAT_BOGUS;
if ((rc = zone_status(name, qclass, keyname, now)) != STAT_SECURE) if ((rc = zone_status(name, qclass, keyname, now)) != STAT_SECURE)
{ {
if (class) if (class)
*class = qclass; /* Class for NEED_DS or NEED_KEY */ *class = qclass; /* Class for NEED_DS or NEED_KEY */
return rc; return rc;
 End of changes. 64 change blocks. 
136 lines changed or deleted 141 lines changed or added

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)