From 03bddb40e14da8825c8967b10f0360e261bbca57 Mon Sep 17 00:00:00 2001 From: Willem Toorop Date: Mon, 3 Oct 2022 13:21:36 +0200 Subject: [PATCH] DANE records can be passed along each upstream with "dane_records" key Needs to be printed as well --- src/context.c | 80 ++++++++++++++++++++++++++++++++++++ src/context.h | 11 +++++ src/getdns/getdns_extra.h.in | 2 + src/openssl/tls.c | 64 +++++++++++++++++++++++++++++ src/stub.c | 9 +++- src/tls.h | 12 ++++++ stubby | 2 +- 7 files changed, 177 insertions(+), 3 deletions(-) diff --git a/src/context.c b/src/context.c index 5f41300c..db648dd8 100644 --- a/src/context.c +++ b/src/context.c @@ -611,6 +611,8 @@ _getdns_upstreams_dereference(getdns_upstreams *upstreams) ; upstreams->count--, upstream++ ) { sha256_pin_t *pin = upstream->tls_pubkey_pinset; + dane_record_t *dane_record = upstream->tls_dane_records; + if (upstream->loop && ( upstream->event.read_cb || upstream->event.write_cb || upstream->event.timeout_cb) ) { @@ -652,6 +654,12 @@ _getdns_upstreams_dereference(getdns_upstreams *upstreams) pin = nextpin; } upstream->tls_pubkey_pinset = NULL; + while (dane_record) { + dane_record_t *next_dane_record = dane_record->next; + GETDNS_FREE(upstreams->mf, dane_record); + dane_record = next_dane_record; + } + upstream->tls_dane_records = NULL; if (upstream->tls_cipher_list) GETDNS_FREE(upstreams->mf, upstream->tls_cipher_list); if (upstream->tls_ciphersuites) @@ -966,6 +974,7 @@ upstream_init(getdns_upstream *upstream, upstream->last_tls_auth_state = GETDNS_AUTH_NONE; upstream->best_tls_auth_state = GETDNS_AUTH_NONE; upstream->tls_pubkey_pinset = NULL; + upstream->tls_dane_records = NULL; upstream->loop = NULL; (void) getdns_eventloop_event_init( &upstream->event, upstream, NULL, NULL, NULL); @@ -2786,6 +2795,52 @@ getdns_context_set_dnssec_allowed_skew(struct getdns_context *context, return GETDNS_RETURN_GOOD; } /* getdns_context_set_dnssec_allowed_skew */ +static getdns_return_t +_getdns_list2dane_records(struct mem_funcs *mf, const getdns_list *dr_list, + dane_record_t **tls_dane_records) +{ + size_t i; + getdns_return_t r; + getdns_dict *dr_dict; + + assert(tls_dane_records); + + for (i = 0; !(r = getdns_list_get_dict(dr_list, i, &dr_dict)); i++) { + uint32_t usage; + uint32_t selector; + uint32_t type; + getdns_bindata *data; + getdns_dict *rdata; + dane_record_t *dane_record; + + if (!getdns_dict_get_dict(dr_dict, "rdata", &rdata)) { + /* only scan TLSA RRs */ + if (!getdns_dict_get_int(dr_dict, "type", &type) + && type != GETDNS_RRTYPE_TLSA) + continue; + dr_dict = rdata; + } + if ((r = getdns_dict_get_int(dr_dict, "certificate_usage", &usage)) + || (r = getdns_dict_get_int(dr_dict, "selector", &selector)) + || (r = getdns_dict_get_int(dr_dict, "matching_type", &type)) + || (r = getdns_dict_get_bindata(dr_dict, + "certificate_association_data", &data))) + return r; + + if (!(dane_record = (dane_record_t *)GETDNS_XMALLOC( + *mf, uint8_t, sizeof(dane_record_t) + data->size))) + return GETDNS_RETURN_MEMORY_ERROR; + dane_record->next = *tls_dane_records; + dane_record->usage = (uint8_t)usage; + dane_record->selector = (uint8_t)selector; + dane_record->type = (uint8_t)type; + dane_record->size = data->size; + memcpy(dane_record->data, data->data, data->size); + *tls_dane_records = dane_record; + } + return r == GETDNS_RETURN_NO_SUCH_LIST_ITEM ? GETDNS_RETURN_GOOD : r; +} + /* * getdns_context_set_upstream_recursive_servers * @@ -2992,6 +3047,7 @@ getdns_context_set_upstream_recursive_servers(struct getdns_context *context, upstream->transport = getdns_upstream_transports[j]; if (dict && getdns_upstream_transports[j] == GETDNS_TRANSPORT_TLS) { getdns_list *pubkey_pinset = NULL; + getdns_list *dane_records = NULL; getdns_bindata *tls_cipher_list = NULL; getdns_bindata *tls_ciphersuites = NULL; getdns_bindata *tls_curves_list = NULL; @@ -3031,6 +3087,30 @@ getdns_context_set_upstream_recursive_servers(struct getdns_context *context, goto invalid_parameter; } } + if (!(r = getdns_dict_get_list( + dict, "dane_records", &dane_records))) { + if ((r = _getdns_list2dane_records( + &(upstreams->mf), dane_records, + &(upstream->tls_dane_records)))) { + _getdns_upstream_log(upstream, + GETDNS_LOG_UPSTREAMS, + GETDNS_LOG_ERR, + "%-40s : Upstream : could" + " not parse dane_records: " + "%s\n", upstream->addr_str, + getdns_get_errorstr_by_id(r)); + freeaddrinfo(ai); + goto invalid_parameter; + } + } else if (r != GETDNS_RETURN_NO_SUCH_DICT_NAME) + _getdns_upstream_log(upstream, + GETDNS_LOG_UPSTREAMS, + GETDNS_LOG_ERR, + "%-40s : Upstream : could not " + "get dane_records: %s\n", + upstream->addr_str, + getdns_get_errorstr_by_id(r)); + (void) getdns_dict_get_bindata( dict, "tls_cipher_list", &tls_cipher_list); upstream->tls_cipher_list = tls_cipher_list diff --git a/src/context.h b/src/context.h index a21368b2..56f9805f 100644 --- a/src/context.h +++ b/src/context.h @@ -132,6 +132,16 @@ typedef struct sha256_pin { struct sha256_pin *next; } sha256_pin_t; +/* for doing DANE authentication of TLS-capable upstreams: */ +typedef struct dane_record { + struct dane_record *next; + uint8_t usage; + uint8_t selector; + uint8_t type; + size_t size; + uint8_t data[]; +} dane_record_t; + typedef struct getdns_upstream { /* backpointer to containing upstreams structure */ struct getdns_upstreams *upstreams; @@ -223,6 +233,7 @@ typedef struct getdns_upstream { /* Auth credentials */ char tls_auth_name[256]; sha256_pin_t *tls_pubkey_pinset; + dane_record_t *tls_dane_records; /* When requests have been scheduled asynchronously on an upstream * that is kept open, and a synchronous call is then done with the diff --git a/src/getdns/getdns_extra.h.in b/src/getdns/getdns_extra.h.in index b6c58645..135325db 100644 --- a/src/getdns/getdns_extra.h.in +++ b/src/getdns/getdns_extra.h.in @@ -584,6 +584,8 @@ typedef enum getdns_loglevel_type { #define GETDNS_LOG_INFO_TEXT "Informational message" #define GETDNS_LOG_DEBUG_TEXT "Debug-level message" +#define GETDNS_LOG_UPSTREAMS 0x1000 +#define GETDNS_LOG_UPSTREAMS_TEXT "Log messages about upstreams" #define GETDNS_LOG_UPSTREAM_STATS 0x3000 #define GETDNS_LOG_UPSTREAM_STATS_TEXT "Log messages about upstream statistics" #define GETDNS_LOG_SYS_STUB 0x2000 diff --git a/src/openssl/tls.c b/src/openssl/tls.c index f58cb013..275f8803 100644 --- a/src/openssl/tls.c +++ b/src/openssl/tls.c @@ -995,6 +995,70 @@ getdns_return_t _getdns_tls_connection_set_host_pinset(_getdns_tls_connection* c return GETDNS_RETURN_GOOD; } +getdns_return_t _getdns_tls_connection_set_dane_records(_getdns_tls_connection* conn, const char* auth_name, const dane_record_t* dane_records) +{ + if (!conn || !conn->ssl || !auth_name) + return GETDNS_RETURN_INVALID_PARAMETER; + +#if 0 && defined(USE_DANESSL) + /* Stash auth name and pinset away for use in cert verification. */ + conn->auth_name = auth_name; + conn->dane_records = dane_records; +#endif + +#if defined(HAVE_SSL_DANE_ENABLE) + int osr = SSL_dane_enable(conn->ssl, *auth_name ? auth_name : NULL); + (void) osr; /* unused parameter */ + DEBUG_STUB("%s %-35s: DEBUG: SSL_dane_enable(\"%s\") -> %d\n" + , STUB_DEBUG_SETUP_TLS, __FUNC__, auth_name, osr); + SSL_set_verify(conn->ssl, SSL_VERIFY_PEER, _getdns_tls_verify_always_ok); + const dane_record_t *dane_p; + size_t n_records= 0; + for (dane_p = dane_records; dane_p; dane_p = dane_p->next) { + osr = SSL_dane_tlsa_add(conn->ssl, dane_p->usage, + dane_p->selector, dane_p->type, dane_p->data, dane_p->size); + DEBUG_STUB("%s %-35s: DEBUG: SSL_dane_tlsa_add() -> %d\n" + , STUB_DEBUG_SETUP_TLS, __FUNC__, osr); + if (osr > 0) + ++n_records; + } +#elif 0 && defined(USE_DANESSL) + conn->pinset = pinset; + if (pinset) { + const char *auth_names[2] = { auth_name, NULL }; + int osr = DANESSL_init(conn->ssl, + *auth_name ? auth_name : NULL, + *auth_name ? auth_names : NULL); + (void) osr; /* unused parameter */ + DEBUG_STUB("%s %-35s: DEBUG: DANESSL_init(\"%s\") -> %d\n" + , STUB_DEBUG_SETUP_TLS, __FUNC__, auth_name, osr); + SSL_set_verify(conn->ssl, SSL_VERIFY_PEER, _getdns_tls_verify_always_ok); + const sha256_pin_t *pin_p; + size_t n_pins = 0; + for (pin_p = pinset; pin_p; pin_p = pin_p->next) { + osr = DANESSL_add_tlsa(conn->ssl, 3, 1, "sha256", + (unsigned char *)pin_p->pin, SHA256_DIGEST_LENGTH); + DEBUG_STUB("%s %-35s: DEBUG: DANESSL_add_tlsa() -> %d\n" + , STUB_DEBUG_SETUP_TLS, __FUNC__, osr); + if (osr > 0) + ++n_pins; + osr = DANESSL_add_tlsa(conn->ssl, 2, 1, "sha256", + (unsigned char *)pin_p->pin, SHA256_DIGEST_LENGTH); + DEBUG_STUB("%s %-35s: DEBUG: DANESSL_add_tlsa() -> %d\n" + , STUB_DEBUG_SETUP_TLS, __FUNC__, osr); + if (osr > 0) + ++n_pins; + } + } else { + SSL_set_verify(conn->ssl, SSL_VERIFY_PEER, _getdns_tls_verify_always_ok); + } +#else +#error Must have either DANE SSL or OpenSSL v1.1. +#endif + return GETDNS_RETURN_GOOD; +} + + getdns_return_t _getdns_tls_connection_certificate_verify(_getdns_tls_connection* conn, long* errnum, const char** errmsg) { if (!conn || !conn->ssl) diff --git a/src/stub.c b/src/stub.c index 545f8994..03502921 100644 --- a/src/stub.c +++ b/src/stub.c @@ -1049,8 +1049,9 @@ tls_create_object(getdns_dns_req *dnsreq, int fd, getdns_upstream *upstream) /* Lack of host name is OK unless only authenticated * TLS is specified and we have no pubkey_pinset */ if (dnsreq->netreqs[0]->tls_auth_min == GETDNS_AUTHENTICATION_REQUIRED) { - if (upstream->tls_pubkey_pinset) { - DEBUG_STUB("%s %-35s: Proceeding with only pubkey pinning authentication\n", + if (upstream->tls_pubkey_pinset + || upstream->tls_dane_records) { + DEBUG_STUB("%s %-35s: Proceeding with only pubkey pinning and/or DANE authentication\n", STUB_DEBUG_SETUP_TLS, __FUNC__); } else { DEBUG_STUB("%s %-35s: ERROR:No auth name or pinset provided for this upstream for Strict TLS authentication\n", @@ -1075,6 +1076,10 @@ tls_create_object(getdns_dns_req *dnsreq, int fd, getdns_upstream *upstream) _getdns_tls_connection_set_host_pinset( tls, upstream->tls_auth_name, upstream->tls_pubkey_pinset); + if (upstream->tls_dane_records) + _getdns_tls_connection_set_dane_records( + tls, upstream->tls_auth_name, upstream->tls_dane_records); + /* Session resumption. There are trade-offs here. Want to do it when possible only if we have the right type of connection. Note a change to the upstream auth info creates a new upstream so never re-uses.*/ diff --git a/src/tls.h b/src/tls.h index 5662e362..71fb1705 100644 --- a/src/tls.h +++ b/src/tls.h @@ -43,6 +43,7 @@ /* Forward declare type. */ struct sha256_pin; struct getdns_log_config; +struct dane_record; /* Additional return codes required by TLS abstraction. Internal use only. */ #define GETDNS_RETURN_TLS_WANT_READ ((getdns_return_t) 420) @@ -332,6 +333,17 @@ getdns_return_t _getdns_tls_connection_setup_hostname_auth(_getdns_tls_connectio */ getdns_return_t _getdns_tls_connection_set_host_pinset(_getdns_tls_connection* conn, const char* auth_name, const struct sha256_pin* pinset); +/** + * Set host pinset. + * + * @param conn the connection. + * @param auth_name the hostname. + * @param dane_records the DANE records that must match. + * @return GETDNS_RETURN_GOOD if all OK. + * @return GETDNS_RETURN_INVALID_PARAMETER if conn is null or has no SSL. + */ +getdns_return_t _getdns_tls_connection_set_dane_records(_getdns_tls_connection* conn, const char* auth_name, const struct dane_record* dane_records); + /** * Get result of certificate verification. * diff --git a/stubby b/stubby index 3d563285..a550394f 160000 --- a/stubby +++ b/stubby @@ -1 +1 @@ -Subproject commit 3d5632852826736b14a5b86b6f19117f7bce97da +Subproject commit a550394f874817bd5fda022c34c0c96e7f819bc2