diff --git a/ChangeLog b/ChangeLog index 8d87442d..d7665bc7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,12 @@ * + * Update of unofficial extension to the API that supports stub mode + TLS verification. GETDNS_AUTHENTICATION_HOSTNAME is replaced by + GETDNS_AUTHENTICATION_REQUIRED (but remains available as an alias). + Upstreams can now be configured with either a hostname or a SPKI pinset + for TLS authentication (or both). If the GETDNS_AUTHENTICATION_REQUIRED + option is used at least one piece of authentication information must be + configured for each upstream, and all the configured authentication + information for an upstream must validate. * Remove STARTTLS implementation (no change to SPEC) * Enable TCP Fast Open when possible. Add OSX support for TFO. * Rename return_call_debugging to return_call_reporting diff --git a/configure.ac b/configure.ac index 4b0ed882..dcae8b46 100644 --- a/configure.ac +++ b/configure.ac @@ -98,7 +98,7 @@ AX_CHECK_COMPILE_FLAG([-xc99],[CFLAGS="$CFLAGS -xc99"],[],[]) AX_CHECK_COMPILE_FLAG([-Wall],[CFLAGS="$CFLAGS -Wall"],[],[]) case "$host_os" in - linux* ) CFLAGS="$CFLAGS -D_BSD_SOURCE" + linux* ) CFLAGS="$CFLAGS -D_BSD_SOURCE -D_DEFAULT_SOURCE" ;; solaris* ) CFLAGS="$CFLAGS -D__EXTENSIONS__" # for strdup() from ;; diff --git a/src/Makefile.in b/src/Makefile.in index fc323242..b380f67d 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -64,8 +64,8 @@ EXTENSION_LIBUV_LDFLAGS=@EXTENSION_LIBUV_LDFLAGS@ C99COMPATFLAGS=@C99COMPATFLAGS@ GETDNS_OBJ=const-info.lo convert.lo dict.lo dnssec.lo general.lo \ - list.lo request-internal.lo rr-dict.lo rr-iter.lo stub.lo sync.lo \ - util-internal.lo + list.lo request-internal.lo pubkey-pinning.lo rr-dict.lo \ + rr-iter.lo stub.lo sync.lo util-internal.lo GLDNS_OBJ=keyraw.lo gbuffer.lo wire2str.lo parse.lo parseutil.lo rrdef.lo \ str2wire.lo @@ -329,3 +329,5 @@ libmini_event.lo libmini_event.o: $(srcdir)/extension/libmini_event.c config.h $ libuv.lo libuv.o: $(srcdir)/extension/libuv.c config.h $(srcdir)/debug.h config.h $(srcdir)/types-internal.h \ getdns/getdns.h getdns/getdns_extra.h getdns/getdns.h $(srcdir)/util/rbtree.h \ $(srcdir)/getdns/getdns_ext_libuv.h getdns/getdns_extra.h +pubkey-pinning.lo pubkey-pinning.o: $(srcdir)/pubkey-pinning.c \ + config.h getdns/getdns.h diff --git a/src/const-info.c b/src/const-info.c index 3cfae20e..1288061c 100644 --- a/src/const-info.c +++ b/src/const-info.c @@ -69,6 +69,7 @@ static struct const_info consts_info[] = { { 618, "GETDNS_CONTEXT_CODE_TLS_AUTHENTICATION", GETDNS_CONTEXT_CODE_TLS_AUTHENTICATION_TEXT }, { 619, "GETDNS_CONTEXT_CODE_EDNS_CLIENT_SUBNET_PRIVATE", GETDNS_CONTEXT_CODE_EDNS_CLIENT_SUBNET_PRIVATE_TEXT }, { 620, "GETDNS_CONTEXT_CODE_TLS_QUERY_PADDING_BLOCKSIZE", GETDNS_CONTEXT_CODE_TLS_QUERY_PADDING_BLOCKSIZE_TEXT }, + { 621, "GETDNS_CONTEXT_CODE_PUBKEY_PINSET", GETDNS_CONTEXT_CODE_PUBKEY_PINSET_TEXT }, { 700, "GETDNS_CALLBACK_COMPLETE", GETDNS_CALLBACK_COMPLETE_TEXT }, { 701, "GETDNS_CALLBACK_CANCEL", GETDNS_CALLBACK_CANCEL_TEXT }, { 702, "GETDNS_CALLBACK_TIMEOUT", GETDNS_CALLBACK_TIMEOUT_TEXT }, @@ -89,7 +90,7 @@ static struct const_info consts_info[] = { { 1201, "GETDNS_TRANSPORT_TCP", GETDNS_TRANSPORT_TCP_TEXT }, { 1202, "GETDNS_TRANSPORT_TLS", GETDNS_TRANSPORT_TLS_TEXT }, { 1300, "GETDNS_AUTHENTICATION_NONE", GETDNS_AUTHENTICATION_NONE_TEXT }, - { 1301, "GETDNS_AUTHENTICATION_HOSTNAME", GETDNS_AUTHENTICATION_HOSTNAME_TEXT }, + { 1301, "GETDNS_AUTHENTICATION_REQUIRED", GETDNS_AUTHENTICATION_REQUIRED_TEXT }, }; static int const_info_cmp(const void *a, const void *b) diff --git a/src/context.c b/src/context.c index d99096af..982a24be 100644 --- a/src/context.c +++ b/src/context.c @@ -55,6 +55,7 @@ #include "stub.h" #include "list.h" #include "dict.h" +#include "pubkey-pinning.h" #define GETDNS_PORT_ZERO 0 #define GETDNS_PORT_DNS 53 @@ -515,6 +516,7 @@ _getdns_upstreams_dereference(getdns_upstreams *upstreams) ; upstreams->count ; upstreams->count--, upstream++ ) { + sha256_pin_t *pin = upstream->tls_pubkey_pinset; if (upstream->loop && ( upstream->event.read_cb || upstream->event.write_cb || upstream->event.timeout_cb) ) { @@ -530,6 +532,12 @@ _getdns_upstreams_dereference(getdns_upstreams *upstreams) } if (upstream->fd != -1) close(upstream->fd); + while (pin) { + sha256_pin_t *nextpin = pin->next; + GETDNS_FREE(upstreams->mf, pin); + pin = nextpin; + } + upstream->tls_pubkey_pinset = NULL; } GETDNS_FREE(upstreams->mf, upstreams); } @@ -669,6 +677,7 @@ upstream_init(getdns_upstream *upstream, upstream->tls_hs_state = GETDNS_HS_NONE; upstream->tls_auth_failed = 0; upstream->tls_auth_name[0] = '\0'; + upstream->tls_pubkey_pinset = NULL; upstream->tcp.write_error = 0; upstream->loop = NULL; (void) getdns_eventloop_event_init( @@ -1478,7 +1487,7 @@ getdns_context_set_tls_authentication(getdns_context *context, { RETURN_IF_NULL(context, GETDNS_RETURN_INVALID_PARAMETER); if (value != GETDNS_AUTHENTICATION_NONE && - value != GETDNS_AUTHENTICATION_HOSTNAME) { + value != GETDNS_AUTHENTICATION_REQUIRED) { return GETDNS_RETURN_CONTEXT_UPDATE_FAIL; } context->tls_auth = value; @@ -1943,6 +1952,7 @@ getdns_context_set_upstream_recursive_servers(struct getdns_context *context, upstream_init(upstream, upstreams, ai); upstream->transport = getdns_upstream_transports[j]; if (getdns_upstream_transports[j] == GETDNS_TRANSPORT_TLS) { + getdns_list *pubkey_pinset = NULL; if ((r = getdns_dict_get_bindata( dict, "tls_auth_name", &tls_auth_name)) == GETDNS_RETURN_GOOD) { /*TODO: VALIDATE THIS STRING!*/ @@ -1951,6 +1961,16 @@ getdns_context_set_upstream_recursive_servers(struct getdns_context *context, tls_auth_name->size); upstream->tls_auth_name[tls_auth_name->size] = '\0'; } + if ((r = getdns_dict_get_list(dict, "tls_pubkey_pinset", + &pubkey_pinset)) == GETDNS_RETURN_GOOD) { + /* TODO: what if the user supplies tls_pubkey_pinset with + * something other than a list? */ + r = _getdns_get_pubkey_pinset_from_list(pubkey_pinset, + &(upstreams->mf), + &(upstream->tls_pubkey_pinset)); + if (r != GETDNS_RETURN_GOOD) + goto invalid_parameter; + } } if ((upstream->tsig_alg = tsig_alg)) { if (tsig_name) { @@ -2439,8 +2459,8 @@ _getdns_context_prepare_for_resolution(struct getdns_context *context, #endif } if (tls_only_is_in_transports_list(context) == 1 && - context->tls_auth == GETDNS_AUTHENTICATION_HOSTNAME) { - context->tls_auth_min = GETDNS_AUTHENTICATION_HOSTNAME; + context->tls_auth == GETDNS_AUTHENTICATION_REQUIRED) { + context->tls_auth_min = GETDNS_AUTHENTICATION_REQUIRED; /* TODO: If no auth data provided for any upstream, fail here */ } else { @@ -3159,11 +3179,19 @@ getdns_context_get_upstream_recursive_servers(getdns_context *context, (uint32_t)upstream_port(upstream)))) break; - if (upstream->transport == GETDNS_TRANSPORT_TLS && - upstream_port(upstream) != getdns_port_array[j] && - (r = getdns_dict_set_int(d, "tls_port", - (uint32_t)upstream_port(upstream)))) - break; + if (upstream->transport == GETDNS_TRANSPORT_TLS) { + if (upstream_port(upstream) == getdns_port_array[j]) + (void) getdns_dict_set_int(d, "tls_port", + (uint32_t) upstream_port(upstream)); + if (upstream->tls_pubkey_pinset) { + getdns_list *pins = NULL; + if (_getdns_get_pubkey_pinset_list(context, + upstream->tls_pubkey_pinset, + &pins) == GETDNS_RETURN_GOOD) + (void) getdns_dict_set_list(d, "tls_pubkey_pinset", pins); + getdns_list_destroy(pins); + } + } } if (!r) r = _getdns_list_append_dict(upstreams, d); diff --git a/src/context.h b/src/context.h index a52f8ed3..5b0ac06d 100644 --- a/src/context.h +++ b/src/context.h @@ -102,6 +102,12 @@ typedef struct getdns_tsig_info { const getdns_tsig_info *_getdns_get_tsig_info(getdns_tsig_algo tsig_alg); +/* for doing public key pinning of TLS-capable upstreams: */ +typedef struct sha256_pin { + char pin[SHA256_DIGEST_LENGTH]; + struct sha256_pin *next; +} sha256_pin_t; + typedef struct getdns_upstream { /* backpointer to containing upstreams structure */ struct getdns_upstreams *upstreams; @@ -126,6 +132,7 @@ typedef struct getdns_upstream { getdns_tcp_state tcp; char tls_auth_name[256]; size_t tls_auth_failed; + sha256_pin_t *tls_pubkey_pinset; /* Pipelining of TCP network requests */ getdns_network_req *write_queue; @@ -142,6 +149,7 @@ typedef struct getdns_upstream { unsigned has_prev_client_cookie : 1; unsigned has_server_cookie : 1; unsigned server_cookie_len : 5; + unsigned tls_fallback_ok : 1; /* TSIG */ uint8_t tsig_dname[256]; diff --git a/src/getdns/getdns_extra.h.in b/src/getdns/getdns_extra.h.in index a33e6f4a..ed56d735 100644 --- a/src/getdns/getdns_extra.h.in +++ b/src/getdns/getdns_extra.h.in @@ -342,7 +342,62 @@ getdns_context_get_update_callback(getdns_context *context, void **userarg, */ const char *getdns_get_errorstr_by_id(uint16_t err); + +/** + * Public Key Pinning functionality: + * + * a public key pinset is a list of dicts. each dict should have a + * "digest" and a "value". + * + * "digest": a string indicating the type of digest. at the moment, we + * only support a "digest" of "sha256". + * + * "value": a binary representation of the digest provided. + * + * given a such a pinset, we should be able to validate a chain + * properly according to section 2.6 of RFC 7469. + */ + +/** + * convert an HPKP-style pin description to an appropriate getdns data + * structure. An example string is: (with the quotes, without any + * leading or trailing whitespace): + * + * pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=" + * + * It is the caller's responsibility to call getdns_dict_destroy() on + * the dict returned when it is no longer needed. + * + * @param context a context to use to create the dict, or NULL to create + * it generically + * @param str the pinning string to parse + * @return a dict created from ctx, or NULL if the string did not match. + */ +getdns_dict* getdns_pubkey_pin_create_from_string( + getdns_context* context, + const char* str); + + +/** + * Test whether a given pinset is reasonable, including: + * + * is it well-formed? + * are there at least two pins? + * are the digests used sane? + * + * @param pinset the set of public key pins to check for sanity. This + * should be a list of dicts. + * @return errorlist if not NULL, a list of human-readable strings is + * appended to errorlist. + * @return GETDNS_RETURN_GOOD if the pinset passes the sanity check. + */ +getdns_return_t getdns_pubkey_pinset_sanity_check( + const getdns_list* pinset, + getdns_list* errorlist); + + + /* WARNING! Function getdns_strerror is not in the API specification and * is likely to be removed from future versions of our implementation, to be * replaced by getdns_get_errorstr_by_id or something similar. @@ -363,15 +418,18 @@ uint32_t getdns_get_api_version_number(void); /* Authentication options used when doing TLS */ typedef enum getdns_tls_authentication_t { GETDNS_AUTHENTICATION_NONE = 1300, - GETDNS_AUTHENTICATION_HOSTNAME = 1301, + GETDNS_AUTHENTICATION_REQUIRED = 1301 } getdns_tls_authentication_t; +/* an alias for REQUIRED */ +#define GETDNS_AUTHENTICATION_HOSTNAME GETDNS_AUTHENTICATION_REQUIRED + /** * \defgroup Base authentication texts * @{ */ #define GETDNS_AUTHENTICATION_NONE_TEXT "See getdns_context_set_tls_authentication()" -#define GETDNS_AUTHENTICATION_HOSTNAME_TEXT "See getdns_context_set_tls_authentication()" +#define GETDNS_AUTHENTICATION_REQUIRED_TEXT "See getdns_context_set_tls_authentication()" /** @} */ @@ -381,6 +439,8 @@ typedef enum getdns_tls_authentication_t { #define GETDNS_CONTEXT_CODE_EDNS_CLIENT_SUBNET_PRIVATE_TEXT "Change related to getdns_context_set_edns_client_subnet_private" #define GETDNS_CONTEXT_CODE_TLS_QUERY_PADDING_BLOCKSIZE 620 #define GETDNS_CONTEXT_CODE_TLS_QUERY_PADDING_BLOCKSIZE_TEXT "Change related to getdns_context_set_tls_query_padding_blocksize" +#define GETDNS_CONTEXT_CODE_PUBKEY_PINSET 621 +#define GETDNS_CONTEXT_CODE_PUBKEY_PINSET_TEXT "Change related to getdns_context_set_pubkey_pinset" getdns_return_t getdns_context_set_tls_authentication( diff --git a/src/libgetdns.symbols b/src/libgetdns.symbols index 2503c48c..8b35ff37 100644 --- a/src/libgetdns.symbols +++ b/src/libgetdns.symbols @@ -114,6 +114,8 @@ getdns_pretty_snprint_dict getdns_pretty_snprint_list getdns_print_json_dict getdns_print_json_list +getdns_pubkey_pin_create_from_string +getdns_pubkey_pinset_sanity_check getdns_root_trust_anchor getdns_rr_dict2str getdns_rr_dict2str_buf diff --git a/src/pubkey-pinning.c b/src/pubkey-pinning.c new file mode 100644 index 00000000..2f685ad3 --- /dev/null +++ b/src/pubkey-pinning.c @@ -0,0 +1,444 @@ +/** + * + * /brief functions for Public Key Pinning + * + */ + +/* + * Copyright (c) 2015, Daniel Kahn Gillmor + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the names of the copyright holders nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL Verisign, Inc. BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * getdns Public Key Pinning + * + * a public key pinset is a list of dicts. each dict should have a + * "digest" and a "value". + * + * "digest": a string indicating the type of digest. at the moment, we + * only support a "digest" of "sha256". + * + * "value": a binary representation of the digest provided. + * + * given a such a pinset, we should be able to validate a chain + * properly according to section 2.6 of RFC 7469. + */ +#include "config.h" +#include "debug.h" +#include +#include +#include +#include +#include +#include +#include "context.h" + +/* we only support sha256 at the moment. adding support for another + digest is more complex than just adding another entry here. in + particular, you'll probably need a match for a particular cert + against all supported algorithms. better to wait on doing that + until it is a better-understood problem (i.e. wait until hpkp is + updated and follow the guidance in rfc7469bis) +*/ + +static const getdns_bindata sha256 = { + .size = sizeof("sha256") - 1, + .data = (uint8_t*)"sha256" +}; + + +#define PIN_PREFIX "pin-sha256=\"" +#define PIN_PREFIX_LENGTH (sizeof(PIN_PREFIX) - 1) +/* b64 turns every 3 octets (or fraction thereof) into 4 octets */ +#define B64_ENCODED_SHA256_LENGTH (((SHA256_DIGEST_LENGTH + 2)/3) * 4) + +/* convert an HPKP-style pin description to an appropriate getdns data + structure. An example string is: (with the quotes, without any + leading or trailing whitespace): + + pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=" + + getdns_build_pin_from_string returns a dict created from ctx, or + NULL if the string did not match. If ctx is NULL, the dict is + created via getdns_dict_create(). + + It is the caller's responsibility to call getdns_dict_destroy when + it is no longer needed. + */ +getdns_dict* getdns_pubkey_pin_create_from_string( + getdns_context* context, + const char* str) +{ + BIO *bio = NULL; + int i; + uint8_t buf[SHA256_DIGEST_LENGTH]; + char inbuf[B64_ENCODED_SHA256_LENGTH + 1]; + getdns_bindata value = { .size = SHA256_DIGEST_LENGTH, .data = buf }; + getdns_dict* out = NULL; + + /* we only do sha256 right now, make sure this is well-formed */ + if (strncmp(PIN_PREFIX, str, PIN_PREFIX_LENGTH)) + return NULL; + for (i = PIN_PREFIX_LENGTH; i < PIN_PREFIX_LENGTH + B64_ENCODED_SHA256_LENGTH - 1; i++) + if (!((str[i] >= 'a' && str[i] <= 'z') || + (str[i] >= 'A' && str[i] <= 'Z') || + (str[i] >= '0' && str[i] <= '9') || + (str[i] == '+') || (str[i] == '/'))) + return NULL; + if (str[i++] != '=') + return NULL; + if (str[i++] != '"') + return NULL; + if (str[i++] != '\0') + return NULL; + + /* openssl needs a trailing newline to base64 decode */ + memcpy(inbuf, str + PIN_PREFIX_LENGTH, B64_ENCODED_SHA256_LENGTH); + inbuf[B64_ENCODED_SHA256_LENGTH] = '\n'; + + bio = BIO_push(BIO_new(BIO_f_base64()), + BIO_new_mem_buf(inbuf, sizeof(inbuf))); + if (BIO_read(bio, buf, sizeof(buf)) != sizeof(buf)) + goto fail; + + if (context) + out = getdns_dict_create_with_context(context); + else + out = getdns_dict_create(); + if (out == NULL) + goto fail; + if (getdns_dict_set_bindata(out, "digest", &sha256)) + goto fail; + if (getdns_dict_set_bindata(out, "value", &value)) + goto fail; + return out; + + fail: + BIO_free_all(bio); + getdns_dict_destroy(out); + return NULL; +} + + +/* Test whether a given pinset is reasonable, including: + + * is it well-formed? + * are there at least two pins? + * are the digests used sane? + + if errorlist is NULL, the sanity check just returns success or + failure. + + if errorlist is not NULL, we append human-readable strings to + report the errors. +*/ + +#define PKP_SC_ERR(e) { \ + err.size = sizeof(e); \ + err.data = (uint8_t*)e; \ + if (errorlist) \ + getdns_list_set_bindata(errorlist, \ + preverrs + errorcount, &err); \ + errorcount++; \ + } +#define PKP_SC_HARDERR(e, val) { \ + PKP_SC_ERR(e); return val; \ + } +getdns_return_t getdns_pubkey_pinset_sanity_check( + const getdns_list* pinset, + getdns_list* errorlist) +{ + size_t errorcount = 0, preverrs = 0, pins = 0, i; + getdns_bindata err; + getdns_dict * pin; + getdns_bindata * data; + + if (errorlist) + if (getdns_list_get_length(errorlist, &preverrs)) + return GETDNS_RETURN_INVALID_PARAMETER; + + if (getdns_list_get_length(pinset, &pins)) + PKP_SC_HARDERR("Can't get length of pinset", + GETDNS_RETURN_INVALID_PARAMETER); + if (pins < 2) + PKP_SC_ERR("This pinset has fewer than 2 pins"); + for (i = 0; i < pins; i++) + { + /* is it a dict? */ + if (getdns_list_get_dict(pinset, i, &pin)) { + PKP_SC_ERR("Could not retrieve a pin"); + } else { + /* does the pin have the right digest type? */ + if (getdns_dict_get_bindata(pin, "digest", &data)) { + PKP_SC_ERR("Pin has no 'digest' entry"); + } else { + if (data->size != sha256.size || + memcmp(data->data, sha256.data, sha256.size)) + PKP_SC_ERR("Pin has 'digest' other than sha256"); + } + /* if it does, is the value the right length? */ + if (getdns_dict_get_bindata(pin, "value", &data)) { + PKP_SC_ERR("Pin has no 'value' entry"); + } else { + if (data->size != SHA256_DIGEST_LENGTH) + PKP_SC_ERR("Pin has the wrong size 'value' (should be 32 octets for sha256)"); + } + + /* should we choke if it has some other key? for + * extensibility, we will not treat this as an + * error.*/ + } + } + + if (errorcount > 0) + return GETDNS_RETURN_GENERIC_ERROR; + return GETDNS_RETURN_GOOD; +} + +getdns_return_t +_getdns_get_pubkey_pinset_from_list(const getdns_list *pinset_list, + struct mem_funcs *mf, + sha256_pin_t **pinset_out) +{ + getdns_return_t r; + size_t pins, i; + sha256_pin_t *out = NULL, *onext = NULL; + getdns_dict * pin; + getdns_bindata * data = NULL; + + if (r = getdns_list_get_length(pinset_list, &pins), r) + return r; + for (i = 0; i < pins; i++) + { + if (r = getdns_list_get_dict(pinset_list, i, &pin), r) + goto fail; + /* does the pin have the right digest type? */ + if (r = getdns_dict_get_bindata(pin, "digest", &data), r) + goto fail; + if (data->size != sha256.size || + memcmp(data->data, sha256.data, sha256.size)) { + r = GETDNS_RETURN_INVALID_PARAMETER; + goto fail; + } + /* if it does, is the value the right length? */ + if (r = getdns_dict_get_bindata(pin, "value", &data), r) + goto fail; + if (data->size != SHA256_DIGEST_LENGTH) { + r = GETDNS_RETURN_INVALID_PARAMETER; + goto fail; + } + /* make a new pin */ + onext = GETDNS_MALLOC(*mf, sha256_pin_t); + if (onext == NULL) { + r = GETDNS_RETURN_MEMORY_ERROR; + goto fail; + } + onext->next = out; + memcpy(onext->pin, data->data, SHA256_DIGEST_LENGTH); + out = onext; + } + + *pinset_out = out; + return GETDNS_RETURN_GOOD; + fail: + while (out) { + onext = out->next; + GETDNS_FREE(*mf, out); + out = onext; + } + return r; +} + +getdns_return_t +_getdns_get_pubkey_pinset_list(getdns_context *ctx, + const sha256_pin_t *pinset_in, + getdns_list **pinset_list) +{ + getdns_list *out = getdns_list_create_with_context(ctx); + getdns_return_t r; + uint8_t buf[SHA256_DIGEST_LENGTH]; + getdns_bindata value = { .size = SHA256_DIGEST_LENGTH, .data = buf }; + getdns_dict *pin = NULL; + size_t idx = 0; + + if (out == NULL) + return GETDNS_RETURN_MEMORY_ERROR; + while (pinset_in) { + pin = getdns_dict_create_with_context(ctx); + if (pin == NULL) { + r = GETDNS_RETURN_MEMORY_ERROR; + goto fail; + } + if (r = getdns_dict_set_bindata(pin, "digest", &sha256), r) + goto fail; + memcpy(buf, pinset_in->pin, sizeof(buf)); + if (r = getdns_dict_set_bindata(pin, "value", &value), r) + goto fail; + if (r = getdns_list_set_dict(out, idx++, pin), r) + goto fail; + getdns_dict_destroy(pin); + pin = NULL; + pinset_in = pinset_in->next; + } + + *pinset_list = out; + return GETDNS_RETURN_GOOD; + fail: + getdns_dict_destroy(pin); + getdns_list_destroy(out); + return r; +} + +/* this should only happen once ever in the life of the library. it's + used to associate a getdns_context_t with an SSL_CTX, to be able to + do custom verification. + + see doc/HOWTO/proxy_certificates.txt as an example +*/ +static int +_get_ssl_getdns_upstream_idx() +{ + static volatile int idx = -1; + if (idx < 0) { + CRYPTO_w_lock(CRYPTO_LOCK_X509_STORE); + if (idx < 0) + idx = SSL_get_ex_new_index(0, "associated getdns upstream", + NULL,NULL,NULL); + CRYPTO_w_unlock(CRYPTO_LOCK_X509_STORE); + } + return idx; +} + +getdns_upstream* +_getdns_upstream_from_x509_store(X509_STORE_CTX *store) +{ + int uidx = _get_ssl_getdns_upstream_idx(); + int sslidx = SSL_get_ex_data_X509_STORE_CTX_idx(); + const SSL *ssl; + + /* all *_get_ex_data() should return NULL on failure anyway */ + ssl = X509_STORE_CTX_get_ex_data(store, sslidx); + if (ssl) + return (getdns_upstream*) SSL_get_ex_data(ssl, uidx); + else + return NULL; + /* TODO: if we want more details about errors somehow, we + * might call ERR_get_error (see CRYPTO_set_ex_data(3ssl))*/ +} + +getdns_return_t +_getdns_associate_upstream_with_SSL(SSL *ssl, + getdns_upstream *upstream) +{ + int uidx = _get_ssl_getdns_upstream_idx(); + if (SSL_set_ex_data(ssl, uidx, upstream)) + return GETDNS_RETURN_GOOD; + else + return GETDNS_RETURN_GENERIC_ERROR; + /* TODO: if we want more details about errors somehow, we + * might call ERR_get_error (see CRYPTO_set_ex_data(3ssl))*/ +} + +getdns_return_t +_getdns_verify_pinset_match(const sha256_pin_t *pinset, + X509_STORE_CTX *store) +{ + getdns_return_t ret = GETDNS_RETURN_GENERIC_ERROR; + X509 *x; + int i, len; + unsigned char raw[4096]; + unsigned char *next = raw; + unsigned char buf[sizeof(pinset->pin)]; + const sha256_pin_t *p; + + if (pinset == NULL || store == NULL) + return GETDNS_RETURN_GENERIC_ERROR; + + /* start at the base of the chain (the end-entity cert) and + * make sure that some valid element of the chain does match + * the pinset. */ + + /* Testing with OpenSSL 1.0.1e-1 on debian indicates that + * store->untrusted holds the chain offered by the server in + * the order that the server offers it. If the server offers + * bogus certificates (that is, matching and valid certs that + * belong to private keys that the server does not control), + * the the verification will succeed (including this pinset + * check), but the handshake will fail outside of this + * verification. */ + + /* TODO: how do we handle raw public keys? */ + + for (i = 0; i < sk_X509_num(store->untrusted); i++) { + if (i > 0) { + /* TODO: how do we ensure that the certificates in + * each stage appropriately sign the previous one? + * for now, to be safe, we only examine the end-entity + * cert: */ + return GETDNS_RETURN_GENERIC_ERROR; + } + + x = sk_X509_value(store->untrusted, i); + if (x->cert_info == NULL) + continue; +#if defined(STUB_DEBUG) && STUB_DEBUG + DEBUG_STUB("--- %s: name of cert %d:\n", __FUNCTION__, i); + if (x->cert_info->subject != NULL) + X509_NAME_print_ex_fp(stderr, x->cert_info->subject, 4, XN_FLAG_ONELINE); + fprintf(stderr, "\n"); +#endif + if (x->cert_info->key == NULL) + continue; + + /* digest the cert with sha256 */ + len = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(x), NULL); + if (len > sizeof(raw)) { + DEBUG_STUB("--- %s: pubkey %d is larger than %ld octets\n", + __FUNCTION__, i, sizeof(raw)); + continue; + } + i2d_X509_PUBKEY(X509_get_X509_PUBKEY(x), &next); + if (next - raw != len) { + DEBUG_STUB("--- %s: pubkey %d claimed it needed %d octets, really needed %ld\n", + __FUNCTION__, i, len, next - raw); + continue; + } + SHA256(raw, len, buf); + + /* compare it */ + for (p = pinset; p; p = p->next) + if (0 == memcmp(buf, p->pin, sizeof(p->pin))) { + DEBUG_STUB("--- %s: pubkey %d matched pin %p (%ld)!\n", + __FUNCTION__, i, p, sizeof(p->pin)); + return GETDNS_RETURN_GOOD; + } else + DEBUG_STUB("--- %s: pubkey %d did not match pin %p!\n", + __FUNCTION__, i, p); + } + + return ret; +} + +/* pubkey-pinning.c */ diff --git a/src/pubkey-pinning.h b/src/pubkey-pinning.h new file mode 100644 index 00000000..894ccf00 --- /dev/null +++ b/src/pubkey-pinning.h @@ -0,0 +1,68 @@ +/** + * + * /brief internal functions for dealing with pubkey pinsets + * + */ + +/* + * Copyright (c) 2015 ACLU + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the names of the copyright holders nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL Verisign, Inc. BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef PUBKEY_PINNING_H_ +#define PUBKEY_PINNING_H_ + + +/* create and populate a pinset linked list from a getdns_list pinset */ +getdns_return_t +_getdns_get_pubkey_pinset_from_list(const getdns_list *pinset_list, + struct mem_funcs *mf, + sha256_pin_t **pinset_out); + + +/* create a getdns_list version of the pinset */ +getdns_return_t +_getdns_get_pubkey_pinset_list(getdns_context *ctx, + const sha256_pin_t *pinset_in, + getdns_list **pinset_list); + + +/* internal functions for associating X.509 verification processes in + * OpenSSL with getdns_upstream objects. */ + +getdns_upstream* +_getdns_upstream_from_x509_store(X509_STORE_CTX *store); + + +getdns_return_t +_getdns_associate_upstream_with_SSL(SSL *ssl, + getdns_upstream *upstream); + +getdns_return_t +_getdns_verify_pinset_match(const sha256_pin_t *pinset, + X509_STORE_CTX *store); + +#endif +/* pubkey-pinning.h */ diff --git a/src/stub.c b/src/stub.c index 4576851e..320300a6 100644 --- a/src/stub.c +++ b/src/stub.c @@ -47,6 +47,7 @@ #include "context.h" #include "util-internal.h" #include "general.h" +#include "pubkey-pinning.h" #define STUB_OUT_OF_OPTIONS -5 /* upstream options exceeded MAXIMUM_UPSTREAM_OPTION_SPACE */ #define STUB_TLS_SETUP_ERROR -4 @@ -834,47 +835,40 @@ tls_failed(getdns_upstream *upstream) static int tls_auth_status_ok(getdns_upstream *upstream, getdns_network_req *netreq) { - return (netreq->tls_auth_min == GETDNS_AUTHENTICATION_HOSTNAME && + return (netreq->tls_auth_min == GETDNS_AUTHENTICATION_REQUIRED && upstream->tls_auth_failed) ? 0 : 1; } int tls_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) { -#if defined(STUB_DEBUG) && STUB_DEBUG int err; - const char * err_str; - - err = X509_STORE_CTX_get_error(ctx); - err_str = X509_verify_cert_error_string(err); - DEBUG_STUB("--- %s, VERIFY RESULT: %s\n", __FUNCTION__, err_str); -#endif - /*Always proceed without changing result*/ - return preverify_ok; -} - -int -tls_verify_callback_with_fallback(int preverify_ok, X509_STORE_CTX *ctx) -{ -#ifdef X509_V_ERR_HOSTNAME_MISMATCH - int err; -# if defined(STUB_DEBUG) && STUB_DEBUG - const char * err_str; -# endif + getdns_upstream *upstream; + getdns_return_t pinset_ret = GETDNS_RETURN_GOOD; err = X509_STORE_CTX_get_error(ctx); -# if defined(STUB_DEBUG) && STUB_DEBUG - err_str = X509_verify_cert_error_string(err); - DEBUG_STUB("--- %s, VERIFY RESULT: (%d) \"%s\"\n", __FUNCTION__, err, err_str); -# endif - /*Proceed if error is hostname mismatch*/ - if (err == X509_V_ERR_HOSTNAME_MISMATCH) { - DEBUG_STUB("--- %s, PROCEEDING WITHOUT HOSTNAME VALIDATION!!\n", __FUNCTION__); - return 1; - } - else + upstream = _getdns_upstream_from_x509_store(ctx); + DEBUG_STUB("--- %s, VERIFY RESULT: (%d) \"%s\"\n", __FUNCTION__, + err, X509_verify_cert_error_string(err)); + +#ifdef X509_V_ERR_HOSTNAME_MISMATCH + /*Report if error is hostname mismatch*/ + if (upstream && upstream->tls_fallback_ok && err == X509_V_ERR_HOSTNAME_MISMATCH) + DEBUG_STUB("--- %s, PROCEEDING EVEN THOUGH HOSTNAME VALIDATION FAILED!!\n", __FUNCTION__); #endif - return preverify_ok; + if (upstream && upstream->tls_pubkey_pinset) + pinset_ret = _getdns_verify_pinset_match(upstream->tls_pubkey_pinset, ctx); + + if (pinset_ret != GETDNS_RETURN_GOOD) { + DEBUG_STUB("--- %s, PINSET VALIDATION FAILURE!!\n", __FUNCTION__); + preverify_ok = 0; + upstream->tls_auth_failed = 1; + if (upstream->tls_fallback_ok) + DEBUG_STUB("--- %s, PROCEEDING EVEN THOUGH PINSET VALIDATION FAILED!!\n", __FUNCTION__); + } + /* If fallback is allowed, proceed regardless of what the auth error is + (might not be hostname or pinset related) */ + return (upstream && upstream->tls_fallback_ok) ? 1 : preverify_ok; } static SSL* @@ -892,11 +886,17 @@ tls_create_object(getdns_dns_req *dnsreq, int fd, getdns_upstream *upstream) SSL_free(ssl); return NULL; } + /* make sure we'll be able to find the context again when we need it */ + if (_getdns_associate_upstream_with_SSL(ssl, upstream) != GETDNS_RETURN_GOOD) { + SSL_free(ssl); + return NULL; + } /* NOTE: this code will fallback on a given upstream, without trying authentication on other upstreams first. This is non-optimal and but avoids multiple TLS handshakes before getting a usable connection. */ + upstream->tls_fallback_ok = 0; /* If we have a hostname, always use it */ if (upstream->tls_auth_name[0] != '\0') { /*Request certificate for the auth_name*/ @@ -909,39 +909,44 @@ tls_create_object(getdns_dns_req *dnsreq, int fd, getdns_upstream *upstream) param = SSL_get0_param(ssl); X509_VERIFY_PARAM_set_hostflags(param, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); X509_VERIFY_PARAM_set1_host(param, upstream->tls_auth_name, 0); - DEBUG_STUB("--- %s, HOSTNAME VERIFICATION REQUESTED \n", __FUNCTION__); #else - if (dnsreq->netreqs[0]->tls_auth_min == GETDNS_AUTHENTICATION_HOSTNAME) { + if (dnsreq->netreqs[0]->tls_auth_min == GETDNS_AUTHENTICATION_REQUIRED) { /* TODO: Trigger post-handshake custom validation*/ - DEBUG_STUB("--- %s, ERROR: Authentication functionality not available\n", __FUNCTION__); + DEBUG_STUB("--- %s, ERROR: TLS Authentication functionality not available\n", __FUNCTION__); upstream->tls_hs_state = GETDNS_HS_FAILED; upstream->tls_auth_failed = 1; return NULL; } #endif /* Allow fallback to opportunistic if settings permit it*/ - if (dnsreq->netreqs[0]->tls_auth_min == GETDNS_AUTHENTICATION_HOSTNAME) - SSL_set_verify(ssl, SSL_VERIFY_PEER, tls_verify_callback); - else { - SSL_set_verify(ssl, SSL_VERIFY_NONE, tls_verify_callback_with_fallback); - SSL_set_cipher_list(ssl, "DEFAULT"); - } + if (dnsreq->netreqs[0]->tls_auth_min != GETDNS_AUTHENTICATION_REQUIRED) + upstream->tls_fallback_ok = 1; } else { - /* Lack of host name is OK unless only authenticated TLS is specified*/ - if (dnsreq->netreqs[0]->tls_auth_min == GETDNS_AUTHENTICATION_HOSTNAME) { - DEBUG_STUB("--- %s, ERROR: No host name provided for authentication\n", __FUNCTION__); - upstream->tls_hs_state = GETDNS_HS_FAILED; - upstream->tls_auth_failed = 1; - return NULL; + /* 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, PROCEEDING WITH ONLY PUBKEY PINNING AUTHENTICATION\n", __FUNCTION__); + } else { + DEBUG_STUB("--- %s, ERROR: No host name or pubkey pinset provided for TLS authentication\n", __FUNCTION__); + upstream->tls_hs_state = GETDNS_HS_FAILED; + upstream->tls_auth_failed = 1; + return NULL; + } } else { - /* no hostname verification, so we will make opportunistic connections */ - DEBUG_STUB("--- %s, PROCEEDING WITHOUT HOSTNAME VALIDATION!!\n", __FUNCTION__); + /* no hostname verification, so we will make opportunistic connections */ + DEBUG_STUB("--- %s, PROCEEDING EVEN THOUGH NO HOSTNAME PROVIDED!!\n", __FUNCTION__); upstream->tls_auth_failed = 1; - SSL_set_verify(ssl, SSL_VERIFY_NONE, tls_verify_callback_with_fallback); - SSL_set_cipher_list(ssl, "DEFAULT"); + upstream->tls_fallback_ok = 1; } } - + if (upstream->tls_fallback_ok) { + SSL_set_cipher_list(ssl, "DEFAULT"); + DEBUG_STUB("--- %s, PROCEEDING WITH OPPOTUNISTIC TLS CONNECTION (FALLBACK ALLOWED)!!\n", __FUNCTION__); + } else + DEBUG_STUB("--- %s, PROCEEDING WITH STRICT TLS CONNECTION!!\n", __FUNCTION__); + SSL_set_verify(ssl, SSL_VERIFY_PEER, tls_verify_callback); + SSL_set_connect_state(ssl); (void) SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); return ssl; @@ -1002,10 +1007,15 @@ tls_do_handshake(getdns_upstream *upstream) } upstream->tls_hs_state = GETDNS_HS_DONE; r = SSL_get_verify_result(upstream->tls_obj); + if (upstream->tls_auth_name[0]) #ifdef X509_V_ERR_HOSTNAME_MISMATCH - if (r == X509_V_ERR_HOSTNAME_MISMATCH) + if (r == X509_V_ERR_HOSTNAME_MISMATCH) +#else + /* if we weren't built against OpenSSL with hostname matching we + * could not have matched the hostname, so this would be an automatic + * tls_auth_fail. */ #endif - upstream->tls_auth_failed = 1; + upstream->tls_auth_failed = 1; /* Reset timeout on success*/ GETDNS_CLEAR_EVENT(upstream->loop, &upstream->event); upstream->event.read_cb = NULL; diff --git a/src/test/getdns_query.c b/src/test/getdns_query.c index 2d00c985..a0e36e8e 100644 --- a/src/test/getdns_query.c +++ b/src/test/getdns_query.c @@ -36,6 +36,8 @@ #define MAX_TIMEOUTS FD_SETSIZE +#define EXAMPLE_PIN "pin-sha256=\"E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=\"" + /* Eventloop based on select */ typedef struct my_eventloop { getdns_eventloop base; @@ -260,6 +262,8 @@ static char *the_root = "."; static char *name; static getdns_context *context; static getdns_dict *extensions; +static getdns_list *pubkey_pinset = NULL; +static size_t pincount = 0; static uint16_t request_type = GETDNS_RRTYPE_NS; static int timeout, edns0_size, padding_blocksize; static int async = 0, interactive = 0; @@ -474,8 +478,10 @@ print_usage(FILE *out, const char *progname) fprintf(out, "\t-j\tOutput json response dict\n"); fprintf(out, "\t-J\tPretty print json response dict\n"); fprintf(out, "\t-k\tPrint root trust anchors\n"); + fprintf(out, "\t-K \tPin a public key for TLS connections (can repeat)\n"); + fprintf(out, "\t\t(should look like '" EXAMPLE_PIN "')\n"); fprintf(out, "\t-n\tSet TLS authentication mode to NONE (default)\n"); - fprintf(out, "\t-m\tSet TLS authentication mode to HOSTNAME\n"); + fprintf(out, "\t-m\tSet TLS authentication mode to REQUIRED\n"); fprintf(out, "\t-p\tPretty print response dict\n"); fprintf(out, "\t-P \tPad TLS queries to a multiple of blocksize\n"); fprintf(out, "\t-r\tSet recursing resolution type\n"); @@ -681,6 +687,7 @@ getdns_return_t parse_args(int argc, char **argv) int t, print_api_info = 0, print_trust_anchors = 0; getdns_list *upstream_list = NULL; getdns_list *tas = NULL, *hints = NULL; + getdns_dict *pubkey_pin = NULL; size_t upstream_count = 0; FILE *fh; @@ -819,6 +826,36 @@ getdns_return_t parse_args(int argc, char **argv) case 'J': json = 1; break; + case 'K': + if (c[1] != 0 || ++i >= argc || !*argv[i]) { + fprintf(stderr, "pin string of the form " + EXAMPLE_PIN + "expected after -K\n"); + return GETDNS_RETURN_GENERIC_ERROR; + } + pubkey_pin = getdns_pubkey_pin_create_from_string(context, + argv[i]); + if (pubkey_pin == NULL) { + fprintf(stderr, "could not convert '%s' into a " + "public key pin.\n" + "Good pins look like: " EXAMPLE_PIN "\n" + "Please see RFC 7469 for details about " + "the format\n", argv[i]); + return GETDNS_RETURN_GENERIC_ERROR; + } + if (pubkey_pinset == NULL) + pubkey_pinset = getdns_list_create_with_context(context); + if (r = getdns_list_set_dict(pubkey_pinset, pincount++, + pubkey_pin), r) { + fprintf(stderr, "Failed to add pin to pinset (error %d: %s)\n", + r, getdns_get_errorstr_by_id(r)); + getdns_dict_destroy(pubkey_pin); + pubkey_pin = NULL; + return GETDNS_RETURN_GENERIC_ERROR; + } + getdns_dict_destroy(pubkey_pin); + pubkey_pin = NULL; + break; case 'k': print_trust_anchors = 1; break; @@ -828,7 +865,7 @@ getdns_return_t parse_args(int argc, char **argv) break; case 'm': getdns_context_set_tls_authentication(context, - GETDNS_AUTHENTICATION_HOSTNAME); + GETDNS_AUTHENTICATION_REQUIRED); break; case 'P': if (c[1] != 0 || ++i >= argc || !*argv[i]) { @@ -979,6 +1016,20 @@ next: ; } if (r) return r; + if (pubkey_pinset && upstream_count) { + getdns_dict *upstream; + /* apply the accumulated pubkey pinset to all upstreams: */ + for (i = 0; i < upstream_count; i++) { + if (r = getdns_list_get_dict(upstream_list, i, &upstream), r) { + fprintf(stderr, "Failed to get upstream %lu when adding pinset\n", i); + return r; + } + if (r = getdns_dict_set_list(upstream, "tls_pubkey_pinset", pubkey_pinset), r) { + fprintf(stderr, "Failed to set pubkey pinset on upstream %lu\n", i); + return r; + } + } + } if (upstream_count && (r = getdns_context_set_upstream_recursive_servers( context, upstream_list))) { diff --git a/src/test/tests_transports.sh b/src/test/tests_transports.sh index 9cf7f79d..68b80568 100755 --- a/src/test/tests_transports.sh +++ b/src/test/tests_transports.sh @@ -3,6 +3,8 @@ DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) SERVER_IP="8.8.8.8" TLS_SERVER_IP="185.49.141.38~getdnsapi.net" +TLS_SERVER_KEY="foxZRnIh9gZpWnl+zEiKa0EJ2rdCGroMWm02gaxSc9S=" +TLS_SERVER_WRONG_KEY="foxZRnIh9gZpWnl+zEiKa0EJ2rdCGroMWm02gaxSc1S=" GOOD_RESULT_SYNC="Status was: At least one response was returned" GOOD_RESULT_ASYNC="successfull" BAD_RESULT_SYNC="1 'Generic error'" @@ -59,26 +61,32 @@ usage () { echo " -s server configured for only TCP and UDP" echo " -t server configured for TLS, TCP and UDP" echo " (This must include the hostname e.g. 185.49.141.38~getdnsapi.net)" + echo " -k SPKI pin for server configured for TLS, TCP and UDP" } -while getopts ":p:s:t:dh" opt; do +while getopts ":p:s:t:k:dh" opt; do case $opt in d ) set -x ;; p ) DIR=$OPTARG ;; s ) SERVER_IP=$OPTARG ; echo "Setting server to $OPTARG" ;; t ) TLS_SERVER_IP=$OPTARG ; echo "Setting TLS server to $OPTARG" ;; + k ) TLS_SERVER_KEY=$OPTARG ; echo "Setting TLS server key to $OPTARG" ;; h ) usage ; exit ;; esac done TLS_SERVER_IP_NO_NAME=`echo ${TLS_SERVER_IP%~*}` echo $TLS_SERVER_IP_NO_NAME +TLS_SERVER_IP_WRONG_NAME=`echo ${TLS_SERVER_IP::${#TLS_SERVER_IP}-1}` GOOD_QUERIES=( "-s -A -q getdnsapi.net -l U @${SERVER_IP} " "-s -A -q getdnsapi.net -l T @${SERVER_IP} " "-s -A -q getdnsapi.net -l L @${TLS_SERVER_IP_NO_NAME}" -"-s -A -q getdnsapi.net -l L -m @${TLS_SERVER_IP}") +"-s -A -q getdnsapi.net -l L -m @${TLS_SERVER_IP}" +"-s -A -q getdnsapi.net -l L -m @${TLS_SERVER_IP_NO_NAME} -K pin-sha256=\"${TLS_SERVER_KEY}\"" +"-s -A -q getdnsapi.net -l L -m @${TLS_SERVER_IP} -K pin-sha256=\"${TLS_SERVER_KEY}\"" +"-s -G -q DNSKEY getdnsapi.net -l U @${SERVER_IP} -b 512 -D") GOOD_FALLBACK_QUERIES=( "-s -A -q getdnsapi.net -l LT @${SERVER_IP}" @@ -89,9 +97,11 @@ GOOD_FALLBACK_QUERIES=( "-s -G -q DNSKEY getdnsapi.net -l UT @${SERVER_IP} -b 512 -D") NOT_AVAILABLE_QUERIES=( -"-s -A -q getdnsapi.net -l L @${SERVER_IP} " -"-s -A -q getdnsapi.net -l L -m @${TLS_SERVER_IP_NO_NAME} " -"-s -G -q DNSKEY getdnsapi.net -l U @${SERVER_IP} -b 512 -D") +"-s -A -q getdnsapi.net -l L @${SERVER_IP}" +"-s -A -q getdnsapi.net -l L -m @${TLS_SERVER_IP_WRONG_NAME}" +"-s -A -q getdnsapi.net -l L -m @${TLS_SERVER_IP_NO_NAME}" +"-s -A -q getdnsapi.net -l L -m @${TLS_SERVER_IP_NO_NAME} ${TLS_SERVER_WRONG_KEY}") + echo "Starting transport test" echo