- Re-cloned zonemaster-ldns with --recurse-submodules so the bundled ldns C library source (including Changelog and configure.ac) is present - Added autoconf, automake, libtool to Dockerfile.backend ldns-build stage so libtoolize + autoreconf can generate ldns/configure during make Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2054 lines
50 KiB
C
2054 lines
50 KiB
C
/*
|
|
* 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>
|
|
|
|
#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_VERIFY
|
|
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 successful 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 */
|
|
|
|
#if OPENSSL_VERSION_NUMBER >= 0x10100000 && ! defined(HAVE_LIBRESSL)
|
|
static int _ldns_tls_verify_always_ok(int ok, X509_STORE_CTX *ctx)
|
|
{
|
|
(void)ok;
|
|
(void)ctx;
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* 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;
|
|
# ifdef USE_DANE_VERIFY
|
|
X509_STORE_CTX *store_ctx = NULL;
|
|
# endif /* USE_DANE_VERIFY */
|
|
#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; /* suppress uninitialized warning */
|
|
ldns_rdf* name;
|
|
uint16_t port = 0; /* suppress 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);
|
|
}
|
|
|
|
#if OPENSSL_VERSION_NUMBER < 0x10100000 || defined(HAVE_LIBRESSL)
|
|
/* ssl initialize */
|
|
SSL_load_error_strings();
|
|
SSL_library_init();
|
|
#endif
|
|
|
|
/* 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");
|
|
}
|
|
|
|
/* Use TLSv1.0 or above for connection. */
|
|
long flags = 0;
|
|
# ifdef SSL_OP_NO_SSLv2
|
|
flags |= SSL_OP_NO_SSLv2;
|
|
# endif
|
|
# ifdef SSL_OP_NO_SSLv3
|
|
flags |= SSL_OP_NO_SSLv3;
|
|
# endif
|
|
# ifdef SSL_OP_NO_COMPRESSION
|
|
flags |= SSL_OP_NO_COMPRESSION;
|
|
# endif
|
|
SSL_CTX_set_options(ctx, flags);
|
|
|
|
if (CAfile || CApath) {
|
|
if (!SSL_CTX_load_verify_locations(ctx, CAfile, CApath))
|
|
ssl_err("could not set verify locations\n");
|
|
|
|
} else if (!SSL_CTX_set_default_verify_paths(ctx))
|
|
ssl_err("could not set default verify paths\n");
|
|
#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");
|
|
exit_success = no_tlsas_exit_status;
|
|
}
|
|
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)) | (assume_pkix_validity ? 2 : 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");
|
|
|
|
exit_success = no_tlsas_exit_status;
|
|
if (assume_pkix_validity)
|
|
SSL_set_verify(ssl, SSL_VERIFY_PEER, _ldns_tls_verify_always_ok);
|
|
}
|
|
}
|
|
#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 */
|
|
}
|
|
(void)SSL_shutdown(ssl);
|
|
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 */
|