summaryrefslogtreecommitdiff
path: root/examples/ldns-dane.c
diff options
context:
space:
mode:
Diffstat (limited to 'examples/ldns-dane.c')
-rw-r--r--examples/ldns-dane.c2016
1 files changed, 2016 insertions, 0 deletions
diff --git a/examples/ldns-dane.c b/examples/ldns-dane.c
new file mode 100644
index 0000000..f223675
--- /dev/null
+++ b/examples/ldns-dane.c
@@ -0,0 +1,2016 @@
+/*
+ * Verify or create TLS authentication with DANE (RFC6698)
+ *
+ * (c) NLnetLabs 2012
+ *
+ * See the file LICENSE for the license.
+ *
+ * wish list:
+ * - nicer reporting (tracing of evaluation process)
+ * - verbosity levels
+ * - STARTTLS support
+ */
+
+#include "config.h"
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+#include <sys/time.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <ldns/ldns.h>
+#include <errno.h>
+
+#ifdef USE_DANE
+#ifdef HAVE_SSL
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/x509v3.h>
+
+#ifndef IPPROTO_SCTP
+#define IPPROTO_SCTP 132
+#endif
+
+#define LDNS_ERR(code, msg) do { if (code != LDNS_STATUS_OK) \
+ ldns_err(msg, code); } while (false)
+#define MEMERR(msg) do { fprintf(stderr, "memory error in %s\n", msg); \
+ exit(EXIT_FAILURE); } while (false)
+#define BUFSIZE 16384
+
+/* Exit status on a PKIX validated connection but without TLSA records
+ * when the -T option was given:
+ */
+#define NO_TLSAS_EXIT_STATUS 2
+
+/* int verbosity = 3; */
+
+static void
+print_usage(const char* progname)
+{
+#ifdef USE_DANE_VERIY
+ printf("Usage: %s [OPTIONS] verify <name> <port>\n", progname);
+ printf(" or: %s [OPTIONS] -t <tlsafile> verify\n", progname);
+ printf("\n\tVerify the TLS connection at <name>:<port> or"
+ "\n\tuse TLSA record(s) from <tlsafile> to verify the\n"
+ "\tTLS service they reference.\n");
+ printf("\n or: %s [OPTIONS] create <name> <port> [<usage> "
+#else
+ printf("Usage: %s [OPTIONS] create <name> <port> [<usage> "
+#endif
+ "[<selector> [<type>]]]\n", progname);
+ printf("\n\tUse the TLS connection(s) to <name> <port> "
+ "to create the TLSA\n\t"
+ "resource record(s) that would "
+ "authenticate the connection.\n");
+ printf("\n\t<usage>"
+ "\t\t0 | PKIX-TA : CA constraint\n"
+ "\t\t\t1 | PKIX-EE : Service certificate constraint\n"
+ "\t\t\t2 | DANE-TA : Trust anchor assertion\n"
+ "\t\t\t3 | DANE-EE : Domain-issued certificate "
+ "(default)\n");
+ printf("\n\t<selector>"
+ "\t0 | Cert : Full certificate\n"
+ "\t\t\t1 | SPKI : SubjectPublicKeyInfo "
+ "(default)\n");
+ printf("\n\t<type>"
+ "\t\t0 | Full : No hash used\n"
+ "\t\t\t1 | SHA2-256 : SHA-256 (default)\n"
+ "\t\t\t2 | SHA2-512 : SHA-512\n");
+
+ printf("OPTIONS:\n");
+ printf("\t-h\t\tshow this text\n");
+ printf("\t-4\t\tTLS connect IPv4 only\n");
+ printf("\t-6\t\tTLS connect IPv6 only\n");
+ printf("\t-r <address>\t"
+ "use resolver at <address> instead of local resolver\n");
+ printf("\t-a <address>\t"
+ "don't resolve <name>, but connect to <address>(es)\n");
+ printf("\t-b\t\t"
+ "print \"<name>. TYPE52 \\#<size> <hex data>\" form\n"
+ );
+ printf("\t-c <certfile>\t"
+ "verify or create TLSA records for the\n"
+ "\t\t\tcertificate (chain) in <certfile>\n"
+ );
+ printf("\t-d\t\tassume DNSSEC validity even when insecure or bogus\n");
+ printf("\t-f <CAfile>\tuse CAfile to validate\n");
+#if HAVE_DANE_CA_FILE
+ printf("\t\t\tDefault is %s\n", LDNS_DANE_CA_FILE);
+#endif
+ printf("\t-i\t\tinteract after connecting\n");
+ printf("\t-k <keyfile>\t"
+ "use DNSKEY/DS rr(s) in <keyfile> to validate TLSAs\n"
+ "\t\t\twhen signature chasing (i.e. -S)\n"
+ );
+ printf("\t\t\tDefault is %s\n", LDNS_TRUST_ANCHOR_FILE);
+ printf("\t-n\t\tdo *not* verify server name in certificate\n");
+ printf("\t-o <offset>\t"
+ "select <offset>th certificate from the end of\n"
+ "\t\t\tthe validation chain. -1 means self-signed at end\n"
+ );
+ printf("\t-p <CApath>\t"
+ "use certificates in the <CApath> directory to validate\n"
+ );
+#if HAVE_DANE_CA_PATH
+ printf("\t\t\tDefaults is %s\n", LDNS_DANE_CA_PATH);
+#endif
+ printf("\t-s\t\tassume PKIX validity\n");
+ printf("\t-S\t\tChase signature(s) to a known key\n");
+ printf("\t-t <tlsafile>\tdo not use DNS, "
+ "but read TLSA record(s) from <tlsafile>\n"
+ );
+ printf("\t-T\t\tReturn exit status 2 for PKIX validated connections\n"
+ "\t\t\twithout (secure) TLSA records(s)\n");
+ printf("\t-u\t\tuse UDP transport instead of TCP\n");
+ printf("\t-v\t\tshow version and exit\n");
+ /* printf("\t-V [0-5]\tset verbosity level (default 3)\n"); */
+ exit(EXIT_SUCCESS);
+}
+
+static int
+dane_int_within_range(const char* arg, int max, const char* name)
+{
+ char* endptr; /* utility var for strtol usage */
+ int val = strtol(arg, &endptr, 10);
+
+ if ((val < 0 || val > max)
+ || (errno != 0 && val == 0) /* out of range */
+ || endptr == arg /* no digits */
+ || *endptr != '\0' /* more chars */
+ ) {
+ fprintf(stderr, "<%s> should be in range [0-%d]\n", name, max);
+ exit(EXIT_FAILURE);
+ }
+ return val;
+}
+
+struct dane_param_choice_struct {
+ const char* name;
+ int number;
+};
+typedef struct dane_param_choice_struct dane_param_choice;
+
+dane_param_choice dane_certificate_usage_table[] = {
+ { "PKIX-TA" , 0 },
+ { "CA constraint" , 0 },
+ { "CA-constraint" , 0 },
+ { "PKIX-EE" , 1 },
+ { "Service certificate constraint" , 1 },
+ { "Service-certificate-constraint" , 1 },
+ { "DANE-TA" , 2 },
+ { "Trust anchor assertion" , 2 },
+ { "Trust-anchor-assertion" , 2 },
+ { "anchor" , 2 },
+ { "DANE-EE" , 3 },
+ { "Domain-issued certificate" , 3 },
+ { "Domain-issued-certificate" , 3 },
+ { "PrivCert" , 255 },
+ { NULL, -1 }
+};
+
+dane_param_choice dane_selector_table[] = {
+ { "Cert" , 0 },
+ { "Full certificate" , 0 },
+ { "Full-certificate" , 0 },
+ { "certificate" , 0 },
+ { "SPKI" , 1 },
+ { "SubjectPublicKeyInfo", 1 },
+ { "PublicKey" , 1 },
+ { "pubkey" , 1 },
+ { "key" , 1 },
+ { "PrivSel" , 255 },
+ { NULL, -1 }
+};
+
+dane_param_choice dane_matching_type_table[] = {
+ { "Full" , 0 },
+ { "no-hash-used" , 0 },
+ { "no hash used" , 0 },
+ { "SHA2-256" , 1 },
+ { "sha256" , 1 },
+ { "sha-256" , 1 },
+ { "SHA2-512" , 2 },
+ { "sha512" , 2 },
+ { "sha-512" , 2 },
+ { "PrivMatch" , 255 },
+ { NULL, -1 }
+};
+
+static int
+dane_int_within_range_table(const char* arg, int max, const char* name,
+ dane_param_choice table[])
+{
+ dane_param_choice* t;
+
+ if (*arg) {
+ for (t = table; t->name; t++) {
+ if (strncasecmp(arg, t->name, strlen(arg)) == 0) {
+ return t->number;
+ }
+ }
+ }
+ return dane_int_within_range(arg, max, name);
+}
+
+static void
+ssl_err(const char* s)
+{
+ fprintf(stderr, "error: %s\n", s);
+ ERR_print_errors_fp(stderr);
+ exit(EXIT_FAILURE);
+}
+
+static void
+ldns_err(const char* s, ldns_status err)
+{
+ if (err == LDNS_STATUS_SSL_ERR) {
+ ssl_err(s);
+ } else {
+ fprintf(stderr, "%s: %s\n", s, ldns_get_errorstr_by_id(err));
+ exit(EXIT_FAILURE);
+ }
+}
+
+static ldns_status
+ssl_connect_and_get_cert_chain(
+ X509** cert, STACK_OF(X509)** extra_certs,
+ SSL* ssl, const char* name_str,
+ ldns_rdf* address, uint16_t port,
+ ldns_dane_transport transport)
+{
+ struct sockaddr_storage *a = NULL;
+ size_t a_len = 0;
+ int sock;
+ int r;
+
+ assert(cert != NULL);
+ assert(extra_certs != NULL);
+
+ a = ldns_rdf2native_sockaddr_storage(address, port, &a_len);
+ switch (transport) {
+ case LDNS_DANE_TRANSPORT_TCP:
+
+ sock = socket((int)((struct sockaddr*)a)->sa_family,
+ SOCK_STREAM, IPPROTO_TCP);
+ break;
+
+ case LDNS_DANE_TRANSPORT_UDP:
+
+ sock = socket((int)((struct sockaddr*)a)->sa_family,
+ SOCK_DGRAM, IPPROTO_UDP);
+ break;
+
+ case LDNS_DANE_TRANSPORT_SCTP:
+
+ sock = socket((int)((struct sockaddr*)a)->sa_family,
+ SOCK_STREAM, IPPROTO_SCTP);
+ break;
+
+ default:
+ LDNS_FREE(a);
+ return LDNS_STATUS_DANE_UNKNOWN_TRANSPORT;
+ }
+ if (sock == -1) {
+ LDNS_FREE(a);
+ return LDNS_STATUS_NETWORK_ERR;
+ }
+ if (connect(sock, (struct sockaddr*)a, (socklen_t)a_len) == -1) {
+ LDNS_FREE(a);
+ return LDNS_STATUS_NETWORK_ERR;
+ }
+ LDNS_FREE(a);
+ if (! SSL_clear(ssl)) {
+ close(sock);
+ fprintf(stderr, "SSL_clear\n");
+ return LDNS_STATUS_SSL_ERR;
+ }
+#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
+ (void) SSL_set_tlsext_host_name(ssl, name_str);
+#endif
+ SSL_set_connect_state(ssl);
+ (void) SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
+ if (! SSL_set_fd(ssl, sock)) {
+ close(sock);
+ fprintf(stderr, "SSL_set_fd\n");
+ return LDNS_STATUS_SSL_ERR;
+ }
+ for (;;) {
+ ERR_clear_error();
+ if ((r = SSL_do_handshake(ssl)) == 1) {
+ break;
+ }
+ r = SSL_get_error(ssl, r);
+ if (r != SSL_ERROR_WANT_READ && r != SSL_ERROR_WANT_WRITE) {
+ fprintf(stderr, "handshaking SSL_get_error: %d\n", r);
+ return LDNS_STATUS_SSL_ERR;
+ }
+ }
+ *cert = SSL_get_peer_certificate(ssl);
+ *extra_certs = SSL_get_peer_cert_chain(ssl);
+
+ return LDNS_STATUS_OK;
+}
+
+
+#ifdef USE_DANE_VERIFY
+static void
+ssl_interact(SSL* ssl)
+{
+ fd_set rfds;
+ int maxfd;
+ int sock;
+ int r;
+
+ char buf[BUFSIZE];
+ char* bufptr;
+ int to_write;
+ int written;
+
+ sock = SSL_get_fd(ssl);
+ if (sock == -1) {
+ return;
+ }
+ maxfd = (STDIN_FILENO > sock ? STDIN_FILENO : sock) + 1;
+ for (;;) {
+#ifndef S_SPLINT_S
+ FD_ZERO(&rfds);
+#endif /* splint */
+ FD_SET(sock, &rfds);
+ FD_SET(STDIN_FILENO, &rfds);
+
+ r = select(maxfd, &rfds, NULL, NULL, NULL);
+ if (r == -1) {
+ perror("select");
+ break;
+ }
+ if (FD_ISSET(sock, &rfds)) {
+ to_write = SSL_read(ssl, buf, BUFSIZE);
+ if (to_write <= 0) {
+ r = SSL_get_error(ssl, to_write);
+ if (r != SSL_ERROR_ZERO_RETURN) {
+ fprintf(stderr,
+ "reading SSL_get_error:"
+ " %d\n", r);
+ }
+ break;
+ }
+ bufptr = buf;
+ while (to_write > 0) {
+ written = (int) fwrite(bufptr, 1,
+ (size_t) to_write, stdout);
+ if (written == 0) {
+ perror("fwrite");
+ break;
+ }
+ to_write -= written;
+ bufptr += written;
+ }
+ } /* if (FD_ISSET(sock, &rfds)) */
+
+ if (FD_ISSET(STDIN_FILENO, &rfds)) {
+ to_write = (int) read(STDIN_FILENO, buf, BUFSIZE - 1);
+ if (to_write <= 0) {
+ if (to_write == -1) {
+ perror("read");
+ }
+ break;
+ }
+ if (buf[to_write - 1] == '\n') {
+ buf[to_write - 1] = '\r';
+ buf[to_write ] = '\n';
+ to_write += 1;
+ }
+ bufptr = buf;
+ while (to_write > 0) {
+ written = SSL_write(ssl, bufptr, to_write);
+ if (written <= 0) {
+ r = SSL_get_error(ssl, to_write);
+ if (r != SSL_ERROR_ZERO_RETURN) {
+ fprintf(stderr,
+ "writing SSL_get_error"
+ ": %d\n", r);
+ }
+ break;
+ }
+ to_write -= written;
+ bufptr += written;
+ }
+ } /* if (FD_ISSET(STDIN_FILENO, &rfds)) */
+
+ } /* for (;;) */
+}
+#endif /* USE_DANE_VERIFY */
+
+
+static ldns_rr_list*
+rr_list_filter_rr_type(ldns_rr_list* l, ldns_rr_type t)
+{
+ size_t i;
+ ldns_rr* rr;
+ ldns_rr_list* r = ldns_rr_list_new();
+
+ if (r == NULL) {
+ return r;
+ }
+ for (i = 0; i < ldns_rr_list_rr_count(l); i++) {
+ rr = ldns_rr_list_rr(l, i);
+ if (ldns_rr_get_type(rr) == t) {
+ if (! ldns_rr_list_push_rr(r, rr)) {
+ ldns_rr_list_free(r);
+ return NULL;
+ }
+ }
+ }
+ return r;
+}
+
+
+/* Return a copy of the list of tlsa records where the usage types
+ * "CA constraint" are replaced with "Trust anchor assertion" and the usage
+ * types "Service certificate constraint" are replaced with
+ * "Domain-issued certificate".
+ *
+ * This to check what would happen if PKIX validation was successfull always.
+ */
+static ldns_rr_list*
+dane_no_pkix_transform(const ldns_rr_list* tlas)
+{
+ size_t i;
+ ldns_rr* rr;
+ ldns_rr* new_rr;
+ ldns_rdf* rdf;
+ ldns_rr_list* r = ldns_rr_list_new();
+
+ if (r == NULL) {
+ return r;
+ }
+ for (i = 0; i < ldns_rr_list_rr_count(tlas); i++) {
+ rr = ldns_rr_list_rr(tlas, i);
+ if (ldns_rr_get_type(rr) == LDNS_RR_TYPE_TLSA) {
+
+ new_rr = ldns_rr_clone(rr);
+ if (!new_rr) {
+ ldns_rr_list_deep_free(r);
+ return NULL;
+ }
+ switch(ldns_rdf2native_int8(ldns_rr_rdf(new_rr, 0))) {
+
+ case LDNS_TLSA_USAGE_CA_CONSTRAINT:
+
+ rdf = ldns_native2rdf_int8(LDNS_RDF_TYPE_INT8,
+ (uint8_t) LDNS_TLSA_USAGE_TRUST_ANCHOR_ASSERTION);
+ if (! rdf) {
+ ldns_rr_free(new_rr);
+ ldns_rr_list_deep_free(r);
+ return NULL;
+ }
+ (void) ldns_rr_set_rdf(new_rr, rdf, 0);
+ break;
+
+
+ case LDNS_TLSA_USAGE_SERVICE_CERTIFICATE_CONSTRAINT:
+
+ rdf = ldns_native2rdf_int8(LDNS_RDF_TYPE_INT8,
+ (uint8_t) LDNS_TLSA_USAGE_DOMAIN_ISSUED_CERTIFICATE);
+ if (! rdf) {
+ ldns_rr_free(new_rr);
+ ldns_rr_list_deep_free(r);
+ return NULL;
+ }
+ (void) ldns_rr_set_rdf(new_rr, rdf, 0);
+ break;
+
+
+ default:
+ break;
+ }
+ if (! ldns_rr_list_push_rr(r, new_rr)) {
+ ldns_rr_free(new_rr);
+ ldns_rr_list_deep_free(r);
+ return NULL;
+ }
+ }
+ }
+ return r;
+}
+
+static void
+print_rr_as_TYPEXXX(FILE* out, ldns_rr* rr)
+{
+ size_t i, sz;
+ ldns_status s;
+ ldns_buffer* buf = ldns_buffer_new(LDNS_MAX_PACKETLEN);
+ char* str;
+
+ ldns_buffer_clear(buf);
+ s = ldns_rdf2buffer_str_dname(buf, ldns_rr_owner(rr));
+ LDNS_ERR(s, "could not ldns_rdf2buffer_str_dname");
+ ldns_buffer_printf(buf, "\t%d", ldns_rr_ttl(rr));
+ ldns_buffer_printf(buf, "\t");
+ s = ldns_rr_class2buffer_str(buf, ldns_rr_get_class(rr));
+ LDNS_ERR(s, "could not ldns_rr_class2buffer_str");
+ ldns_buffer_printf(buf, "\tTYPE%d", ldns_rr_get_type(rr));
+ sz = 0;
+ for (i = 0; i < ldns_rr_rd_count(rr); i++) {
+ sz += ldns_rdf_size(ldns_rr_rdf(rr, i));
+ }
+ ldns_buffer_printf(buf, "\t\\# %d ", sz);
+ for (i = 0; i < ldns_rr_rd_count(rr); i++) {
+ s = ldns_rdf2buffer_str_hex(buf, ldns_rr_rdf(rr, i));
+ LDNS_ERR(s, "could not ldns_rdf2buffer_str_hex");
+ }
+ str = ldns_buffer_export2str(buf);
+ ldns_buffer_free(buf);
+ fprintf(out, "%s\n", str);
+ LDNS_FREE(str);
+}
+
+static void
+print_rr_list_as_TYPEXXX(FILE* out, ldns_rr_list* l)
+{
+ size_t i;
+
+ for (i = 0; i < ldns_rr_list_rr_count(l); i++) {
+ print_rr_as_TYPEXXX(out, ldns_rr_list_rr(l, i));
+ }
+}
+
+static ldns_status
+read_key_file(const char *filename, ldns_rr_list *keys)
+{
+ ldns_status status = LDNS_STATUS_ERR;
+ ldns_rr *rr;
+ FILE *fp;
+ uint32_t my_ttl = 0;
+ ldns_rdf *my_origin = NULL;
+ ldns_rdf *my_prev = NULL;
+ int line_nr;
+
+ if (!(fp = fopen(filename, "r"))) {
+ return LDNS_STATUS_FILE_ERR;
+ }
+ while (!feof(fp)) {
+ status = ldns_rr_new_frm_fp_l(&rr, fp, &my_ttl, &my_origin,
+ &my_prev, &line_nr);
+
+ if (status == LDNS_STATUS_OK) {
+
+ if ( ldns_rr_get_type(rr) == LDNS_RR_TYPE_DS
+ || ldns_rr_get_type(rr) == LDNS_RR_TYPE_DNSKEY)
+
+ ldns_rr_list_push_rr(keys, rr);
+
+ } else if ( status == LDNS_STATUS_SYNTAX_EMPTY
+ || status == LDNS_STATUS_SYNTAX_TTL
+ || status == LDNS_STATUS_SYNTAX_ORIGIN
+ || status == LDNS_STATUS_SYNTAX_INCLUDE)
+
+ status = LDNS_STATUS_OK;
+ else
+ break;
+ }
+ fclose(fp);
+ return status;
+}
+
+
+static ldns_status
+dane_setup_resolver(ldns_resolver** res, ldns_rdf* nameserver_addr,
+ ldns_rr_list* keys, bool dnssec_off)
+{
+ ldns_status s = LDNS_STATUS_OK;
+
+ assert(res != NULL);
+
+ if (nameserver_addr) {
+ *res = ldns_resolver_new();
+ if (*res) {
+ s = ldns_resolver_push_nameserver(*res, nameserver_addr);
+ } else {
+ s = LDNS_STATUS_MEM_ERR;
+ }
+ } else {
+ s = ldns_resolver_new_frm_file(res, NULL);
+ }
+ if (s == LDNS_STATUS_OK) {
+ ldns_resolver_set_dnssec(*res, ! dnssec_off);
+
+ if (keys && ldns_rr_list_rr_count(keys) > 0) {
+ /* anchors must trigger signature chasing */
+ ldns_resolver_set_dnssec_anchors(*res, keys);
+ ldns_resolver_set_dnssec_cd(*res, true);
+ }
+ }
+ return s;
+}
+
+
+static ldns_status
+dane_query(ldns_rr_list** rrs, ldns_resolver* r,
+ ldns_rdf *name, ldns_rr_type t, ldns_rr_class c,
+ bool insecure_is_ok)
+{
+ ldns_pkt* p = NULL;
+ ldns_rr_list* keys = NULL;
+ ldns_rr_list* rrsigs = NULL;
+ ldns_rdf* signame = NULL;
+ ldns_status s;
+
+ assert(rrs != NULL);
+
+ p = ldns_resolver_query(r, name, t, c, LDNS_RD);
+ if (! p) {
+ return LDNS_STATUS_MEM_ERR;
+ }
+ *rrs = ldns_pkt_rr_list_by_type(p, t, LDNS_SECTION_ANSWER);
+
+ if (! ldns_resolver_dnssec(r)) { /* DNSSEC explicitly disabled,
+ anything goes */
+ ldns_pkt_free(p);
+ return LDNS_STATUS_OK;
+ }
+ if (ldns_rr_list_rr_count(*rrs) == 0) { /* assert(*rrs == NULL) */
+
+ if (ldns_pkt_get_rcode(p) == LDNS_RCODE_SERVFAIL) {
+
+ ldns_pkt_free(p);
+ return LDNS_STATUS_DANE_BOGUS;
+ } else {
+ ldns_pkt_free(p);
+ return LDNS_STATUS_OK;
+ }
+ }
+ /* We have answers and we have dnssec. */
+
+ if (! ldns_pkt_cd(p)) { /* we act as stub resolver (no sigchase) */
+
+ if (! ldns_pkt_ad(p)) { /* Not secure */
+
+ goto insecure;
+ }
+ ldns_pkt_free(p);
+ return LDNS_STATUS_OK;
+ }
+
+ /* sigchase */
+
+ /* TODO: handle cname reference check */
+
+ rrsigs = ldns_pkt_rr_list_by_type(p,
+ LDNS_RR_TYPE_RRSIG,
+ LDNS_SECTION_ANSWER);
+
+ if (! rrsigs || ldns_rr_list_rr_count(rrsigs) == 0) {
+ goto insecure;
+ }
+
+ signame = ldns_rr_rrsig_signame(ldns_rr_list_rr(rrsigs, 0));
+ if (! signame) {
+ s = LDNS_STATUS_ERR;
+ goto error;
+ }
+ /* First try with the keys we already have */
+ s = ldns_verify(*rrs, rrsigs, ldns_resolver_dnssec_anchors(r), NULL);
+ if (s == LDNS_STATUS_OK) {
+ goto cleanup;
+ }
+ /* Fetch the necessary keys and recheck */
+ keys = ldns_fetch_valid_domain_keys(r, signame,
+ ldns_resolver_dnssec_anchors(r), &s);
+
+ if (s != LDNS_STATUS_OK) {
+ goto error;
+ }
+ if (ldns_rr_list_rr_count(keys) == 0) { /* An insecure island */
+ goto insecure;
+ }
+ s = ldns_verify(*rrs, rrsigs, keys, NULL);
+ switch (s) {
+ case LDNS_STATUS_CRYPTO_BOGUS: goto bogus;
+ case LDNS_STATUS_OK : goto cleanup;
+ default : break;
+ }
+insecure:
+ s = LDNS_STATUS_DANE_INSECURE;
+bogus:
+ if (! insecure_is_ok) {
+error:
+ ldns_rr_list_deep_free(*rrs);
+ *rrs = ldns_rr_list_new();
+ }
+cleanup:
+ if (keys) {
+ ldns_rr_list_deep_free(keys);
+ }
+ if (rrsigs) {
+ ldns_rr_list_deep_free(rrsigs);
+ }
+ ldns_pkt_free(p);
+ return s;
+}
+
+
+static ldns_rr_list*
+dane_lookup_addresses(ldns_resolver* res, ldns_rdf* dname,
+ int ai_family)
+{
+ ldns_status s;
+ ldns_rr_list *as = NULL;
+ ldns_rr_list *aaas = NULL;
+ ldns_rr_list *r = ldns_rr_list_new();
+
+ if (r == NULL) {
+ MEMERR("ldns_rr_list_new");
+ }
+ if (ai_family == AF_UNSPEC || ai_family == AF_INET) {
+
+ s = dane_query(&as, res,
+ dname, LDNS_RR_TYPE_A, LDNS_RR_CLASS_IN,
+ true);
+
+ if (s == LDNS_STATUS_DANE_INSECURE &&
+ ldns_rr_list_rr_count(as) > 0) {
+ fprintf(stderr, "Warning! Insecure IPv4 addresses. "
+ "Continuing with them...\n");
+
+ } else if (s == LDNS_STATUS_DANE_BOGUS ||
+ LDNS_STATUS_CRYPTO_BOGUS == s) {
+ fprintf(stderr, "Warning! Bogus IPv4 addresses. "
+ "Discarding...\n");
+ ldns_rr_list_deep_free(as);
+ as = ldns_rr_list_new();
+
+ } else if (s != LDNS_STATUS_OK) {
+ LDNS_ERR(s, "dane_query");
+
+ }
+ if (! ldns_rr_list_push_rr_list(r, as)) {
+ MEMERR("ldns_rr_list_push_rr_list");
+ }
+ }
+ if (ai_family == AF_UNSPEC || ai_family == AF_INET6) {
+
+ s = dane_query(&aaas, res,
+ dname, LDNS_RR_TYPE_AAAA, LDNS_RR_CLASS_IN,
+ true);
+
+ if (s == LDNS_STATUS_DANE_INSECURE &&
+ ldns_rr_list_rr_count(aaas) > 0) {
+ fprintf(stderr, "Warning! Insecure IPv6 addresses. "
+ "Continuing with them...\n");
+
+ } else if (s == LDNS_STATUS_DANE_BOGUS ||
+ LDNS_STATUS_CRYPTO_BOGUS == s) {
+ fprintf(stderr, "Warning! Bogus IPv6 addresses. "
+ "Discarding...\n");
+ ldns_rr_list_deep_free(aaas);
+ aaas = ldns_rr_list_new();
+
+ } else if (s != LDNS_STATUS_OK) {
+ LDNS_ERR(s, "dane_query");
+
+ }
+ if (! ldns_rr_list_push_rr_list(r, aaas)) {
+ MEMERR("ldns_rr_list_push_rr_list");
+ }
+ }
+ return r;
+}
+
+static ldns_status
+dane_read_tlsas_from_file(ldns_rr_list** tlsas,
+ char* filename, ldns_rdf* origin)
+{
+ FILE* fp = NULL;
+ ldns_rr* rr = NULL;
+ ldns_rdf *my_origin = NULL;
+ ldns_rdf *my_prev = NULL;
+ ldns_rdf *origin_lc = NULL;
+ int line_nr;
+ ldns_status s = LDNS_STATUS_MEM_ERR;
+
+ assert(tlsas != NULL);
+ assert(filename != NULL);
+
+ if (strcmp(filename, "-") == 0) {
+ fp = stdin;
+ } else {
+ fp = fopen(filename, "r");
+ if (!fp) {
+ fprintf(stderr, "Unable to open %s: %s\n",
+ filename, strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ }
+ if (origin) {
+ my_origin = ldns_rdf_clone(origin);
+ if (! my_origin) {
+ goto error;
+ }
+ my_prev = ldns_rdf_clone(origin);
+ if (! my_prev) {
+ goto error;
+ }
+ origin_lc = ldns_rdf_clone(origin);
+ if (! origin_lc) {
+ goto error;
+ }
+ ldns_dname2canonical(origin_lc);
+ }
+ *tlsas = ldns_rr_list_new();
+ if (! *tlsas) {
+ goto error;
+ }
+ while (! feof(fp)) {
+ s = ldns_rr_new_frm_fp_l(&rr, fp, NULL,
+ &my_origin, &my_prev, &line_nr);
+ if (s != LDNS_STATUS_OK) {
+ goto error;
+ }
+ if (ldns_rr_get_type(rr) == LDNS_RR_TYPE_TLSA) {
+ ldns_dname2canonical(ldns_rr_owner(rr));
+ if (! origin || ldns_dname_compare(ldns_rr_owner(rr),
+ origin_lc) == 0) {
+ if (ldns_rr_list_push_rr(*tlsas, rr)) {
+ continue;
+ } else {
+ s = LDNS_STATUS_MEM_ERR;
+ goto error;
+ }
+ }
+ }
+ ldns_rr_free(rr);
+ }
+
+ ldns_rdf_deep_free(origin_lc);
+ ldns_rdf_deep_free(my_prev);
+ ldns_rdf_deep_free(my_origin);
+ fclose(fp);
+
+ return LDNS_STATUS_OK;
+
+error:
+ if (*tlsas) {
+ ldns_rr_list_deep_free(*tlsas);
+ *tlsas = NULL;
+ }
+ if (origin_lc) {
+ ldns_rdf_deep_free(origin_lc);
+ }
+ if (my_prev) {
+ ldns_rdf_deep_free(my_prev);
+ }
+ if (my_origin) {
+ ldns_rdf_deep_free(my_origin);
+ }
+ if (fp && fp != stdin) {
+ fclose(fp);
+ }
+ return s;
+}
+
+static bool
+dane_wildcard_label_cmp(uint8_t iw, const char* w, uint8_t il, const char* l)
+{
+ if (iw == 0) { /* End of match label */
+ if (il == 0) { /* And end in the to be matched label */
+ return true;
+ }
+ return false;
+ }
+ do {
+ if (*w == '*') {
+ if (iw == 1) { /* '*' is the last match char,
+ remainder matches wildcard */
+ return true;
+ }
+ while (il > 0) { /* more to match? */
+
+ if (w[1] == *l) { /* Char after '*' matches.
+ * Recursion for backtracking
+ */
+ if (dane_wildcard_label_cmp(
+ iw - 1, w + 1,
+ il , l)) {
+ return true;
+ }
+ }
+ l += 1;
+ il -= 1;
+ }
+ }
+ /* Skip up till next wildcard (if possible) */
+ while (il > 0 && iw > 0 && *w != '*' && *w == *l) {
+ w += 1;
+ l += 1;
+ il -= 1;
+ iw -= 1;
+ }
+ } while (iw > 0 && *w == '*' && /* More to match a next wildcard? */
+ (il > 0 || iw == 1));
+
+ return iw == 0 && il == 0;
+}
+
+static bool
+dane_label_matches_label(ldns_rdf* w, ldns_rdf* l)
+{
+ uint8_t iw;
+ uint8_t il;
+
+ iw = ldns_rdf_data(w)[0];
+ il = ldns_rdf_data(l)[0];
+ return dane_wildcard_label_cmp(
+ iw, (const char*)ldns_rdf_data(w) + 1,
+ il, (const char*)ldns_rdf_data(l) + 1);
+}
+
+static bool
+dane_name_matches_server_name(const char* name_str, ldns_rdf* server_name)
+{
+ ldns_rdf* name;
+ uint8_t nn, ns, i;
+ ldns_rdf* ln;
+ ldns_rdf* ls;
+
+ name = ldns_dname_new_frm_str((const char*)name_str);
+ if (! name) {
+ LDNS_ERR(LDNS_STATUS_ERR, "ldns_dname_new_frm_str");
+ }
+ nn = ldns_dname_label_count(name);
+ ns = ldns_dname_label_count(server_name);
+ if (nn != ns) {
+ ldns_rdf_free(name);
+ return false;
+ }
+ ldns_dname2canonical(name);
+ for (i = 0; i < nn; i++) {
+ ln = ldns_dname_label(name, i);
+ if (! ln) {
+ return false;
+ }
+ ls = ldns_dname_label(server_name, i);
+ if (! ls) {
+ ldns_rdf_free(ln);
+ return false;
+ }
+ if (! dane_label_matches_label(ln, ls)) {
+ ldns_rdf_free(ln);
+ ldns_rdf_free(ls);
+ return false;
+ }
+ ldns_rdf_free(ln);
+ ldns_rdf_free(ls);
+ }
+ return true;
+}
+
+static bool
+dane_X509_any_subject_alt_name_matches_server_name(
+ X509 *cert, ldns_rdf* server_name)
+{
+ GENERAL_NAMES* names;
+ GENERAL_NAME* name;
+ unsigned char* subject_alt_name_str = NULL;
+ int i, n;
+
+ names = X509_get_ext_d2i(cert, NID_subject_alt_name, 0, 0 );
+ if (! names) { /* No subjectAltName extension */
+ return false;
+ }
+ n = sk_GENERAL_NAME_num(names);
+ for (i = 0; i < n; i++) {
+ name = sk_GENERAL_NAME_value(names, i);
+ if (name->type == GEN_DNS) {
+ (void) ASN1_STRING_to_UTF8(&subject_alt_name_str,
+ name->d.dNSName);
+ if (subject_alt_name_str) {
+ if (dane_name_matches_server_name((char*)
+ subject_alt_name_str,
+ server_name)) {
+ OPENSSL_free(subject_alt_name_str);
+ return true;
+ }
+ OPENSSL_free(subject_alt_name_str);
+ }
+ }
+ }
+ /* sk_GENERAL_NAMES_pop_free(names, sk_GENERAL_NAME_free); */
+ return false;
+}
+
+static bool
+dane_X509_subject_name_matches_server_name(X509 *cert, ldns_rdf* server_name)
+{
+ X509_NAME* subject_name;
+ int i;
+ X509_NAME_ENTRY* entry;
+ ASN1_STRING* entry_data;
+ unsigned char* subject_name_str = NULL;
+ bool r;
+
+ subject_name = X509_get_subject_name(cert);
+ if (! subject_name ) {
+ ssl_err("could not X509_get_subject_name");
+ }
+ i = X509_NAME_get_index_by_NID(subject_name, NID_commonName, -1);
+ entry = X509_NAME_get_entry(subject_name, i);
+ entry_data = X509_NAME_ENTRY_get_data(entry);
+ (void) ASN1_STRING_to_UTF8(&subject_name_str, entry_data);
+ if (subject_name_str) {
+ r = dane_name_matches_server_name(
+ (char*)subject_name_str, server_name);
+ OPENSSL_free(subject_name_str);
+ return r;
+ } else {
+ return false;
+ }
+}
+
+static bool
+dane_verify_server_name(X509* cert, ldns_rdf* server_name)
+{
+ ldns_rdf* server_name_lc;
+ bool r;
+ server_name_lc = ldns_rdf_clone(server_name);
+ if (! server_name_lc) {
+ LDNS_ERR(LDNS_STATUS_MEM_ERR, "ldns_rdf_clone");
+ }
+ ldns_dname2canonical(server_name_lc);
+ r = dane_X509_any_subject_alt_name_matches_server_name(
+ cert, server_name_lc) ||
+ dane_X509_subject_name_matches_server_name(
+ cert, server_name_lc);
+ ldns_rdf_free(server_name_lc);
+ return r;
+}
+
+static void
+dane_create(ldns_rr_list* tlsas, ldns_rdf* tlsa_owner,
+ ldns_tlsa_certificate_usage certificate_usage, int offset,
+ ldns_tlsa_selector selector,
+ ldns_tlsa_matching_type matching_type,
+ X509* cert, STACK_OF(X509)* extra_certs,
+ X509_STORE* validate_store,
+ bool verify_server_name, ldns_rdf* name)
+{
+ ldns_status s;
+ X509* selected_cert;
+ ldns_rr* tlsa_rr;
+
+ if (verify_server_name && ! dane_verify_server_name(cert, name)) {
+ fprintf(stderr, "The certificate does not match the "
+ "server name\n");
+ exit(EXIT_FAILURE);
+ }
+
+ s = ldns_dane_select_certificate(&selected_cert,
+ cert, extra_certs, validate_store,
+ certificate_usage, offset);
+ LDNS_ERR(s, "could not select certificate");
+
+ s = ldns_dane_create_tlsa_rr(&tlsa_rr,
+ certificate_usage, selector, matching_type,
+ selected_cert);
+ LDNS_ERR(s, "could not create tlsa rr");
+
+ ldns_rr_set_owner(tlsa_rr, ldns_rdf_clone(tlsa_owner));
+
+ if (! ldns_rr_list_contains_rr(tlsas, tlsa_rr)) {
+ if (! ldns_rr_list_push_rr(tlsas, tlsa_rr)) {
+ MEMERR("ldns_rr_list_push_rr");
+ }
+ }
+}
+
+#if defined(USE_DANE_VERIFY) && ( OPENSSL_VERSION_NUMBER < 0x10100000 || defined(HAVE_LIBRESSL) )
+static bool
+dane_verify(ldns_rr_list* tlsas, ldns_rdf* address,
+ X509* cert, STACK_OF(X509)* extra_certs,
+ X509_STORE* validate_store,
+ bool verify_server_name, ldns_rdf* name,
+ bool assume_pkix_validity)
+{
+ ldns_status s;
+ char* address_str = NULL;
+
+ s = ldns_dane_verify(tlsas, cert, extra_certs, validate_store);
+ if (address) {
+ address_str = ldns_rdf2str(address);
+ fprintf(stdout, "%s", address_str ? address_str : "<address>");
+ free(address_str);
+ } else {
+ X509_NAME_print_ex_fp(stdout,
+ X509_get_subject_name(cert), 0, 0);
+ }
+ if (s == LDNS_STATUS_OK) {
+ if (verify_server_name &&
+ ! dane_verify_server_name(cert, name)) {
+
+ fprintf(stdout, " did not dane-validate, because:"
+ " the certificate name did not match"
+ " the server name\n");
+ return false;
+ }
+ fprintf(stdout, " dane-validated successfully\n");
+ return true;
+ } else if (assume_pkix_validity &&
+ s == LDNS_STATUS_DANE_PKIX_DID_NOT_VALIDATE) {
+ fprintf(stdout, " dane-validated successfully,"
+ " because PKIX is assumed valid\n");
+ return true;
+ }
+ fprintf(stdout, " did not dane-validate, because: %s\n",
+ ldns_get_errorstr_by_id(s));
+ return false;
+}
+#endif /* defined(USE_DANE_VERIFY) && OPENSSL_VERSION_NUMBER < 0x10100000 */
+
+/**
+ * Return either an A or AAAA rdf, based on the given
+ * string. If it it not a valid ip address, return null.
+ *
+ * Caller receives ownership of returned rdf (if not null),
+ * and must free it.
+ */
+static inline ldns_rdf* rdf_addr_frm_str(const char* str) {
+ ldns_rdf *a = ldns_rdf_new_frm_str(LDNS_RDF_TYPE_A, str);
+ if (!a) {
+ a = ldns_rdf_new_frm_str(LDNS_RDF_TYPE_AAAA, str);
+ }
+ return a;
+}
+
+
+int
+main(int argc, char* const* argv)
+{
+ int c;
+ enum { UNDETERMINED, VERIFY, CREATE } mode = UNDETERMINED;
+
+ ldns_status s;
+ size_t i;
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000 && ! defined(HAVE_LIBRESSL)
+ size_t j, usable_tlsas = 0;
+ X509_STORE_CTX *store_ctx = NULL;
+#endif /* OPENSSL_VERSION_NUMBER >= 0x10100000 */
+
+ bool print_tlsa_as_type52 = false;
+ bool assume_dnssec_validity = false;
+ bool assume_pkix_validity = false;
+ bool verify_server_name = true;
+ bool interact = false;
+
+#if HAVE_DANE_CA_FILE
+ const char* CAfile = LDNS_DANE_CA_FILE;
+#else
+ const char* CAfile = NULL;
+#endif
+#if HAVE_DANE_CA_PATH
+ const char* CApath = LDNS_DANE_CA_PATH;
+#else
+ const char* CApath = NULL;
+#endif
+ char* cert_file = NULL;
+ X509* cert = NULL;
+ STACK_OF(X509)* extra_certs = NULL;
+
+ ldns_rr_list* keys = ldns_rr_list_new();
+ size_t nkeys = 0;
+ bool do_sigchase = false;
+
+ ldns_rr_list* addresses = ldns_rr_list_new();
+ ldns_rr* address_rr;
+ ldns_rdf* address;
+
+ int ai_family = AF_UNSPEC;
+ int transport = LDNS_DANE_TRANSPORT_TCP;
+
+ char* name_str = NULL; /* supress uninitialized warning */
+ ldns_rdf* name;
+ uint16_t port = 0; /* supress uninitialized warning */
+
+ ldns_resolver* res = NULL;
+ ldns_rdf* nameserver_rdf = NULL;
+ ldns_rdf* tlsa_owner = NULL;
+ char* tlsa_owner_str = NULL;
+ ldns_rr_list* tlsas = NULL;
+ char* tlsas_file = NULL;
+
+ /* For extracting service port and transport from tla_owner. */
+ ldns_rdf* port_rdf = NULL;
+ char* port_str = NULL;
+ ldns_rdf* transport_rdf = NULL;
+ char* transport_str = NULL;
+
+ ldns_rr_list* originals = NULL; /* original tlsas (before
+ * transform), but also used
+ * as temporary.
+ */
+
+ ldns_tlsa_certificate_usage certificate_usage = 666;
+ int offset = -1;
+ ldns_tlsa_selector selector = 666;
+ ldns_tlsa_matching_type matching_type = 666;
+
+
+ X509_STORE *store = NULL;
+
+ SSL_CTX* ctx = NULL;
+ SSL* ssl = NULL;
+
+ int no_tlsas_exit_status = EXIT_SUCCESS;
+ int exit_success = EXIT_SUCCESS;
+
+ bool success = true;
+
+ if (! keys || ! addresses) {
+ MEMERR("ldns_rr_list_new");
+ }
+ while((c = getopt(argc, argv, "46a:bc:df:hik:no:p:r:sSt:TuvV:")) != -1){
+ switch(c) {
+ case 'h':
+ print_usage("ldns-dane");
+ break;
+ case '4':
+ ai_family = AF_INET;
+ break;
+ case '6':
+ ai_family = AF_INET6;
+ break;
+ case 'r':
+ if (nameserver_rdf) {
+ fprintf(stderr, "Can only specify -r once\n");
+ exit(EXIT_FAILURE);
+ }
+ nameserver_rdf = rdf_addr_frm_str(optarg);
+ if (!nameserver_rdf) {
+ fprintf(stderr,
+ "Could not interpret address %s\n",
+ optarg);
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 'a':
+ s = ldns_str2rdf_a(&address, optarg);
+ if (s == LDNS_STATUS_OK) {
+ address_rr = ldns_rr_new_frm_type(
+ LDNS_RR_TYPE_A);
+ } else {
+ s = ldns_str2rdf_aaaa(&address, optarg);
+ if (s == LDNS_STATUS_OK) {
+ address_rr = ldns_rr_new_frm_type(
+ LDNS_RR_TYPE_AAAA);
+ } else {
+ fprintf(stderr,
+ "Could not interpret address "
+ "%s\n",
+ optarg);
+ exit(EXIT_FAILURE);
+ }
+ }
+ (void) ldns_rr_a_set_address(address_rr, address);
+ for (i = 0; i < ldns_rr_list_rr_count(addresses); i++){
+ if (ldns_rdf_compare(address,
+ ldns_rr_a_address(
+ ldns_rr_list_rr(addresses, i))) == 0) {
+ break;
+ }
+ }
+ if (i >= ldns_rr_list_rr_count(addresses)) {
+ if (! ldns_rr_list_push_rr(addresses,
+ address_rr)) {
+ MEMERR("ldns_rr_list_push_rr");
+ }
+ }
+ break;
+ case 'b':
+ print_tlsa_as_type52 = true;
+ /* TODO: do it with output formats... maybe... */
+ break;
+ case 'c':
+ cert_file = optarg; /* checking in SSL stuff below */
+ break;
+ case 'd':
+ assume_dnssec_validity = true;
+ break;
+ case 'f':
+ CAfile = optarg;
+ break;
+ case 'i':
+ interact = true;
+ break;
+ case 'k':
+ s = read_key_file(optarg, keys);
+ if (s == LDNS_STATUS_FILE_ERR) {
+ fprintf(stderr, "Error opening %s: %s\n",
+ optarg, strerror(errno));
+ }
+ LDNS_ERR(s, "Could not parse key file");
+ if (ldns_rr_list_rr_count(keys) == nkeys) {
+ fprintf(stderr, "No keys found in file"
+ " %s\n", optarg);
+ exit(EXIT_FAILURE);
+ }
+ nkeys = ldns_rr_list_rr_count(keys);
+ break;
+ case 'n':
+ verify_server_name = false;
+ break;
+ case 'o':
+ offset = atoi(optarg); /* todo check if all numeric */
+ break;
+ case 'p':
+ CApath = optarg;
+ break;
+ case 's':
+ assume_pkix_validity = true;
+ break;
+ case 'S':
+ do_sigchase = true;
+ break;
+ case 't':
+ tlsas_file = optarg;
+ break;
+ case 'T':
+ no_tlsas_exit_status = NO_TLSAS_EXIT_STATUS;
+ break;
+ case 'u':
+ transport = LDNS_DANE_TRANSPORT_UDP;
+ break;
+ case 'v':
+ printf("ldns-dane version %s (ldns version %s)\n",
+ LDNS_VERSION, ldns_version());
+ exit(EXIT_SUCCESS);
+ break;
+/* case 'V':
+ verbosity = atoi(optarg);
+ break;
+ */
+ }
+ }
+
+ /* Filter out given IPv4 addresses when -6 was given,
+ * and IPv6 addresses when -4 was given.
+ */
+ if (ldns_rr_list_rr_count(addresses) > 0 &&
+ ai_family != AF_UNSPEC) {
+ originals = addresses;
+ addresses = rr_list_filter_rr_type(originals,
+ (ai_family == AF_INET
+ ? LDNS_RR_TYPE_A : LDNS_RR_TYPE_AAAA));
+ ldns_rr_list_free(originals);
+ if (addresses == NULL) {
+ MEMERR("rr_list_filter_rr_type");
+ }
+ if (ldns_rr_list_rr_count(addresses) == 0) {
+ fprintf(stderr,
+ "No addresses of the specified type remain\n");
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (do_sigchase) {
+ if (nkeys == 0) {
+ (void) read_key_file(LDNS_TRUST_ANCHOR_FILE, keys);
+ nkeys = ldns_rr_list_rr_count(keys);
+
+ if (nkeys == 0) {
+ fprintf(stderr, "Unable to chase "
+ "signature without keys.\n");
+ exit(EXIT_FAILURE);
+ }
+ }
+ } else {
+ keys = NULL;
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0) {
+
+ print_usage("ldns-dane");
+ }
+ if (strncasecmp(*argv, "create", strlen(*argv)) == 0) {
+
+ mode = CREATE;
+ argc--;
+ argv++;
+
+#ifdef USE_DANE_VERIFY
+ } else if (strncasecmp(*argv, "verify", strlen(*argv)) == 0) {
+
+ mode = VERIFY;
+ argc--;
+ argv++;
+
+ } else {
+ fprintf(stderr, "Specify create or verify mode\n");
+#else
+ } else {
+ fprintf(stderr, "Specify create mode\n");
+#endif
+ exit(EXIT_FAILURE);
+ }
+
+#ifndef USE_DANE_VERIFY
+ (void)transport_str;
+ (void)transport_rdf;
+ (void)port_str;
+ (void)port_rdf;
+ (void)interact;
+#else
+ if (mode == VERIFY && argc == 0) {
+
+ if (! tlsas_file) {
+ fprintf(stderr, "ERROR! Nothing given to verify\n");
+ exit(EXIT_FAILURE);
+ }
+ s = dane_read_tlsas_from_file(&tlsas, tlsas_file, NULL);
+ LDNS_ERR(s, "could not read tlsas from file");
+
+ /* extract port, transport and hostname from TLSA owner name */
+
+ if (ldns_rr_list_rr_count(tlsas) == 0) {
+
+ fprintf(stderr, "ERROR! No TLSA records to extract "
+ "service port, transport and hostname"
+ "\n");
+ exit(EXIT_FAILURE);
+ }
+ tlsa_owner = ldns_rr_list_owner(tlsas);
+ if (ldns_dname_label_count(tlsa_owner) < 2) {
+ fprintf(stderr, "ERROR! To few labels in TLSA owner\n");
+ exit(EXIT_FAILURE);
+ }
+ do {
+ s = LDNS_STATUS_MEM_ERR;
+ port_rdf = ldns_dname_label(tlsa_owner, 0);
+ if (! port_rdf) {
+ break;
+ }
+ port_str = ldns_rdf2str(port_rdf);
+ if (! port_str) {
+ break;
+ }
+ if (*port_str != '_') {
+ fprintf(stderr, "ERROR! Badly formatted "
+ "service port label in the "
+ "TLSA owner name\n");
+ exit(EXIT_FAILURE);
+ }
+ if (port_str[strlen(port_str) - 1] == '.') {
+ port_str[strlen(port_str) - 1] = '\000';
+ }
+ port = (uint16_t) dane_int_within_range(
+ port_str + 1, 65535, "port");
+ s = LDNS_STATUS_OK;
+ } while (false);
+ LDNS_ERR(s, "could not extract service port from TLSA owner");
+
+ do {
+ s = LDNS_STATUS_MEM_ERR;
+ transport_rdf = ldns_dname_label(tlsa_owner, 1);
+ if (! transport_rdf) {
+ break;
+ }
+ transport_str = ldns_rdf2str(transport_rdf);
+ if (! transport_str) {
+ break;
+ }
+ if (transport_str[strlen(transport_str) - 1] == '.') {
+ transport_str[strlen(transport_str) - 1] =
+ '\000';
+ }
+ if (strcmp(transport_str, "_tcp") == 0) {
+
+ transport = LDNS_DANE_TRANSPORT_TCP;
+
+ } else if (strcmp(transport_str, "_udp") == 0) {
+
+ transport = LDNS_DANE_TRANSPORT_UDP;
+
+ } else if (strcmp(transport_str, "_sctp") == 0) {
+
+ transport = LDNS_DANE_TRANSPORT_SCTP;
+
+ } else {
+ fprintf(stderr, "ERROR! Badly formatted "
+ "transport label in the "
+ "TLSA owner name\n");
+ exit(EXIT_FAILURE);
+ }
+ s = LDNS_STATUS_OK;
+ break;
+ } while(false);
+ LDNS_ERR(s, "could not extract transport from TLSA owner");
+
+ tlsa_owner_str = ldns_rdf2str(tlsa_owner);
+ if (! tlsa_owner_str) {
+ MEMERR("ldns_rdf2str");
+ }
+ name = ldns_dname_clone_from(tlsa_owner, 2);
+ if (! name) {
+ MEMERR("ldns_dname_clone_from");
+ }
+ name_str = ldns_rdf2str(name);
+ if (! name_str) {
+ MEMERR("ldns_rdf2str");
+ }
+
+
+ } else
+#endif /* USE_DANE_VERIFY */
+ if (argc < 2) {
+
+ print_usage("ldns-dane");
+
+ } else {
+ name_str = *argv++; argc--;
+ s = ldns_str2rdf_dname(&name, name_str);
+ LDNS_ERR(s, "could not ldns_str2rdf_dname");
+
+ port = (uint16_t)dane_int_within_range(*argv++, 65535, "port");
+ --argc;
+
+ s = ldns_dane_create_tlsa_owner(&tlsa_owner,
+ name, port, transport);
+ LDNS_ERR(s, "could not create TLSA owner name");
+ tlsa_owner_str = ldns_rdf2str(tlsa_owner);
+ if (! tlsa_owner_str) {
+ MEMERR("ldns_rdf2str");
+ }
+ }
+
+ switch (mode) {
+ case VERIFY:
+ if (argc > 0) {
+
+ print_usage("ldns-dane");
+ }
+ if (tlsas_file) {
+
+ s = dane_read_tlsas_from_file(&tlsas, tlsas_file,
+ tlsa_owner);
+ LDNS_ERR(s, "could not read tlas from file");
+ } else {
+ /* lookup tlsas */
+ s = dane_setup_resolver(&res, nameserver_rdf,
+ keys, assume_dnssec_validity);
+ LDNS_ERR(s, "could not dane_setup_resolver");
+ s = dane_query(&tlsas, res, tlsa_owner,
+ LDNS_RR_TYPE_TLSA, LDNS_RR_CLASS_IN,
+ false);
+ ldns_resolver_free(res);
+ }
+
+ if (s == LDNS_STATUS_DANE_INSECURE) {
+
+ fprintf(stderr, "Warning! TLSA records for %s "
+ "were found, but were insecure.\n"
+ "PKIX validation without DANE will be "
+ "performed. If you wish to perform DANE\n"
+ "even though the RR's are insecure, "
+ "use the -d option.\n", tlsa_owner_str);
+
+ exit_success = no_tlsas_exit_status;
+
+ } else if (s != LDNS_STATUS_OK) {
+
+ ldns_err("dane_query", s);
+
+ } else if (ldns_rr_list_rr_count(tlsas) == 0) {
+
+ fprintf(stderr, "Warning! No TLSA records for %s "
+ "were found.\n"
+ "PKIX validation without DANE will be "
+ "performed.\n", ldns_rdf2str(tlsa_owner));
+
+ exit_success = no_tlsas_exit_status;
+
+ } else if (assume_pkix_validity) { /* number of tlsa's > 0 */
+
+ /* transform type "CA constraint" to "Trust anchor
+ * assertion" and "Service Certificate Constraint"
+ * to "Domain Issues Certificate"
+ */
+ originals = tlsas;
+ tlsas = dane_no_pkix_transform(originals);
+ }
+
+ break;
+
+ case CREATE:
+ if (argc > 0) {
+ certificate_usage = dane_int_within_range_table(
+ *argv++, 3, "certificate usage",
+ dane_certificate_usage_table);
+ argc--;
+ } else {
+ certificate_usage = LDNS_TLSA_USAGE_DANE_EE;
+ }
+ if (argc > 0) {
+ selector = dane_int_within_range_table(
+ *argv++, 1, "selector",
+ dane_selector_table);
+ argc--;
+ } else {
+ selector = LDNS_TLSA_SELECTOR_SPKI;
+ }
+ if (argc > 0) {
+ matching_type = dane_int_within_range_table(
+ *argv++, 2, "matching type",
+ dane_matching_type_table);
+
+ argc--;
+ } else {
+ matching_type = LDNS_TLSA_MATCHING_TYPE_SHA2_256;
+ }
+ if (argc > 0) {
+
+ print_usage("ldns-dane");
+ }
+ if ((certificate_usage == LDNS_TLSA_USAGE_CA_CONSTRAINT ||
+ certificate_usage ==
+ LDNS_TLSA_USAGE_SERVICE_CERTIFICATE_CONSTRAINT) &&
+ ! CAfile && ! CApath && ! assume_pkix_validity) {
+
+ fprintf(stderr,
+ "When using the \"CA constraint\" or "
+ "\"Service certificate constraint\",\n"
+ "-f <CAfile> and/or -p <CApath> options "
+ "must be given to perform PKIX validation.\n\n"
+ "PKIX validation may be turned off "
+ "with the -s option. Note that with\n"
+ "\"CA constraint\" the verification process "
+ "should then end with a self-signed\n"
+ "certificate which must be present "
+ "in the server certificate chain.\n\n");
+
+ exit(EXIT_FAILURE);
+ }
+ tlsas = ldns_rr_list_new();
+ break;
+ default:
+ fprintf(stderr, "Unreachable code\n");
+ assert(0);
+ }
+
+ /* ssl inititalize */
+ SSL_load_error_strings();
+ SSL_library_init();
+
+ /* ssl load validation store */
+ if (! assume_pkix_validity || CAfile || CApath) {
+ store = X509_STORE_new();
+ if (! store) {
+ ssl_err("could not X509_STORE_new");
+ }
+ if ((CAfile || CApath) && X509_STORE_load_locations(
+ store, CAfile, CApath) != 1) {
+ ssl_err("error loading CA certificates");
+ }
+ }
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000 || defined(HAVE_LIBRESSL)
+ ctx = SSL_CTX_new(SSLv23_client_method());
+#else
+ ctx = SSL_CTX_new(TLS_client_method());
+ if (ctx && SSL_CTX_dane_enable(ctx) <= 0) {
+ ssl_err("could not SSL_CTX_dane_enable");
+ }
+#endif
+ if (! ctx) {
+ ssl_err("could not SSL_CTX_new");
+ }
+ if (cert_file &&
+ SSL_CTX_use_certificate_chain_file(ctx, cert_file) != 1) {
+ ssl_err("error loading certificate");
+ }
+
+ if (cert_file) { /* ssl load certificate */
+
+ ssl = SSL_new(ctx);
+ if (! ssl) {
+ ssl_err("could not SSL_new");
+ }
+ cert = SSL_get_certificate(ssl);
+ if (! cert) {
+ ssl_err("could not SSL_get_certificate");
+ }
+#ifndef SSL_CTX_get_extra_chain_certs
+#ifndef S_SPLINT_S
+ extra_certs = ctx->extra_certs;
+#endif /* splint */
+#else
+ if(!SSL_CTX_get_extra_chain_certs(ctx, &extra_certs)) {
+ ssl_err("could not SSL_CTX_get_extra_chain_certs");
+ }
+#endif
+ switch (mode) {
+ case CREATE: dane_create(tlsas, tlsa_owner, certificate_usage,
+ offset, selector, matching_type,
+ cert, extra_certs, store,
+ verify_server_name, name);
+ break;
+#ifdef USE_DANE_VERIFY
+#if OPENSSL_VERSION_NUMBER < 0x10100000 || defined(HAVE_LIBRESSL)
+ case VERIFY: if (! dane_verify(tlsas, NULL,
+ cert, extra_certs, store,
+ verify_server_name, name,
+ assume_pkix_validity)) {
+ success = false;
+ }
+ break;
+#else /* OPENSSL_VERSION_NUMBER < 0x10100000 */
+ case VERIFY:
+ usable_tlsas = 0;
+ SSL_set_connect_state(ssl);
+ if (SSL_dane_enable(ssl, name_str) <= 0) {
+ ssl_err("could not SSL_dane_enable");
+ }
+ if (!verify_server_name) {
+ SSL_dane_set_flags(ssl, DANE_FLAG_NO_DANE_EE_NAMECHECKS);
+ }
+ for (j = 0; j < ldns_rr_list_rr_count(tlsas); j++) {
+ int ret;
+ ldns_rr *tlsa_rr = ldns_rr_list_rr(tlsas, j);
+
+ if (ldns_rr_get_type(tlsa_rr) != LDNS_RR_TYPE_TLSA) {
+ fprintf(stderr, "Skipping non TLSA RR: ");
+ ldns_rr_print(stderr, tlsa_rr);
+ fprintf(stderr, "\n");
+ continue;
+ }
+ if (ldns_rr_rd_count(tlsa_rr) != 4) {
+ fprintf(stderr, "Skipping TLSA with wrong rdata RR: ");
+ ldns_rr_print(stderr, tlsa_rr);
+ fprintf(stderr, "\n");
+ continue;
+ }
+ ret = SSL_dane_tlsa_add(ssl,
+ ldns_rdf2native_int8(ldns_rr_rdf(tlsa_rr, 0)),
+ ldns_rdf2native_int8(ldns_rr_rdf(tlsa_rr, 1)),
+ ldns_rdf2native_int8(ldns_rr_rdf(tlsa_rr, 2)),
+ ldns_rdf_data(ldns_rr_rdf(tlsa_rr, 3)),
+ ldns_rdf_size(ldns_rr_rdf(tlsa_rr, 3)));
+ if (ret < 0) {
+ ssl_err("could not SSL_dane_tlsa_add");
+ }
+ if (ret == 0) {
+ fprintf(stderr, "Skipping unusable TLSA RR: ");
+ ldns_rr_print(stderr, tlsa_rr);
+ fprintf(stderr, "\n");
+ continue;
+ }
+ usable_tlsas += 1;
+ }
+ if (!usable_tlsas) {
+ fprintf(stderr, "No usable TLSA records were found.\n"
+ "PKIX validation without DANE will be performed.\n");
+ }
+ if (!(store_ctx = X509_STORE_CTX_new())) {
+ ssl_err("could not SSL_new");
+ }
+ if (!X509_STORE_CTX_init(store_ctx, store, cert, extra_certs)) {
+ ssl_err("could not X509_STORE_CTX_init");
+ }
+ X509_STORE_CTX_set_default(store_ctx,
+ SSL_is_server(ssl) ? "ssl_client" : "ssl_server");
+ X509_VERIFY_PARAM_set1(X509_STORE_CTX_get0_param(store_ctx),
+ SSL_get0_param(ssl));
+ X509_STORE_CTX_set0_dane(store_ctx, SSL_get0_dane(ssl));
+ X509_NAME_print_ex_fp(stdout,
+ X509_get_subject_name(cert), 0, 0);
+ if (X509_verify_cert(store_ctx)) {
+ fprintf(stdout, " %s-validated successfully\n",
+ usable_tlsas
+ ? "dane" : "PKIX");
+ } else {
+ fprintf(stdout, " did not dane-validate, because: %s\n",
+ X509_verify_cert_error_string(
+ X509_STORE_CTX_get_error(store_ctx)));
+ success = false;
+ }
+ if (store_ctx) {
+ X509_STORE_CTX_free(store_ctx);
+ }
+ break;
+#endif /* OPENSSL_VERSION_NUMBER < 0x10100000 */
+#endif /* ifdef USE_DANE_VERIFY */
+ default: break; /* suppress warning */
+ }
+ SSL_free(ssl);
+
+ } else {/* No certificate file given, creation/validation via TLS. */
+
+ /* We need addresses to connect to */
+ if (ldns_rr_list_rr_count(addresses) == 0) {
+ s = dane_setup_resolver(&res, nameserver_rdf,
+ keys, assume_dnssec_validity);
+ LDNS_ERR(s, "could not dane_setup_resolver");
+ ldns_rr_list_free(addresses);
+ addresses =dane_lookup_addresses(res, name, ai_family);
+ ldns_resolver_free(res);
+ }
+ if (ldns_rr_list_rr_count(addresses) == 0) {
+ fprintf(stderr, "No addresses for %s\n", name_str);
+ exit(EXIT_FAILURE);
+ }
+
+ /* for all addresses, setup SSL and retrieve certificates */
+ for (i = 0; i < ldns_rr_list_rr_count(addresses); i++) {
+
+ ssl = SSL_new(ctx);
+ if (! ssl) {
+ ssl_err("could not SSL_new");
+ }
+ address = ldns_rr_a_address(
+ ldns_rr_list_rr(addresses, i));
+ assert(address != NULL);
+#if OPENSSL_VERSION_NUMBER >= 0x10100000 && ! defined(HAVE_LIBRESSL)
+ if (mode == VERIFY) {
+ usable_tlsas = 0;
+ if (SSL_dane_enable(ssl, name_str) <= 0) {
+ ssl_err("could not SSL_dane_enable");
+ }
+ if (!verify_server_name) {
+ SSL_dane_set_flags(ssl, DANE_FLAG_NO_DANE_EE_NAMECHECKS);
+ }
+ for (j = 0; j < ldns_rr_list_rr_count(tlsas); j++) {
+ int ret;
+ ldns_rr *tlsa_rr = ldns_rr_list_rr(tlsas, j);
+
+ if (ldns_rr_get_type(tlsa_rr) != LDNS_RR_TYPE_TLSA) {
+ fprintf(stderr, "Skipping non TLSA RR: ");
+ ldns_rr_print(stderr, tlsa_rr);
+ fprintf(stderr, "\n");
+ continue;
+ }
+ if (ldns_rr_rd_count(tlsa_rr) != 4) {
+ fprintf(stderr, "Skipping TLSA with wrong rdata RR: ");
+ ldns_rr_print(stderr, tlsa_rr);
+ fprintf(stderr, "\n");
+ continue;
+ }
+ ret = SSL_dane_tlsa_add(ssl,
+ ldns_rdf2native_int8(ldns_rr_rdf(tlsa_rr, 0)),
+ ldns_rdf2native_int8(ldns_rr_rdf(tlsa_rr, 1)),
+ ldns_rdf2native_int8(ldns_rr_rdf(tlsa_rr, 2)),
+ ldns_rdf_data(ldns_rr_rdf(tlsa_rr, 3)),
+ ldns_rdf_size(ldns_rr_rdf(tlsa_rr, 3)));
+ if (ret < 0) {
+ ssl_err("could not SSL_dane_tlsa_add");
+ }
+ if (ret == 0) {
+ fprintf(stderr, "Skipping unusable TLSA RR: ");
+ ldns_rr_print(stderr, tlsa_rr);
+ fprintf(stderr, "\n");
+ continue;
+ }
+ usable_tlsas += 1;
+ }
+ if (!usable_tlsas) {
+ fprintf(stderr, "No usable TLSA records were found.\n"
+ "PKIX validation without DANE will be performed.\n");
+ }
+ }
+#endif /* OPENSSL_VERSION_NUMBER >= 0x10100000 */
+ s = ssl_connect_and_get_cert_chain(&cert, &extra_certs,
+ ssl, name_str, address,port, transport);
+ if (s == LDNS_STATUS_NETWORK_ERR) {
+ fprintf(stderr, "Could not connect to ");
+ ldns_rdf_print(stderr, address);
+ fprintf(stderr, " %d\n", (int) port);
+
+ /* All addresses should succeed */
+ success = false;
+ continue;
+ }
+ LDNS_ERR(s, "could not get cert chain from ssl");
+#if OPENSSL_VERSION_NUMBER >= 0x10100000 && ! defined(HAVE_LIBRESSL)
+
+ if (mode == VERIFY) {
+ char *address_str = ldns_rdf2str(address);
+ long verify_result = SSL_get_verify_result(ssl);
+
+ fprintf(stdout, "%s", address_str ? address_str : "<address>");
+ free(address_str);
+
+ if (verify_result == X509_V_OK) {
+ fprintf(stdout, " %s-validated successfully\n",
+ usable_tlsas
+ ? "dane" : "PKIX");
+ } else {
+ fprintf(stdout, " did not dane-validate, because: %s\n",
+ X509_verify_cert_error_string(verify_result));
+ success = false;
+ }
+ }
+#endif /* OPENSSL_VERSION_NUMBER >= 0x10100000 */
+ switch (mode) {
+ case CREATE: dane_create(tlsas, tlsa_owner,
+ certificate_usage, offset,
+ selector, matching_type,
+ cert, extra_certs, store,
+ verify_server_name, name);
+ break;
+
+#ifdef USE_DANE_VERIFY
+ case VERIFY:
+#if OPENSSL_VERSION_NUMBER < 0x10100000 || defined(HAVE_LIBRESSL)
+ if (! dane_verify(tlsas, address,
+ cert, extra_certs, store,
+ verify_server_name, name,
+ assume_pkix_validity)) {
+ success = false;
+
+ }
+#endif /* OPENSSL_VERSION_NUMBER < 0x10100000 */
+ if (success && interact) {
+ ssl_interact(ssl);
+ }
+ break;
+#endif /* USE_DANE_VERIFY */
+
+ default: break; /* suppress warning */
+ }
+ while (SSL_shutdown(ssl) == 0);
+ SSL_free(ssl);
+ } /* end for all addresses */
+ } /* end No certification file */
+
+ if (mode == CREATE) {
+ if (print_tlsa_as_type52) {
+ print_rr_list_as_TYPEXXX(stdout, tlsas);
+ } else {
+ ldns_rr_list_print(stdout, tlsas);
+ }
+ }
+ ldns_rr_list_deep_free(tlsas);
+
+ /* cleanup */
+ SSL_CTX_free(ctx);
+
+ if (nameserver_rdf) {
+ ldns_rdf_deep_free(nameserver_rdf);
+ }
+ if (store) {
+ X509_STORE_free(store);
+ }
+ if (tlsa_owner_str) {
+ LDNS_FREE(tlsa_owner_str);
+ }
+ if (tlsa_owner) {
+ ldns_rdf_free(tlsa_owner);
+ }
+ if (addresses) {
+ ldns_rr_list_deep_free(addresses);
+ }
+ if (success) {
+ exit(exit_success);
+ } else {
+ exit(EXIT_FAILURE);
+ }
+}
+#else /* HAVE_SSL */
+
+int
+main(int argc, char **argv)
+{
+ fprintf(stderr, "ldns-dane needs OpenSSL support, "
+ "which has not been compiled in\n");
+ return 1;
+}
+#endif /* HAVE_SSL */
+
+#else /* USE_DANE */
+
+int
+main(int argc, char **argv)
+{
+ (void)argc;
+ (void)argv;
+
+ fprintf(stderr, "dane support was disabled with this build of ldns, "
+ "and has not been compiled in\n");
+ return 1;
+}
+
+#endif /* USE_DANE */