diff --git a/src/getdns/getdns.h b/src/getdns/getdns.h index 3cdc1bec..68cc0695 100644 --- a/src/getdns/getdns.h +++ b/src/getdns/getdns.h @@ -779,9 +779,9 @@ char *getdns_convert_ulabel_to_alabel(const char *ulabel); char *getdns_convert_alabel_to_ulabel(const char *alabel); getdns_return_t -getdns_validate_dnssec(struct getdns_bindata *record_to_validate, - struct getdns_list *bundle_of_support_records, - struct getdns_list *trust_anchor_rdatas); +getdns_validate_dnssec(struct getdns_list *to_validate, + struct getdns_list *support_records, + struct getdns_list *trust_anchors); /** * creates a string that describes the dictionary in a human readable form diff --git a/src/rr-dict.c b/src/rr-dict.c index ae458366..6a218168 100644 --- a/src/rr-dict.c +++ b/src/rr-dict.c @@ -37,6 +37,7 @@ #include "rr-dict.h" #include "types-internal.h" #include "context.h" +#include "dict.h" #define ALEN(a) (sizeof(a)/sizeof(a[0])) @@ -1023,15 +1024,157 @@ priv_getdns_create_reply_question_dict( return r; } +static getdns_return_t priv_getdns_construct_wire_rdata_from_rdata( + struct getdns_dict *rdata, uint32_t rr_type, + uint8_t **wire, size_t *wire_size) +{ + getdns_return_t r = GETDNS_RETURN_GOOD; + const ldns_rr_descriptor *rr_descript; + const struct rr_def *def; + size_t i, size; + struct getdns_bindata *bindata; + uint32_t value; + uint8_t *ptr; + + assert(rdata); + assert(wire); + assert(wire_size); + + def = rr_def_lookup(rr_type); + rr_descript = ldns_rr_descript(rr_type); + + /* First calculate needed size */ + size = 0; + for (i = 0; i < def->n_rdata_fields && r == GETDNS_RETURN_GOOD; i++) { + switch (def->rdata[i].type) { + case t_bindata: r = getdns_dict_get_bindata(rdata, + def->rdata[i].name, &bindata); + if (r) + break; + size += bindata->size; + break; + case t_int : switch (ldns_rr_descriptor_field_type( + rr_descript, i)) { + + case LDNS_RDF_TYPE_CLASS: + case LDNS_RDF_TYPE_ALG : + case LDNS_RDF_TYPE_INT8 : size += 1; + break; + case LDNS_RDF_TYPE_TYPE : + case LDNS_RDF_TYPE_CERT_ALG: + case LDNS_RDF_TYPE_INT16: size += 2; + break; + case LDNS_RDF_TYPE_TIME : + case LDNS_RDF_TYPE_PERIOD: + case LDNS_RDF_TYPE_INT32: size += 4; + break; + default: r = GETDNS_RETURN_GENERIC_ERROR; + break; + } + break; + default : r = GETDNS_RETURN_GENERIC_ERROR; + break; + } + } + *wire_size = size + 2; + *wire = ptr = GETDNS_XMALLOC(rdata->mf, uint8_t, size + 2); + if (! ptr) + return GETDNS_RETURN_MEMORY_ERROR; + + ptr[0] = (uint8_t) (size >> 8) & 0xff; + ptr[1] = (uint8_t) size & 0xff; + ptr += 2; + for (i = 0; i < def->n_rdata_fields && r == GETDNS_RETURN_GOOD; i++) { + switch (def->rdata[i].type) { + case t_bindata: r = getdns_dict_get_bindata(rdata, + def->rdata[i].name, &bindata); + if (r) + break; + (void) memcpy(ptr, bindata->data, + bindata->size); + ptr += bindata->size; + break; + case t_int : r = getdns_dict_get_int(rdata, + def->rdata[i].name, &value); + if (r) + break; + + switch (ldns_rr_descriptor_field_type( + rr_descript, i)) { + + case LDNS_RDF_TYPE_CLASS: + case LDNS_RDF_TYPE_ALG : + case LDNS_RDF_TYPE_INT8 : ptr[0] = (uint8_t) + value & 0xff; + ptr += 1; + break; + case LDNS_RDF_TYPE_TYPE : + case LDNS_RDF_TYPE_CERT_ALG: + case LDNS_RDF_TYPE_INT16: ptr[0] = (uint8_t) + (value>>8)&0xff; + ptr[1] = (uint8_t) + value & 0xff; + ptr += 2; + break; + case LDNS_RDF_TYPE_TIME : + case LDNS_RDF_TYPE_PERIOD: + case LDNS_RDF_TYPE_INT32: ptr[0] = (uint8_t) + (value>>24)&0xff; + ptr[1] = (uint8_t) + (value>>16)&0xff; + ptr[2] = (uint8_t) + (value>>8)&0xff; + ptr[3] = (uint8_t) + value & 0xff; + ptr += 4; + break; + default: r = GETDNS_RETURN_GENERIC_ERROR; + break; + } + break; + default : r = GETDNS_RETURN_GENERIC_ERROR; + break; + } + } + if (r) + GETDNS_FREE(rdata->mf, ptr); + return r; +} + +static getdns_return_t +priv_getdns_dict_get_raw_rdata(struct getdns_dict *rdata, + uint8_t **wire, size_t *wire_size) +{ + getdns_return_t r; + struct getdns_bindata *bindata; + + if ((r = getdns_dict_get_bindata(rdata, "rdata_raw", &bindata))) + return r; + + *wire_size = bindata->size + 2; + *wire = GETDNS_XMALLOC(rdata->mf, uint8_t, *wire_size); + if (! *wire) + return GETDNS_RETURN_MEMORY_ERROR; + + (*wire)[0] = (uint8_t) (bindata->size >> 8) & 0xff; + (*wire)[1] = (uint8_t) bindata->size & 0xff; + + (void) memcpy(*wire + 2, bindata->data, bindata->size); + return GETDNS_RETURN_GOOD; +} + getdns_return_t priv_getdns_create_rr_from_dict(struct getdns_dict *rr_dict, ldns_rr **rr) { getdns_return_t r = GETDNS_RETURN_GOOD; - struct getdns_bindata *name, *rdata_raw; + struct getdns_bindata *name; struct getdns_dict *rdata; uint32_t rr_type; ldns_rdf *owner; ldns_status s; + size_t pos; + uint8_t *wire; + size_t wire_size; assert(rr_dict); assert(rr); @@ -1060,14 +1203,35 @@ priv_getdns_create_rr_from_dict(struct getdns_dict *rr_dict, ldns_rr **rr) if (r != GETDNS_RETURN_GOOD) break; - r = getdns_dict_get_bindata(rdata, "rdata_raw", &rdata_raw); + //r = getdns_dict_get_bindata(rdata, "rdata_raw", &rdata_raw); + r = priv_getdns_dict_get_raw_rdata(rdata, &wire, &wire_size); + if (r == GETDNS_RETURN_NO_SUCH_DICT_NAME) { + r = priv_getdns_construct_wire_rdata_from_rdata( + rdata, rr_type, &wire, &wire_size); + } if (r != GETDNS_RETURN_GOOD) break; - - s = ldns_wire2rdf(*rr, rdata_raw->data, rdata_raw->size, 0); +#if 0 + fprintf(stderr, "wire_size: %d\n", (int)wire_size); + fprintf(stderr, "wire data: "); + for (size_t i = 0; i < wire_size; i++) { + if (i) { + if (i % 24 == 0) + fprintf(stderr, "\n "); + else if (i % 4 == 0) + fprintf(stderr, " "); + } + fprintf(stderr, "%.2x", wire[i]); + } + fprintf(stderr, "\n"); +#endif + pos = 0; + s = ldns_wire2rdf(*rr, wire, wire_size, &pos); + GETDNS_FREE(rr_dict->mf, wire); if (s == LDNS_STATUS_OK) return r; - + fprintf( stderr, "ldns error: %s (pos: %d)\n" + , ldns_get_errorstr_by_id(s), (int)pos); r = GETDNS_RETURN_GENERIC_ERROR; } while (0); ldns_rr_free(*rr); diff --git a/src/test/tests_dnssec.c b/src/test/tests_dnssec.c index f0fe9aae..1ecbad7c 100644 --- a/src/test/tests_dnssec.c +++ b/src/test/tests_dnssec.c @@ -46,28 +46,175 @@ #include #include +getdns_return_t create_root_trustanchor_list(struct getdns_list **tas) +{ + static const struct getdns_bindata root_dname = { 1, (uint8_t *) "" }; + static const int root_key_tag = 19036; + static const int root_algorithm = 8; + static const int root_digest_type = 2; + static const struct getdns_bindata root_digest = { 32, (uint8_t *) + "\x49\xaa\xc1\x1d\x7b\x6f\x64\x46\x70\x2e\x54\xa1\x60\x73\x71\x60" + "\x7a\x1a\x41\x85\x52\x00\xfd\x2c\xe1\xcd\xde\x32\xf2\x4e\x8f\xb5" + }; + + getdns_return_t r = GETDNS_RETURN_GOOD; + struct getdns_dict *ta; + struct getdns_dict *rdata; + + if (! tas) + return GETDNS_RETURN_INVALID_PARAMETER; + + ta = getdns_dict_create(); + if (! ta) + return GETDNS_RETURN_MEMORY_ERROR; + do { + r = getdns_dict_set_bindata(ta, "name", + (struct getdns_bindata *)&root_dname); + if (r != GETDNS_RETURN_GOOD) + break; + + r = getdns_dict_set_int(ta, "type", GETDNS_RRTYPE_DS); + if (r != GETDNS_RETURN_GOOD) + break; + + rdata = getdns_dict_create(); + if (! rdata) { + r = GETDNS_RETURN_MEMORY_ERROR; + break; + } + do { + r = getdns_dict_set_int(rdata, + "key_tag", root_key_tag); + if (r != GETDNS_RETURN_GOOD) + break; + + r = getdns_dict_set_int(rdata, + "algorithm", root_algorithm); + if (r != GETDNS_RETURN_GOOD) + break; + + r = getdns_dict_set_int(rdata, + "digest_type", root_digest_type); + if (r != GETDNS_RETURN_GOOD) + break; + + r = getdns_dict_set_bindata(rdata, + "digest", (struct getdns_bindata *)&root_digest); + if (r != GETDNS_RETURN_GOOD) + break; + + r = getdns_dict_set_dict(ta, "rdata", rdata); + } while(0); + + getdns_dict_destroy(rdata); + if (r != GETDNS_RETURN_GOOD) + break; + + *tas = getdns_list_create(); + if (! *tas) { + r = GETDNS_RETURN_MEMORY_ERROR; + break; + } + r = getdns_list_set_dict(*tas, 0, ta); + if (r == GETDNS_RETURN_GOOD) + return r; + + getdns_list_destroy(*tas); + } while(0); + getdns_dict_destroy(ta); + return r; +} + /* Set up the callback function, which will also do the processing of the results */ void -this_callbackfn(struct getdns_context *this_context, - uint16_t this_callback_type, - struct getdns_dict *this_response, - void *this_userarg, getdns_transaction_t this_transaction_id) +this_callbackfn(struct getdns_context *context, uint16_t callback_type, + struct getdns_dict *response, void *userarg, + getdns_transaction_t transaction_id) { - if (this_callback_type == GETDNS_CALLBACK_COMPLETE) { /* This is a callback with data */ - char *res = getdns_pretty_print_dict(this_response); - fprintf(stdout, "%s\n", res); - free(res); + struct getdns_list *validation_chain; + struct getdns_list *trust_anchors; + struct getdns_list *replies_tree; + size_t replies_tree_length, i; + struct getdns_dict *reply; + struct getdns_list *answer; + size_t answer_length; + getdns_return_t r; - } else if (this_callback_type == GETDNS_CALLBACK_CANCEL) - fprintf(stderr, - "The callback with ID %llu was cancelled. Exiting.", - (unsigned long long)this_transaction_id); - else - fprintf(stderr, - "The callback got a callback_type of %d. Exiting.", - this_callback_type); - getdns_dict_destroy(this_response); - (void) event_base_loopexit((struct event_base *)this_userarg, NULL); + do { + if (callback_type == GETDNS_CALLBACK_CANCEL) { + fprintf(stderr, + "The callback with ID %llu was cancelled.\n", + (long long unsigned int)transaction_id); + break; + } else if (callback_type != GETDNS_CALLBACK_COMPLETE) { + fprintf(stderr, + "The callback got a callback_type of %d.\n", + callback_type); + break; + } + r = getdns_dict_get_list(response, + "validation_chain", &validation_chain); + if (r != GETDNS_RETURN_GOOD) { + fprintf(stderr, + "Could not get \"validation_chain\" from response:" + " %d\n", r); + break; + } + r = getdns_dict_get_list(response, "replies_tree", &replies_tree); + if (r != GETDNS_RETURN_GOOD) { + fprintf(stderr, + "Could not get \"replies_tree\" from response:" + " %d\n", r); + break; + } + r = getdns_list_get_length(replies_tree, &replies_tree_length); + if (r != GETDNS_RETURN_GOOD) { + fprintf(stderr, + "Could not get length of the replies_tree:" + " %d\n", r); + break; + } + r = create_root_trustanchor_list(&trust_anchors); + if (r != GETDNS_RETURN_GOOD) { + fprintf(stderr, + "Error in creating trust_anchor:" + " %d\n", r); + break; + } + for (i = 0; i < replies_tree_length; i++) { + r = getdns_list_get_dict(replies_tree, i, &reply); + if (r != GETDNS_RETURN_GOOD) { + fprintf(stderr, + "Could not get \"reply\" from replies_tree:" + " %d\n", r); + break; + } + r = getdns_dict_get_list(reply, "answer", &answer); + if (r != GETDNS_RETURN_GOOD) { + fprintf(stderr, + "Could not get \"answer\" from reply:" + " %d\n", r); + break; + } + r = getdns_list_get_length(answer, &answer_length); + if (r != GETDNS_RETURN_GOOD) { + fprintf(stderr, + "Could not get length of answer list:" + " %d\n", r); + break; + } + if (answer_length == 0) + continue; + + r = getdns_validate_dnssec(answer, + validation_chain, trust_anchors); + printf("getdns_validate_dnssec returned: %d\n", r); + } + getdns_list_destroy(trust_anchors); + } while (0); + //printf("%s\n", getdns_pretty_print_dict(response)); + getdns_dict_destroy(response); + (void) event_base_loopexit((struct event_base *)userarg, NULL); } int @@ -83,7 +230,6 @@ main(int argc, char** argv) return (GETDNS_RETURN_GENERIC_ERROR); } getdns_context_set_timeout(this_context, 5000); - struct getdns_dict * this_extensions = getdns_dict_create(); getdns_return_t this_ret = getdns_dict_set_int(this_extensions, "dnssec_return_validation_chain", GETDNS_EXTENSION_TRUE); diff --git a/src/validate_dnssec.c b/src/validate_dnssec.c index 716aaab1..58b939e7 100644 --- a/src/validate_dnssec.c +++ b/src/validate_dnssec.c @@ -35,23 +35,295 @@ */ #include +#include +#include "rr-dict.h" /* stuff to make it compile pedantically */ #define UNUSED_PARAM(x) ((void)(x)) +static getdns_return_t +priv_getdns_rr_list_from_list(struct getdns_list *list, ldns_rr_list **rr_list) +{ + getdns_return_t r; + size_t i, l; + struct getdns_dict *rr_dict; + ldns_rr *rr; + + if ((r = getdns_list_get_length(list, &l))) + return r; + + if (! (*rr_list = ldns_rr_list_new())) + return GETDNS_RETURN_MEMORY_ERROR; + + for (i = 0; i < l; i++) { + if ((r = getdns_list_get_dict(list, i, &rr_dict))) + break; + + if ((r = priv_getdns_create_rr_from_dict(rr_dict, &rr))) + break; + + if (! ldns_rr_list_push_rr(*rr_list, rr)) { + ldns_rr_free(rr); + r = GETDNS_RETURN_GENERIC_ERROR; + break; + } + } + if (r) + ldns_rr_list_deep_free(*rr_list); + return r; +} + +static getdns_return_t +priv_getdns_dnssec_zone_from_list(struct getdns_list *list, + ldns_dnssec_zone **zone) +{ + getdns_return_t r; + size_t i, l; + struct getdns_dict *rr_dict; + ldns_rr *rr; + ldns_status s; + + if ((r = getdns_list_get_length(list, &l))) + return r; + + if (! (*zone = ldns_dnssec_zone_new())) + return GETDNS_RETURN_MEMORY_ERROR; + + for (i = 0; i < l; i++) { + if ((r = getdns_list_get_dict(list, i, &rr_dict))) + break; + + if ((r = priv_getdns_create_rr_from_dict(rr_dict, &rr))) + break; + + if ((s = ldns_dnssec_zone_add_rr(*zone, rr))) { + ldns_rr_free(rr); + r = GETDNS_RETURN_GENERIC_ERROR; + break; + } + } + if (r) + ldns_dnssec_zone_free(*zone); + return r; +} + +typedef struct zone_iter { + ldns_dnssec_zone *zone; + ldns_rbnode_t *cur_node; + ldns_dnssec_rrsets *cur_rrset; +} zone_iter; + +static void +rrset_iter_init_zone(zone_iter *i, ldns_dnssec_zone *zone) +{ + assert(i); + + i->zone = zone; + i->cur_node = zone->names ? ldns_rbtree_first(zone->names) + : LDNS_RBTREE_NULL; + i->cur_rrset = i->cur_node != LDNS_RBTREE_NULL + ? ((ldns_dnssec_name *)i->cur_node->data)->rrsets + : NULL; +} + +static ldns_dnssec_rrsets * +rrset_iter_value(zone_iter *i) +{ + assert(i); + + return i->cur_rrset; +} + +static void +rrset_iter_next(zone_iter *i) +{ + assert(i); + + if (! i->cur_rrset) + return; + + if (! (i->cur_rrset = i->cur_rrset->next)) { + i->cur_node = ldns_rbtree_next(i->cur_node); + i->cur_rrset = i->cur_node != LDNS_RBTREE_NULL + ? ((ldns_dnssec_name *)i->cur_node->data)->rrsets + : NULL; + } +} + +static ldns_rr_list * +rrs2rr_list(ldns_dnssec_rrs *rrs) +{ + ldns_rr_list *r = ldns_rr_list_new(); + if (r) + while (rrs) { + (void) ldns_rr_list_push_rr(r, rrs->rr); + rrs = rrs->next; + } + return r; +} + +static ldns_status +verify_rrset(ldns_dnssec_rrsets *rrset_and_sigs, + const ldns_rr_list *keys, ldns_rr_list *good_keys) +{ + ldns_status s; + ldns_rr_list *rrset = rrs2rr_list(rrset_and_sigs->rrs); + ldns_rr_list *sigs = rrs2rr_list(rrset_and_sigs->signatures); + s = ldns_verify(rrset, sigs, keys, good_keys); + ldns_rr_list_free(sigs); + ldns_rr_list_free(rrset); + return s; +} + +static ldns_status +chase(ldns_dnssec_rrsets *rrset, ldns_dnssec_zone *support, + ldns_rr_list *support_keys, ldns_rr_list *trusted) +{ + ldns_status s; + ldns_rr_list *verifying_keys; + size_t i, j; + ldns_rr *rr; + ldns_dnssec_rrsets *key_rrset; + ldns_dnssec_rrs *rrs; + + /* Secure by trusted keys? */ + verifying_keys = ldns_rr_list_new(); + s = verify_rrset(rrset, trusted, verifying_keys); + if (s == 0) + goto done_free_verifying_keys; + + /* No, chase with support records.. + * Is there a verifying key in the support records? + */ + verifying_keys = ldns_rr_list_new(); + s = verify_rrset(rrset, support_keys, verifying_keys); + if (s != 0) + goto done_free_verifying_keys; + + /* Ok, we have verifying keys from the support records. + * Compare them with the *trusted* keys or DSes, + * or chase them further down the validation chain. + */ + for (i = 0; i < ldns_rr_list_rr_count(verifying_keys); i++) { + /* Lookup the rrset for key rr from the support records */ + rr = ldns_rr_list_rr(verifying_keys, i); + key_rrset = ldns_dnssec_zone_find_rrset( + support, ldns_rr_owner(rr), ldns_rr_get_type(rr)); + if (! key_rrset) { + s = LDNS_STATUS_CRYPTO_NO_DNSKEY; + break; + } + /* When we signed ourselves, we have to cross domain border + * and look for a matching DS signed by a parents key + */ + if (rrset == key_rrset) { + /* Is the verifying key trusted? + * (i.e. DS in trusted) + */ + for (j = 0; j < ldns_rr_list_rr_count(trusted); j++) + if (ldns_rr_compare_ds(ldns_rr_list_rr( + trusted, j), rr)) + break; + /* If so, check for the next verifying key + * (or exit SECURE) + */ + if (j < ldns_rr_list_rr_count(trusted)) + continue; + + /* Search for a matching DS in the support records */ + key_rrset = ldns_dnssec_zone_find_rrset( + support, ldns_rr_owner(rr), LDNS_RR_TYPE_DS); + if (! key_rrset) { + s = LDNS_STATUS_CRYPTO_NO_DNSKEY; + break; + } + /* Now check if DS matches the DNSKEY! */ + for (rrs = key_rrset->rrs; rrs; rrs = rrs->next) + if (ldns_rr_compare_ds(rr, rrs->rr)) + break; + if (! rrs) { + s = LDNS_STATUS_CRYPTO_NO_DNSKEY; + break; + } + } + /* Pursue the chase with the verifying key (or its DS) */ + s = chase(key_rrset, support, support_keys, trusted); + if (s != 0) + break; + } +done_free_verifying_keys: + ldns_rr_list_free(verifying_keys); + return s; +} + /* * getdns_validate_dnssec * */ getdns_return_t -getdns_validate_dnssec(struct getdns_bindata * record_to_validate, - struct getdns_list * bundle_of_support_records, - struct getdns_list * trust_anchor_rdatas) +getdns_validate_dnssec(struct getdns_list *records_to_validate, + struct getdns_list *support_records, + struct getdns_list *trust_anchors) { - UNUSED_PARAM(record_to_validate); - UNUSED_PARAM(bundle_of_support_records); - UNUSED_PARAM(trust_anchor_rdatas); - return GETDNS_RETURN_GOOD; + getdns_return_t r; + ldns_rr_list *trusted; + ldns_dnssec_zone *support; + ldns_rr_list *support_keys; + ldns_dnssec_zone *to_validate; + zone_iter i; + ldns_dnssec_rrsets *rrset; + ldns_dnssec_rrs *rrs; + ldns_status s = LDNS_STATUS_OK; + + if ((r = priv_getdns_rr_list_from_list(trust_anchors, &trusted))) + return r; + + if ((r = priv_getdns_dnssec_zone_from_list( + support_records, &support))) + goto done_free_trusted; + + if ((r = priv_getdns_dnssec_zone_from_list( + records_to_validate, &to_validate))) + goto done_free_support; + + if (! (support_keys = ldns_rr_list_new())) { + r = GETDNS_RETURN_MEMORY_ERROR; + goto done_free_to_validate; + } + /* Create a rr_list of all the keys in the support records */ + for (rrset_iter_init_zone(&i, support); + (rrset = rrset_iter_value(&i)); rrset_iter_next(&i)) + + if (ldns_dnssec_rrsets_type(rrset) == LDNS_RR_TYPE_DS || + ldns_dnssec_rrsets_type(rrset) == LDNS_RR_TYPE_DNSKEY) + + for (rrs = rrset->rrs; rrs; rrs = rrs->next) + (void) ldns_rr_list_push_rr( + support_keys, rrs->rr); + + /* Now walk through the rrsets to validate */ + for (rrset_iter_init_zone(&i, to_validate); + (rrset = rrset_iter_value(&i)); rrset_iter_next(&i)) { + + s |= chase(rrset, support, support_keys, trusted); + if (s != 0) + break; + } + if (s == LDNS_STATUS_CRYPTO_BOGUS) + r = GETDNS_DNSSEC_BOGUS; + else if (s != LDNS_STATUS_OK) + r = GETDNS_DNSSEC_INSECURE; + else + r = GETDNS_DNSSEC_SECURE; + + ldns_rr_list_free(support_keys); +done_free_to_validate: + ldns_dnssec_zone_free(to_validate); +done_free_support: + ldns_dnssec_zone_free(support); +done_free_trusted: + ldns_rr_list_deep_free(trusted); + return r; } /* getdns_validate_dnssec */ /* validate_dnssec.c */