Provide access to the pinsets during the TLS verification callback

We do this by associating a getdns_upstream object with the SSL object
handled by that upstream.

This allows us to collapse the verification callback code to a single
function.

Note that if we've agreed that fallback is ok, we are now willing to
accept *any* cert verification error, not just HOSTNAME_MISMATCH.
This is fine, because the alternative is falling back to cleartext,
which would be worse.

We also always set SSL_VERIFY_PEER, since we might as well try to do
so; we'll drop the verification error ourselves if we know we're OK
with falling back.
This commit is contained in:
Daniel Kahn Gillmor 2015-12-21 19:27:40 -05:00 committed by Sara Dickinson
parent 614d317fd8
commit d09675539e
4 changed files with 83 additions and 36 deletions

View File

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

@ -308,5 +308,54 @@ _getdns_get_pubkey_pinset_list(getdns_context *ctx,
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))*/
}
/* pubkey-pinning.c */

View File

@ -49,5 +49,17 @@ _getdns_get_pubkey_pinset_list(getdns_context *ctx,
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);
#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
@ -841,40 +842,20 @@ tls_auth_status_ok(getdns_upstream *upstream, getdns_network_req *netreq) {
int
tls_verify_callback(int preverify_ok, X509_STORE_CTX *ctx)
{
#if defined(STUB_DEBUG) && STUB_DEBUG
int err;
const char * err_str;
getdns_upstream *upstream;
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;
}
upstream = _getdns_upstream_from_x509_store(ctx);
DEBUG_STUB("--- %s, VERIFY RESULT: (%d) \"%s\"\n", __FUNCTION__,
err, X509_verify_cert_error_string(err));
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
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) {
if (upstream && upstream->tls_fallback_ok && err == X509_V_ERR_HOSTNAME_MISMATCH)
DEBUG_STUB("--- %s, PROCEEDING WITHOUT HOSTNAME VALIDATION!!\n", __FUNCTION__);
return 1;
}
else
#endif
return preverify_ok;
return (upstream && upstream->tls_fallback_ok) ? 1 : preverify_ok;
}
static SSL*
@ -892,11 +873,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*/
@ -920,12 +907,8 @@ tls_create_object(getdns_dns_req *dnsreq, int fd, getdns_upstream *upstream)
}
#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_HOSTNAME)
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) {
@ -937,10 +920,12 @@ tls_create_object(getdns_dns_req *dnsreq, int fd, getdns_upstream *upstream)
/* no hostname verification, so we will make opportunistic connections */
DEBUG_STUB("--- %s, PROCEEDING WITHOUT HOSTNAME VALIDATION!!\n", __FUNCTION__);
upstream->tls_auth_failed = 1;
SSL_set_verify(ssl, SSL_VERIFY_NONE, tls_verify_callback_with_fallback);
upstream->tls_fallback_ok = 1;
}
}
if (upstream->tls_fallback_ok)
SSL_set_cipher_list(ssl, "DEFAULT");
}
}
SSL_set_verify(ssl, SSL_VERIFY_PEER, tls_verify_callback);
SSL_set_connect_state(ssl);
(void) SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);