Validate replies with getdns_validate_dnssec

You can feed it the replies_tree as the records to validate list
This commit is contained in:
Willem Toorop 2015-07-02 00:25:41 +02:00
parent f92dd5ac0d
commit 6cffc4792b
3 changed files with 197 additions and 75 deletions

View File

@ -698,7 +698,6 @@ static int bitmap_contains_rrtype(priv_getdns_rdf_iter *bitmap, uint16_t rr_type
uint8_t window = rr_type >> 8;
uint8_t subtype = rr_type & 0xFF;
DEBUG_SEC("bitmap: %p, type: %d\n", bitmap, (int)rr_type);
if (!bitmap)
return 0;
dptr = bitmap->pos;
@ -884,14 +883,28 @@ static uint8_t *name2nsec3_label(
return NULL;
}
static uint8_t *_dname_label_copy(uint8_t *dst, uint8_t *src, size_t dst_len)
{
uint8_t *r = dst, i;
if (!src || *src + 1 > dst_len)
return NULL;
for (i = *src + 1; i > 0; i--)
*dst++ = tolower(*src++);
return r;
}
static int nsec3_matches_name(getdns_rrset *nsec3, uint8_t *name)
{
uint8_t label[64];
uint8_t label[64], owner[64];
if (name2nsec3_label(nsec3, name, label, sizeof(label)))
if (name2nsec3_label(nsec3, name, label, sizeof(label))
&& _dname_label_copy(owner, nsec3->name, sizeof(owner)))
return *nsec3->name == label[0] /* Labels same size? */
&& memcmp(nsec3->name + 1, label + 1, label[0]) == 0;
&& memcmp(owner + 1, label + 1, label[0]) == 0;
return 0;
}
@ -913,7 +926,7 @@ static int nsec3_covers_name(getdns_rrset *nsec3, uint8_t *name, int *opt_out)
|| (nsz = gldns_b32_ntop_extended_hex(rdf->pos + 1, *rdf->pos,
(char *)next + 1, sizeof(next)-2)) < 0
|| *nsec3->name > sizeof(owner) - 2
|| !memcpy(owner, nsec3->name, *nsec3->name + 1)) {
|| !_dname_label_copy(owner, nsec3->name, sizeof(owner)-1)) {
DEBUG_SEC("Error getting NSEC3 owner & next labels\n");
return 0;
@ -1583,6 +1596,7 @@ static void val_chain_node_soa_cb(getdns_dns_req *dnsreq)
! priv_getdns_dname_equal(node->ds.name, rrset->name))
node = node->parent;
if (node)
val_chain_sched_node(node);
} else
val_chain_sched_soa_node(node->parent);
@ -1614,6 +1628,14 @@ static chain_head *add_rrset2val_chain(struct mem_funcs *mf,
max_head = NULL;
max_labels = 0;
for (head = *chain_p; head; head = head->next) {
/* Also, try to prevent adding double rrsets */
if ( rrset->rr_class == head->rrset.rr_class
&& rrset->rr_type == head->rrset.rr_type
&& rrset->pkt == head->rrset.pkt
&& rrset->pkt_len == head->rrset.pkt_len
&& priv_getdns_dname_equal(rrset->name, head->rrset.name))
return NULL;
for (label = labels; label < last_label; label++) {
if (! is_subdomain(*label, head->rrset.name))
break;
@ -1715,7 +1737,6 @@ static chain_head *add_rrset2val_chain(struct mem_funcs *mf,
*/
static void add_pkt2val_chain(struct mem_funcs *mf,
chain_head **chain_p, uint8_t *pkt, size_t pkt_len,
uint8_t *qname, uint16_t qtype, uint16_t qclass,
getdns_network_req *netreq)
{
rrset_iter *i, i_spc;
@ -1723,36 +1744,14 @@ static void add_pkt2val_chain(struct mem_funcs *mf,
rrsig_iter *rrsig, rrsig_spc;
size_t n_rrsigs;
chain_head *head;
getdns_rrset empty_rrset;
getdns_rrset q_rrset;
uint8_t cname_spc[256];
size_t cname_len = sizeof(cname_spc);
size_t anti_loop;
priv_getdns_rdf_iter rdf_spc, *rdf;
rrtype_iter *rr, rr_spc;
assert(pkt);
assert(pkt_len >= GLDNS_HEADER_SIZE);
/* On empty packet, find SOA (zonecut) for the qname and query DS */
/* For all things with signatures, create a chain */
/* For all things without signature, find SOA (zonecut) and query DS */
if (GLDNS_ANCOUNT(pkt) == 0 && GLDNS_NSCOUNT(pkt) == 0 && qname) {
empty_rrset.name = qname;
empty_rrset.rr_class = qclass;
empty_rrset.rr_type = qtype;
empty_rrset.pkt = pkt;
empty_rrset.pkt_len = pkt_len;
head = add_rrset2val_chain(mf, chain_p, &empty_rrset, netreq);
val_chain_sched_soa(head, empty_rrset.name);
return;
}
for ( i = rrset_iter_init(&i_spc, pkt, pkt_len)
; i
; i = rrset_iter_next(i)) {
@ -1760,7 +1759,9 @@ static void add_pkt2val_chain(struct mem_funcs *mf,
rrset = rrset_iter_value(i);
debug_sec_print_rrset("rrset: ", rrset);
head = add_rrset2val_chain(mf, chain_p, rrset, netreq);
if (!(head = add_rrset2val_chain(mf, chain_p, rrset, netreq)))
continue;
for ( rrsig = rrsig_iter_init(&rrsig_spc, rrset), n_rrsigs = 0
; rrsig
; rrsig = rrsig_iter_next(rrsig), n_rrsigs++) {
@ -1772,16 +1773,35 @@ static void add_pkt2val_chain(struct mem_funcs *mf,
if (rrset->rr_type == GETDNS_RRTYPE_SOA)
val_chain_sched(head, rrset->name);
else if (rrset->rr_type == GETDNS_RRTYPE_CNAME)
val_chain_sched_soa(head, rrset->name + *rrset->name + 1);
else
val_chain_sched_soa(head, rrset->name);
}
/* For NOERROR/NODATA or NXDOMAIN responses add extra rrset to
}
/* For NOERROR/NODATA or NXDOMAIN responses add extra rrset to
* the validation chain so the denial of existence will be
* checked eventually.
* But only if we knew the question of course...
* But only if we know the question of course...
*/
if (!qname)
return;
static void add_question2val_chain(struct mem_funcs *mf,
chain_head **chain_p, uint8_t *pkt, size_t pkt_len,
uint8_t *qname, uint16_t qtype, uint16_t qclass,
getdns_network_req *netreq)
{
getdns_rrset q_rrset;
uint8_t cname_spc[256];
size_t cname_len = sizeof(cname_spc);
size_t anti_loop;
priv_getdns_rdf_iter rdf_spc, *rdf;
rrtype_iter *rr, rr_spc;
chain_head *head;
assert(pkt);
assert(pkt_len >= GLDNS_HEADER_SIZE);
assert(qname);
/* First find the canonical name for the question */
q_rrset.name = qname;
@ -1798,25 +1818,37 @@ static void add_pkt2val_chain(struct mem_funcs *mf,
q_rrset.name = priv_getdns_rdf_if_or_as_decompressed(
rdf, cname_spc, &cname_len);
}
q_rrset.rr_type = qtype;
if (!(rr = rrtype_iter_init(&rr_spc, &q_rrset))) {
/* No answer for the question. Add a head for this rrset
* anyway, to validate proof of non-existance, or to find
* proof that the packet is insecure.
*/
debug_sec_print_rrset("Adding NX rrset: ", &q_rrset);
add_rrset2val_chain(mf, chain_p, &q_rrset, netreq);
head = add_rrset2val_chain(mf, chain_p, &q_rrset, netreq);
/* On empty packet, find SOA (zonecut) for the qname */
if (head && GLDNS_ANCOUNT(pkt) == 0 && GLDNS_NSCOUNT(pkt) == 0)
val_chain_sched_soa(head, q_rrset.name);
}
}
static void get_val_chain(getdns_dns_req *dnsreq)
void priv_getdns_get_validation_chain(getdns_dns_req *dnsreq)
{
getdns_network_req *netreq, **netreq_p;
chain_head *chain = NULL;
for (netreq_p = dnsreq->netreqs; (netreq = *netreq_p) ; netreq_p++) {
add_pkt2val_chain( &dnsreq->my_mf, &chain
, netreq->response, netreq->response_len
, netreq
);
add_question2val_chain( &dnsreq->my_mf, &chain
, netreq->response, netreq->response_len
, netreq->owner->name
, netreq->request_type, netreq->request_class
, netreq->request_type
, netreq->request_class
, netreq
);
}
@ -1828,18 +1860,6 @@ static void get_val_chain(getdns_dns_req *dnsreq)
create_getdns_response(dnsreq));
}
/******************************************************************************/
/***************************** *******************************/
/***************************** NEW CHAIN CODE *******************************/
/***************************** (above) *******************************/
/***************************** *******************************/
/******************************************************************************/
void priv_getdns_get_validation_chain(getdns_dns_req *dns_req)
{
get_val_chain(dns_req);
}
/*
* getdns_validate_dnssec
*
@ -1878,7 +1898,7 @@ getdns_validate_dnssec(getdns_list *records_to_validate,
return GETDNS_RETURN_INVALID_PARAMETER;
mf = &records_to_validate->mf;
/* First convert everything to wire formatxi
/* First convert everything to wire format
*/
if (!(to_val = _getdns_list2wire(records_to_validate,
to_val_buf, &to_val_len, mf)))
@ -1892,19 +1912,39 @@ getdns_validate_dnssec(getdns_list *records_to_validate,
tas_buf, &tas_len, mf)))
goto exit_free_support;
if (GLDNS_QDCOUNT(to_val) > 0
&& (rr = priv_getdns_rr_iter_init(&rr_spc, to_val, to_val_len))
&& (qname = priv_getdns_owner_if_or_as_decompressed(
if (GLDNS_QDCOUNT(to_val) == 0 && GLDNS_ANCOUNT(to_val) == 0) {
r = GETDNS_RETURN_GENERIC_ERROR;
goto exit_free_tas;
}
chain = NULL;
/* First create a chain (head + nodes) for each rr in the answer and
* authority section of the fake to_val packet.
*/
add_pkt2val_chain(mf, &chain, to_val, to_val_len, NULL);
/* When records_to_validate contained replies, like the replies_tree
* list in a response dict, the returned wireformat packet may contain
* multiple questions in the question section. For each reply one
* question.
*
* For each question in the question section add a chain head.
*/
for ( rr = priv_getdns_rr_iter_init(&rr_spc, to_val, to_val_len)
; rr && priv_getdns_rr_iter_section(rr) == GLDNS_SECTION_QUESTION
; rr = priv_getdns_rr_iter_next(rr) ) {
if ((qname = priv_getdns_owner_if_or_as_decompressed(
rr, qname_spc, &qname_len))
&& rr->nxt >= rr->rr_type + 4) {
qtype = gldns_read_uint16(rr->rr_type);
qclass = gldns_read_uint16(rr->rr_type + 2);
}
/* Create the chain hierarchy where all head's need to be validated. */
chain = NULL;
add_pkt2val_chain(mf, &chain, to_val, to_val_len,
add_question2val_chain(mf, &chain, to_val, to_val_len,
qname, qtype, qclass, NULL);
}
}
/* Now equip the nodes with the support records wireformat */
for (head = chain; head; head = head->next) {
@ -1921,6 +1961,7 @@ getdns_validate_dnssec(getdns_list *records_to_validate,
r = (getdns_return_t)chain_validate_dnssec(
chain, rrset_iter_init(&tas_iter, tas, tas_len));
exit_free_tas:
if (tas != tas_buf)
GETDNS_FREE(*mf, tas);
exit_free_support:

View File

@ -141,11 +141,14 @@ static getdns_return_t validate_chain(getdns_dict *response)
getdns_list *validation_chain;
getdns_list *replies_tree;
getdns_dict *reply;
getdns_list *answer;
getdns_list *to_validate;
getdns_list *trust_anchor;
size_t i;
int s;
if (!(to_validate = getdns_list_create()))
return GETDNS_RETURN_MEMORY_ERROR;
if (!(trust_anchor = getdns_root_trust_anchor(NULL)))
return GETDNS_RETURN_GENERIC_ERROR;
@ -157,16 +160,39 @@ static getdns_return_t validate_chain(getdns_dict *response)
response, "replies_tree", &replies_tree)))
return r;
fprintf(stdout, "replies_tree %zu, dnssec_status: ", i);
switch ((s = getdns_validate_dnssec(
replies_tree, validation_chain, trust_anchor))) {
case GETDNS_DNSSEC_SECURE:
fprintf(stdout, "GETDNS_DNSSEC_SECURE\n");
break;
case GETDNS_DNSSEC_BOGUS:
fprintf(stdout, "GETDNS_DNSSEC_BOGUS\n");
break;
case GETDNS_DNSSEC_INDETERMINATE:
fprintf(stdout, "GETDNS_DNSSEC_INDETERMINATE\n");
break;
case GETDNS_DNSSEC_INSECURE:
fprintf(stdout, "GETDNS_DNSSEC_INSECURE\n");
break;
case GETDNS_DNSSEC_NOT_PERFORMED:
fprintf(stdout, "GETDNS_DNSSEC_NOT_PERFORMED\n");
break;
default:
fprintf(stdout, "%d\n", (int)s);
}
i = 0;
while (!(r = getdns_list_get_dict(replies_tree, i++, &reply))) {
if ((r = getdns_dict_get_list(reply, "answer", &answer)))
if ((r = getdns_list_set_dict(to_validate, 0, reply)))
return r;
fprintf( stdout
, "reply %zu, getdns_validate_dnssec returned: ", i);
, "reply %zu, dnssec_status: ", i);
switch ((s = getdns_validate_dnssec(
answer, validation_chain, trust_anchor))) {
to_validate, validation_chain, trust_anchor))) {
case GETDNS_DNSSEC_SECURE:
fprintf(stdout, "GETDNS_DNSSEC_SECURE\n");

View File

@ -992,9 +992,10 @@ priv_getdns_validate_dname(const char* dname) {
static void _getdns_list2wire_buf(gldns_buffer *buf, getdns_list *l)
{
getdns_dict *rr_dict;
getdns_dict *rr_dict, *q_dict;
getdns_list *section;
getdns_return_t r;
size_t i, pkt_start, ancount;
size_t i, j, pkt_start, ancount, qdcount;
uint32_t qtype, qclass;
getdns_bindata *qname;
@ -1004,7 +1005,7 @@ static void _getdns_list2wire_buf(gldns_buffer *buf, getdns_list *l)
gldns_buffer_write_u32(buf, 0);
gldns_buffer_write_u32(buf, 0);
for ( i = 0
for ( i = 0, qdcount = 0
; (r = getdns_list_get_dict(l, i, &rr_dict))
!= GETDNS_RETURN_NO_SUCH_LIST_ITEM
; i++ ) {
@ -1015,6 +1016,14 @@ static void _getdns_list2wire_buf(gldns_buffer *buf, getdns_list *l)
else
break;
}
if (getdns_dict_get_dict(rr_dict, "question", &q_dict)
== GETDNS_RETURN_GOOD) {
/* rr_dict was actually a reply
* with a question section/rr_dict
*/
rr_dict = q_dict;
}
if (getdns_dict_get_int(rr_dict, "qtype", &qtype) ||
getdns_dict_get_bindata(rr_dict, "qname", &qname))
continue;
@ -1022,9 +1031,9 @@ static void _getdns_list2wire_buf(gldns_buffer *buf, getdns_list *l)
gldns_buffer_write(buf, qname->data, qname->size);
gldns_buffer_write_u16(buf, (uint16_t)qtype);
gldns_buffer_write_u16(buf, (uint16_t)qclass);
gldns_buffer_write_u16_at(buf, pkt_start+GLDNS_QDCOUNT_OFF, 1);
break;
qdcount++;
}
gldns_buffer_write_u16_at(buf, pkt_start+GLDNS_QDCOUNT_OFF, qdcount);
for ( i = 0, ancount = 0
; (r = getdns_list_get_dict(l, i, &rr_dict))
!= GETDNS_RETURN_NO_SUCH_LIST_ITEM
@ -1036,8 +1045,54 @@ static void _getdns_list2wire_buf(gldns_buffer *buf, getdns_list *l)
else
break;
}
if (priv_getdns_rr_dict2wire(rr_dict, buf) == GETDNS_RETURN_GOOD)
if (priv_getdns_rr_dict2wire(rr_dict, buf)
== GETDNS_RETURN_GOOD) {
ancount++;
continue;
}
if (getdns_dict_get_list(rr_dict, "answer", &section)
== GETDNS_RETURN_GOOD) {
for ( j = 0
; (r = getdns_list_get_dict(section, j, &q_dict))
!= GETDNS_RETURN_NO_SUCH_LIST_ITEM
; j++ ) {
if (r) {
if (r ==
GETDNS_RETURN_WRONG_TYPE_REQUESTED)
continue;
else
break;
}
if (priv_getdns_rr_dict2wire(q_dict, buf)
== GETDNS_RETURN_GOOD)
ancount++;
}
}
if (getdns_dict_get_list(rr_dict, "authority", &section)
== GETDNS_RETURN_GOOD) {
for ( j = 0
; (r = getdns_list_get_dict(section, j, &q_dict))
!= GETDNS_RETURN_NO_SUCH_LIST_ITEM
; j++ ) {
if (r) {
if (r ==
GETDNS_RETURN_WRONG_TYPE_REQUESTED)
continue;
else
break;
}
if (priv_getdns_rr_dict2wire(q_dict, buf)
== GETDNS_RETURN_GOOD)
ancount++;
}
}
}
gldns_buffer_write_u16_at(buf, pkt_start+GLDNS_ANCOUNT_OFF, ancount);
}