diff --git a/src/Makefile.in b/src/Makefile.in index 7863f426..574653c5 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -71,7 +71,7 @@ EXTENSION_LIBUV_LDFLAGS=@EXTENSION_LIBUV_LDFLAGS@ GETDNS_OBJ=sync.lo context.lo list.lo dict.lo convert.lo general.lo \ hostname.lo service.lo request-internal.lo util-internal.lo \ - getdns_error.lo rr-dict.lo dnssec.lo const-info.lo + getdns_error.lo rr-dict.lo dnssec.lo const-info.lo dane.lo .SUFFIXES: .c .o .a .lo .h diff --git a/src/dane.c b/src/dane.c new file mode 100644 index 00000000..f2368b70 --- /dev/null +++ b/src/dane.c @@ -0,0 +1,66 @@ +/** + * + * /brief function for DANE + * + * The getdns_dane_verify function is used to match and validate TLSAs with a + * certificate. + */ + +/* + * Copyright (c) 2014, NLnet Labs, Verisign, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the names of the copyright holders nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL Verisign, Inc. BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include "getdns/getdns.h" +#include "getdns/getdns_extra.h" +#include "config.h" +#include "rr-dict.h" + +int +getdns_dane_verify(getdns_list *tlsa_rr_dicts, X509 *cert, + STACK_OF(X509) *extra_certs, X509_STORE *pkix_validation_store ) +{ + getdns_return_t r; + ldns_rr_list *tlsas; + + if ((r = priv_getdns_rr_list_from_list(tlsa_rr_dicts, &tlsas))) + return r; + + switch (ldns_dane_verify(tlsas, cert, + extra_certs, pkix_validation_store)) { + case LDNS_STATUS_OK: + return GETDNS_RETURN_GOOD; + case LDNS_STATUS_DANE_PKIX_DID_NOT_VALIDATE: + return GETDNS_DANE_PKIX_DID_NOT_VALIDATE; + case LDNS_STATUS_DANE_TLSA_DID_NOT_MATCH: + return GETDNS_DANE_TLSA_DID_NOT_MATCH; + default: + break; + } + return GETDNS_RETURN_GENERIC_ERROR; +} + +/* dane.c */ diff --git a/src/dnssec.c b/src/dnssec.c index 28692f59..2dbca9eb 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -328,38 +328,6 @@ priv_getdns_get_validation_chain_sync(getdns_dns_req *dns_req) /********************** functions for validate_dnssec *************************/ -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) diff --git a/src/getdns/getdns_extra.h b/src/getdns/getdns_extra.h index 3d6be12b..604b36c8 100644 --- a/src/getdns/getdns_extra.h +++ b/src/getdns/getdns_extra.h @@ -30,6 +30,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -121,6 +122,16 @@ getdns_context_get_extension_data(struct getdns_context* context); getdns_return_t getdns_extension_detach_eventloop(struct getdns_context* context); +/* dane support */ +#define GETDNS_DANE_PKIX_DID_NOT_VALIDATE 3000 +#define GETDNS_DANE_PKIX_DID_NOT_VALIDATE_TEXT "A TLSA matched but PKIX validation failed." +#define GETDNS_DANE_TLSA_DID_NOT_MATCH 3001 +#define GETDNS_DANE_TLSA_DID_NOT_MATCH_TEXT "None of the given TLSAs matched" + +int /* actually extended getdns_return_t */ +getdns_dane_verify(getdns_list *tlsas, X509 *cert, + STACK_OF(X509) *extra_certs, X509_STORE *pkix_validation_store ); + #ifdef __cplusplus } #endif diff --git a/src/rr-dict.c b/src/rr-dict.c index bfa74970..af39b12f 100644 --- a/src/rr-dict.c +++ b/src/rr-dict.c @@ -1234,6 +1234,42 @@ priv_getdns_create_rr_from_dict(struct getdns_dict *rr_dict, ldns_rr **rr) return r; } +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 (list == NULL) { + l = 0; + r = GETDNS_RETURN_GOOD; + + } else 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_get_opt_dict(struct getdns_context* context, struct getdns_dict** record_dict, uint8_t* record_start, diff --git a/src/rr-dict.h b/src/rr-dict.h index abb7463e..7f67c808 100644 --- a/src/rr-dict.h +++ b/src/rr-dict.h @@ -44,6 +44,9 @@ getdns_return_t priv_getdns_create_reply_question_dict( getdns_return_t priv_getdns_create_rr_from_dict( struct getdns_dict *rr_dict, ldns_rr **rr); +getdns_return_t priv_getdns_rr_list_from_list( + struct getdns_list *list, ldns_rr_list **rr_list); + const char *priv_getdns_rr_type_name(int rr_type); getdns_return_t priv_getdns_append_opt_rr( diff --git a/src/test/Makefile.in b/src/test/Makefile.in index d730d714..d70821e4 100644 --- a/src/test/Makefile.in +++ b/src/test/Makefile.in @@ -58,7 +58,7 @@ CC=@CC@ CFLAGS=@CFLAGS@ -Wall -I$(srcdir)/ -I$(srcdir)/../ -I/usr/local/include -std=c99 $(cflags) LDFLAGS=@LDFLAGS@ -L. -L.. -L$(srcdir)/../ -L/usr/local/lib LDLIBS=-lgetdns @LIBS@ -lcheck -PROGRAMS=tests_dict tests_list tests_stub_async tests_stub_sync check_getdns tests_dnssec $(CHECK_EV_PROG) $(CHECK_EVENT_PROG) $(CHECK_UV_PROG) +PROGRAMS=tests_dict tests_list tests_stub_async tests_stub_sync check_getdns tests_dnssec tests_dane $(CHECK_EV_PROG) $(CHECK_EVENT_PROG) $(CHECK_UV_PROG) .SUFFIXES: .c .o .a .lo .h @@ -99,6 +99,8 @@ check_getdns_ev: check_getdns.o check_getdns_common.o check_getdns_context_set_t tests_dnssec: tests_dnssec.o testmessages.o $(LIBTOOL) --tag=CC --mode=link $(CC) $(CFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ tests_dnssec.o testmessages.o +tests_dane: tests_dane.o testmessages.o + $(LIBTOOL) --tag=CC --mode=link $(CC) $(CFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ tests_dane.o testmessages.o test: all ./check_getdns diff --git a/src/test/tests_dane.c b/src/test/tests_dane.c new file mode 100644 index 00000000..fda19adc --- /dev/null +++ b/src/test/tests_dane.c @@ -0,0 +1,363 @@ +/** + * \file + * unit tests for getdns_dict helper routines, these should be used to + * perform regression tests, output must be unchanged from canonical output + * stored with the sources + */ + +/* + * Copyright (c) 2014, NLNet Labs, Verisign, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the names of the copyright holders nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL Verisign, Inc. BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +int +main(int argc, const char **argv) +{ + getdns_return_t r; + + const char *hostname; + int port = 443; + char danename[1024]; + + getdns_context *context; + getdns_dict *response; + getdns_dict *extensions; + getdns_list *replies_tree; + uint32_t status; + getdns_dict *reply; + getdns_list *tlsas; + size_t ntlsas; + getdns_dict *response2; + getdns_list *addresses; + size_t naddresses, i; + SSL_CTX *ctx; + getdns_dict *address; + + getdns_bindata *address_type; + getdns_bindata *address_data; + + struct sockaddr_storage sas; + struct sockaddr_in *sa4 = (struct sockaddr_in *)&sas; + struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&sas; + size_t sa_len; + + char *address_str; + int sock; + SSL *ssl; + int ssl_status; + + X509 *cert; + STACK_OF(X509) *extra_certs; + + /* Number of successfully validated addresses */ + int nsuccess = 0; + + /* + * Get hostname and optional port from commandline arguments. + */ + if (argc == 3) + port = atoi(argv[2]); + + else if (argc != 2) { + + printf("usage: %s [ ]\n", progname); + printf("\t defaults to 443\n"); + + return EXIT_FAILURE; + } + hostname = argv[1]; + + /* + * Setup getdns stub resolution + */ + if ((r = getdns_context_create(&context, 1))) + return r; + + if ((r = getdns_context_set_resolution_type( + context, GETDNS_RESOLUTION_STUB))) + goto done_destroy_context; + + if (! (extensions = getdns_dict_create())) { + r = GETDNS_RETURN_MEMORY_ERROR; + goto done_destroy_context; + } + + /* + * Lookup TLSA's (but only when they are secure (i.e. DNSSEC signed)) + */ + if ((r = getdns_dict_set_int( + extensions, "dnssec_return_only_secure", GETDNS_EXTENSION_TRUE))) + goto done_destroy_extensions; + + /* construct the dane name */ + (void) snprintf(danename, 1024, "_%d._tcp.%s", port, hostname); + + /* actual lookup */ + if ((r = getdns_general_sync(context, + danename, GETDNS_RRTYPE_TLSA, extensions, &response))) + goto done_destroy_extensions; + + /* Did we get anything? Securely? */ + if ((r = getdns_dict_get_int(response, "status", &status))) + goto done_destroy_response; + + if (status == GETDNS_RESPSTATUS_NO_SECURE_ANSWERS) { + printf("No secure TLSA RR's for %s were found.\n", danename); + printf("PKIX validation without dane will be performed.\n"); + tlsas = NULL; + ntlsas = 0; + + } else { + /* descend into response dict to get to the tlsas */ + if ((r = getdns_dict_get_list( + response, "replies_tree", &replies_tree))) + goto done_destroy_response; + + if ((r = getdns_list_get_dict(replies_tree, 0, &reply))) + goto done_destroy_response; + + if ((r = getdns_dict_get_list(reply, "answer", &tlsas))) + goto done_destroy_response; + + if ((r = getdns_list_get_length(tlsas, &ntlsas))) + goto done_destroy_response; + + if (ntlsas == 0) { + printf("No TLSA RR's for %s were found.\n", danename); + printf("PKIX validation " + "without dane will be performed.\n"); + } + } + /* + * Lookup addresses for the hostname (don't have to be secure). + */ + if ((r = getdns_address_sync(context, hostname, NULL, &response2))) + goto done_destroy_response; + + /* get the addresses from the response dict */ + if ((r = getdns_dict_get_list(response2, "just_address_answers", + &addresses))) + goto done_destroy_response2; + + /* exit when there are none */ + if ((r = getdns_list_get_length(addresses, &naddresses))) + goto done_destroy_response2; + if (naddresses <= 0) { + printf("%s did not have any addresses to connect to\n", hostname); + goto done_destroy_response2; + } + + /* + * Setup OpenSSL context + */ + SSL_load_error_strings(); + SSL_library_init(); + ctx = SSL_CTX_new(SSLv23_client_method()); + if (! ctx) { + fprintf(stderr, "could not SSL_CTX_new\n"); + r = EXIT_FAILURE; + goto done_destroy_response2; + } + + /* + * For each address SSL connect and get and verify the certificate + */ + for (i = 0; i < naddresses && r == GETDNS_RETURN_GOOD; i++) { + + if ((r = getdns_list_get_dict(addresses, i, &address))) + break; + + /* + * Create a sockaddr_in from
+ */ + if ((r = getdns_dict_get_bindata( + address, "address_type", &address_type))) + return r; + + if ((r = getdns_dict_get_bindata( + address, "address_data", &address_data))) + return r; + + if (strncmp((const char *)address_type->data, "IPv4", 4) == 0) { + + sas.ss_family = AF_INET; + sa4->sin_port = htons(port); + memcpy(&(sa4->sin_addr),address_data->data, + address_data->size); + sa_len = sizeof(struct sockaddr_in); + + } else if (strncmp((const char *)address_type->data, "IPv6", 4) == 0) { + + sas.ss_family = AF_INET6; + sa6->sin6_port = htons(port); + memcpy(&(sa6->sin6_addr), address_data->data, + address_data->size); + sa_len = sizeof(struct sockaddr_in6); + + } else { + r = GETDNS_RETURN_GENERIC_ERROR; + break; + } + + /* + * Open and tcp-connect a socket with this sockaddr_in + */ + sock = socket(sas.ss_family, SOCK_STREAM, IPPROTO_TCP); + if (sock == -1) { + r = GETDNS_RETURN_GENERIC_ERROR; + break; + } + + /* display connection details */ + address_str = getdns_display_ip_address(address_data); + printf("Connecting to %s%s%s:%d... ", + (sas.ss_family == AF_INET6 ? "[" : ""), address_str, + (sas.ss_family == AF_INET6 ? "]" : ""), port); + free(address_str); + + /* and connect */ + if (connect(sock, (struct sockaddr *)&sas, sa_len) == -1) { + printf("failed\n"); + close(sock); + continue; + } + + /* + * Create ssl + */ + ssl = SSL_new(ctx); + if (! ssl) { + printf("failed setting up ssl (creating)\n"); + close(sock); + continue; + } + + /* + * Associate ssl with the hostname + */ +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + (void) SSL_set_tlsext_host_name(ssl, hostname); +#endif + + /* + * Associate ssl with the connected socket + */ + SSL_set_connect_state(ssl); + (void) SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); + if (! SSL_set_fd(ssl, sock)) { + printf("failed setting up ssl (associating socket)\n"); + SSL_free(ssl); + close(sock); + continue; + } + + /* + * Shake hands and get the certificates + */ + for (;;) { + ERR_clear_error(); + if ((ssl_status = SSL_do_handshake(ssl)) == 1) { + break; + } + ssl_status = SSL_get_error(ssl, ssl_status); + if (ssl_status != SSL_ERROR_WANT_READ && + ssl_status != SSL_ERROR_WANT_WRITE) { + r = GETDNS_RETURN_GENERIC_ERROR; + break; + } + } + if (r == GETDNS_RETURN_GENERIC_ERROR) { + r = GETDNS_RETURN_GOOD; + printf("failed setting up ssl (handshaking)\n"); + SSL_free(ssl); + close(sock); + continue; + } + + cert = SSL_get_peer_certificate(ssl); + extra_certs = SSL_get_peer_cert_chain(ssl); + + /* + * Dane validate the certificate + */ + switch (getdns_dane_verify(tlsas, cert, extra_certs, NULL)) { + case GETDNS_RETURN_GOOD: + printf("dane-validated successfully.\n"); + nsuccess++; + break; + + case GETDNS_DANE_PKIX_DID_NOT_VALIDATE: + if (ntlsas) printf("A TLSA matched, but "); + printf("PKIX validation failed\n"); + break; + + case GETDNS_DANE_TLSA_DID_NOT_MATCH: + printf("No matching TLSA found\n"); + break; + default: + printf("An error occurred when verifying TLSA's\n"); + break; + } + while (SSL_shutdown(ssl) == 0); + SSL_free(ssl); + + } /* for (i = 0; i < naddresses && r == GETDNS_RETURN_GOOD; i++) */ + + /* Clean up */ + SSL_CTX_free(ctx); + +done_destroy_response2: + getdns_dict_destroy(response2); + +done_destroy_response: + getdns_dict_destroy(response); + +done_destroy_extensions: + getdns_dict_destroy(extensions); + +done_destroy_context: + getdns_context_destroy(context); + + return r ? r : (naddresses == nsuccess ? EXIT_SUCCESS : EXIT_FAILURE); +} + +/* tests_dane.c */