Check that unsigned replies come from unsigned zones if --dnssec-check-unsigned set.

This commit is contained in:
Simon Kelley
2014-02-28 18:10:55 +00:00
parent b8eac19177
commit 00a5b5d477
6 changed files with 469 additions and 110 deletions

View File

@@ -24,6 +24,14 @@ static unsigned short get_id(void);
static void free_frec(struct frec *f);
static struct randfd *allocate_rfd(int family);
#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(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class);
static int send_check_sign(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"
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,
@@ -250,6 +258,8 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
#endif
unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL);
(void)do_bit;
/* may be no servers available. */
if (!daemon->servers)
forward = NULL;
@@ -522,6 +532,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
size_t plen;
(void)ad_reqd;
(void) do_bit;
#ifdef HAVE_IPSET
/* Similar algorithm to search_servers. */
@@ -793,13 +804,27 @@ void reply_query(int fd, int family, time_t now)
else 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);
{
status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
if (status == STAT_NO_DS)
status = STAT_INSECURE;
}
else if (forward->flags & FREC_CHECK_NOSIGN)
status = do_check_sign(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
else
status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class);
{
status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, NULL);
if (status == STAT_NO_SIG)
{
if (option_bool(OPT_DNSSEC_NO_SIGN))
status = send_check_sign(now, header, n, daemon->namebuff, daemon->keyname);
else
status = STAT_INSECURE;
}
}
/* 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_KEY)
if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG || status == STAT_NEED_KEY)
{
struct frec *new, *orig;
@@ -829,7 +854,7 @@ void reply_query(int fd, int family, time_t now)
#ifdef HAVE_IPV6
new->rfd6 = NULL;
#endif
new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY);
new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_CHECK_NOSIGN);
new->dependent = forward; /* to find query awaiting new one. */
forward->blocking_query = new; /* for garbage cleaning */
@@ -842,7 +867,10 @@ void reply_query(int fd, int family, time_t now)
}
else
{
new->flags |= FREC_DS_QUERY;
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);
}
@@ -906,11 +934,26 @@ void reply_query(int fd, int family, time_t now)
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);
{
status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
if (status == STAT_NO_DS)
status = STAT_INSECURE;
}
else if (forward->flags & FREC_CHECK_NOSIGN)
status = do_check_sign(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
else
status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class);
if (status == STAT_NEED_DS || status == STAT_NEED_KEY)
{
status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, NULL);
if (status == STAT_NO_SIG)
{
if (option_bool(OPT_DNSSEC_NO_SIGN))
status = send_check_sign(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;
}
}
@@ -1207,6 +1250,164 @@ void receive_query(struct listener *listen, time_t now)
}
#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(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname)
{
struct crec *crecp;
char *name_start = name;
int status = dnssec_chase_cname(now, header, plen, name, keyname);
if (status != STAT_INSECURE)
return status;
while (1)
{
crecp = cache_find_by_name(NULL, name_start, now, F_DS);
if (crecp && (crecp->flags & F_DNSSECOK))
return (crecp->flags & F_NEG) ? STAT_INSECURE : STAT_BOGUS;
if (crecp && (crecp->flags & F_NEG) && (name_start = strchr(name_start, '.')))
{
name_start++; /* chop a label off and try again */
continue;
}
strcpy(keyname, name_start);
return STAT_NEED_DS_NEG;
}
}
/* Got answer to DS query from send_check_sign, check for proven non-existence, or make the next DS query to try. */
static int do_check_sign(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
{
char *name_start;
unsigned char *p;
int status = dnssec_validate_ds(now, header, plen, name, keyname, class);
if (status != STAT_INSECURE)
{
if (status == STAT_NO_DS)
status = STAT_INSECURE;
return status;
}
p = (unsigned char *)(header+1);
if (extract_name(header, plen, &p, name, 1, 4) &&
(name_start = strchr(name, '.')))
{
name_start++; /* chop a label off and try again */
strcpy(keyname, name_start);
return STAT_NEED_DS_NEG;
}
return STAT_BOGUS;
}
/* Move toward 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;
unsigned char *p = (unsigned char *)(header+1);
int status;
char *name_start = name;
/* 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;
while (1)
{
unsigned char *newhash, hash[HASH_SIZE];
unsigned char c1, c2;
struct crec *crecp = cache_find_by_name(NULL, name_start, now, F_DS);
if (--(*keycount) == 0)
return STAT_BOGUS;
if (crecp && (crecp->flags & F_DNSSECOK))
{
free(packet);
return (crecp->flags & F_NEG) ? STAT_INSECURE : STAT_BOGUS;
}
/* If we have cached insecurely that a DS doesn't exist,
ise that is a hit for where to start looking for the secure one */
if (crecp && (crecp->flags & F_NEG) && (name_start = strchr(name_start, '.')))
{
name_start++; /* chop a label off and try again */
continue;
}
m = dnssec_generate_query(header, ((char *) header) + 65536, name_start, class, T_DS, &server->addr);
/* We rely on the question section coming back unchanged, ensure it is with the hash. */
if ((newhash = hash_questions(header, (unsigned int)m, name)))
memcpy(hash, newhash, HASH_SIZE);
*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;
newhash = hash_questions(header, (unsigned int)m, name);
if (newhash && memcmp(hash, newhash, HASH_SIZE) == 0)
{
/* Note this trashes all three name workspaces */
status = tcp_key_recurse(now, STAT_NEED_DS_NEG, header, m, class, name, keyname, server, keycount);
/* We've found a DS which proves the bit of the DNS where the
original query is, is unsigned, so the answer is OK,
if unvalidated. */
if (status == STAT_NO_DS)
{
free(packet);
return STAT_INSECURE;
}
/* No DS, not got to DNSSEC-land yet, go up. */
if (status == STAT_INSECURE)
{
p = (unsigned char *)(header+1);
if (extract_name(header, plen, &p, name, 1, 4) &&
(name_start = strchr(name, '.')))
{
name_start++; /* chop a label off and try again */
continue;
}
}
}
}
free(packet);
return STAT_BOGUS;
}
}
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)
{
@@ -1219,11 +1420,27 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
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);
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 && new_status == STAT_NO_DS)
new_status = STAT_INSECURE;
}
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);
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;
}
}
/* 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)
@@ -1253,7 +1470,9 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
{
m = (c1 << 8) | c2;
if (tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, keycount) == STAT_SECURE)
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.
@@ -1261,11 +1480,27 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
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);
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 && new_status == STAT_NO_DS)
new_status = STAT_INSECURE; /* Validated no DS */
}
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);
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;
}
@@ -1273,7 +1508,6 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
free(packet);
}
return new_status;
}
#endif