mirror of https://github.com/getdnsapi/getdns.git
Merge pull request #131 from saradickinson/feature/pubkey-pinning
Feature/pubkey pinning
This commit is contained in:
commit
b777552f34
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
;;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 */
|
|
@ -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 */
|
116
src/stub.c
116
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;
|
||||
|
|
|
@ -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))) {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue