mirror of
https://github.com/pi-hole/dnsmasq.git
synced 2025-12-19 18:28:25 +00:00
DNSSEC: implement RFC-4036 para 5.3.3. rules on TTL values.
This commit is contained in:
@@ -125,7 +125,7 @@ int main (int argc, char **argv)
|
|||||||
daemon->workspacename = safe_malloc(MAXDNAME * 2);
|
daemon->workspacename = safe_malloc(MAXDNAME * 2);
|
||||||
/* one char flag per possible RR in answer section (may get extended). */
|
/* one char flag per possible RR in answer section (may get extended). */
|
||||||
daemon->rr_status_sz = 64;
|
daemon->rr_status_sz = 64;
|
||||||
daemon->rr_status = safe_malloc(daemon->rr_status_sz);
|
daemon->rr_status = safe_malloc(sizeof(*daemon->rr_status) * daemon->rr_status_sz);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -1079,7 +1079,7 @@ extern struct daemon {
|
|||||||
#ifdef HAVE_DNSSEC
|
#ifdef HAVE_DNSSEC
|
||||||
char *keyname; /* MAXDNAME size buffer */
|
char *keyname; /* MAXDNAME size buffer */
|
||||||
char *workspacename; /* ditto */
|
char *workspacename; /* ditto */
|
||||||
char *rr_status; /* flags for individual RRs */
|
unsigned long *rr_status; /* ceiling in TTL from DNSSEC or zero for insecure */
|
||||||
int rr_status_sz;
|
int rr_status_sz;
|
||||||
int dnssec_no_time_check;
|
int dnssec_no_time_check;
|
||||||
int back_to_the_future;
|
int back_to_the_future;
|
||||||
|
|||||||
92
src/dnssec.c
92
src/dnssec.c
@@ -186,10 +186,8 @@ int setup_timestamp(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* 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
|
||||||
@@ -211,12 +209,15 @@ static int check_date_range(u32 date_start, u32 date_end)
|
|||||||
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;
|
||||||
@@ -390,10 +391,10 @@ static int explore_rrset(struct dns_header *header, size_t plen, int class, int
|
|||||||
|
|
||||||
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))
|
||||||
@@ -456,7 +457,7 @@ static int explore_rrset(struct dns_header *header, size_t plen, int class, int
|
|||||||
sigs[sigidx++] = pdata;
|
sigs[sigidx++] = pdata;
|
||||||
}
|
}
|
||||||
|
|
||||||
p = pdata + 2; /* restore for ADD_RDLEN */
|
p = pdata + 6; /* restore for ADD_RDLEN */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -485,16 +486,22 @@ static int explore_rrset(struct dns_header *header, size_t plen, int class, int
|
|||||||
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, int class, int type, int sigidx, int rrsetidx,
|
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)
|
char *name, char *keyname, char **wildcard_out, struct 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;
|
||||||
|
|
||||||
@@ -513,9 +520,10 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
|||||||
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;
|
||||||
|
|
||||||
@@ -530,7 +538,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
|||||||
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_expiration)) ||
|
||||||
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))
|
||||||
@@ -540,6 +548,18 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
|
|||||||
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);
|
||||||
|
|
||||||
@@ -653,7 +673,8 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
|
|||||||
{
|
{
|
||||||
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;
|
||||||
union all_addr a;
|
union all_addr a;
|
||||||
|
|
||||||
@@ -752,7 +773,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
|
|||||||
explore_rrset(header, plen, class, T_DNSKEY, name, keyname, &sigcnt, &rrcnt) &&
|
explore_rrset(header, plen, class, T_DNSKEY, name, keyname, &sigcnt, &rrcnt) &&
|
||||||
sigcnt != 0 && rrcnt != 0 &&
|
sigcnt != 0 && rrcnt != 0 &&
|
||||||
validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname,
|
validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname,
|
||||||
NULL, key, rdlen - 4, algo, keytag) == STAT_SECURE)
|
NULL, key, rdlen - 4, algo, keytag, &sig_ttl) == STAT_SECURE)
|
||||||
{
|
{
|
||||||
valid = 1;
|
valid = 1;
|
||||||
break;
|
break;
|
||||||
@@ -780,6 +801,10 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
|
|||||||
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 */
|
||||||
|
|
||||||
@@ -1498,7 +1523,8 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
|
|||||||
|
|
||||||
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, ttl;
|
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)))
|
||||||
@@ -1521,7 +1547,12 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
|
|||||||
if (class == qclass && (type == T_NSEC || type == T_NSEC3))
|
if (class == qclass && (type == T_NSEC || type == T_NSEC3))
|
||||||
{
|
{
|
||||||
if (nsec_ttl)
|
if (nsec_ttl)
|
||||||
*nsec_ttl = 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)
|
||||||
@@ -1703,7 +1734,7 @@ static int zone_status(char *name, int class, char *keyname, time_t now)
|
|||||||
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 for any which aren't.
|
answer and auth sections. This is set to 1 for each RR which is validated, and 0 for any which aren't.
|
||||||
|
|
||||||
When validating replies to DS records, we're only interested in the NSEC{3} RRs in the auth section.
|
When validating replies to DS records, we're only interested in the NSEC{3} RRs in the auth section.
|
||||||
Other RRs in that section missing sigs will not cause am INSECURE reply. We determine this mode
|
Other RRs in that section missing sigs will not cause am INSECURE reply. We determine this mode
|
||||||
@@ -1721,19 +1752,19 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
|||||||
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(header->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) + 64;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
@@ -1824,12 +1855,9 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
|||||||
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 */
|
||||||
@@ -1881,8 +1909,9 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
|||||||
/* 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)
|
||||||
{
|
{
|
||||||
@@ -1894,8 +1923,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
|
|||||||
/* 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
|
||||||
|
|||||||
@@ -608,7 +608,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
|
|||||||
#ifdef HAVE_DNSSEC
|
#ifdef HAVE_DNSSEC
|
||||||
if (option_bool(OPT_DNSSEC_VALID))
|
if (option_bool(OPT_DNSSEC_VALID))
|
||||||
for (i = 0; i < ntohs(header->ancount); i++)
|
for (i = 0; i < ntohs(header->ancount); i++)
|
||||||
if (daemon->rr_status[i])
|
if (daemon->rr_status[i] != 0)
|
||||||
return 0;
|
return 0;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -681,13 +681,16 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
|
|||||||
if (!extract_name(header, qlen, &p1, name, 1, 0))
|
if (!extract_name(header, qlen, &p1, name, 1, 0))
|
||||||
return 0;
|
return 0;
|
||||||
#ifdef HAVE_DNSSEC
|
#ifdef HAVE_DNSSEC
|
||||||
if (option_bool(OPT_DNSSEC_VALID) && daemon->rr_status[j])
|
if (option_bool(OPT_DNSSEC_VALID) && daemon->rr_status[j] != 0)
|
||||||
{
|
{
|
||||||
/* validated RR anywhere in CNAME chain, don't cache. */
|
/* validated RR anywhere in CNAME chain, don't cache. */
|
||||||
if (cname_short || aqtype == T_CNAME)
|
if (cname_short || aqtype == T_CNAME)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
secflag = F_DNSSECOK;
|
secflag = F_DNSSECOK;
|
||||||
|
/* limit TTL based on signature. */
|
||||||
|
if (daemon->rr_status[j] < cttl)
|
||||||
|
cttl = daemon->rr_status[j];
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -768,8 +771,14 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
|
|||||||
if (aqclass == C_IN && res != 2 && (aqtype == T_CNAME || aqtype == qtype))
|
if (aqclass == C_IN && res != 2 && (aqtype == T_CNAME || aqtype == qtype))
|
||||||
{
|
{
|
||||||
#ifdef HAVE_DNSSEC
|
#ifdef HAVE_DNSSEC
|
||||||
if (option_bool(OPT_DNSSEC_VALID) && daemon->rr_status[j])
|
if (option_bool(OPT_DNSSEC_VALID) && daemon->rr_status[j] != 0)
|
||||||
|
{
|
||||||
secflag = F_DNSSECOK;
|
secflag = F_DNSSECOK;
|
||||||
|
|
||||||
|
/* limit TTl based on sig. */
|
||||||
|
if (daemon->rr_status[j] < attl)
|
||||||
|
attl = daemon->rr_status[j];
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
if (aqtype == T_CNAME)
|
if (aqtype == T_CNAME)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user