Merge pull request #131 from saradickinson/feature/pubkey-pinning

Feature/pubkey pinning
This commit is contained in:
saradickinson 2015-12-24 10:13:53 +00:00
commit b777552f34
13 changed files with 766 additions and 74 deletions

View File

@ -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

View File

@ -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 <string.h>
;;

View File

@ -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

View File

@ -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)

View File

@ -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);

View File

@ -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];

View File

@ -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(

View File

@ -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

444
src/pubkey-pinning.c Normal file
View File

@ -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 <getdns/getdns.h>
#include <openssl/evp.h>
#include <openssl/bio.h>
#include <openssl/sha.h>
#include <openssl/x509.h>
#include <string.h>
#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 */

68
src/pubkey-pinning.h Normal file
View File

@ -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 */

View File

@ -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;

View File

@ -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 <pin>\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 <blocksize>\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))) {

View File

@ -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