mirror of https://github.com/getdnsapi/getdns.git
907 lines
25 KiB
C
907 lines
25 KiB
C
/**
|
|
*
|
|
* \file tls.c
|
|
* @brief getdns TLS functions
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 2018-2019, NLnet Labs
|
|
* 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.
|
|
*/
|
|
|
|
#include <gnutls/x509.h>
|
|
|
|
#include "config.h"
|
|
|
|
#include "debug.h"
|
|
#include "context.h"
|
|
|
|
#include "tls.h"
|
|
|
|
/*
|
|
* Cipher suites recommended in RFC7525.
|
|
*
|
|
* The following strings generate a list with the same ciphers that are
|
|
* generated by the equivalent string in the OpenSSL version of this file.
|
|
*/
|
|
static char const * const _getdns_tls_context_default_cipher_list =
|
|
"+ECDHE-RSA:+ECDHE-ECDSA:+AEAD";
|
|
|
|
static char const * const _getdns_tls_context_default_cipher_suites =
|
|
"+AES-256-GCM:+AES-128-GCM:+CHACHA20-POLY1305";
|
|
|
|
static char const * const _getdns_tls_connection_opportunistic_cipher_list =
|
|
"NORMAL";
|
|
|
|
static char const * const _getdns_tls_priorities[] = {
|
|
NULL, /* No protocol */
|
|
NULL, /* SSL3 - no available keyword. */
|
|
"+VERS-TLS1.0", /* TLS1.0 */
|
|
"+VERS-TLS1.1", /* TLS1.1 */
|
|
"+VERS-TLS1.2", /* TLS1.2 */
|
|
"+VERS-TLS1.3", /* TLS1.3 */
|
|
};
|
|
|
|
static char* getdns_strdup(struct mem_funcs* mfs, const char* s)
|
|
{
|
|
char* res;
|
|
|
|
if (!s)
|
|
return NULL;
|
|
|
|
res = GETDNS_XMALLOC(*mfs, char, strlen(s) + 1);
|
|
if (!res)
|
|
return NULL;
|
|
strcpy(res, s);
|
|
return res;
|
|
}
|
|
|
|
static char* getdns_priappend(struct mem_funcs* mfs, char* s1, const char* s2)
|
|
{
|
|
char* res;
|
|
|
|
if (!s1)
|
|
return getdns_strdup(mfs, s2);
|
|
if (!s2)
|
|
return s1;
|
|
|
|
res = GETDNS_XMALLOC(*mfs, char, strlen(s1) + strlen(s2) + 2);
|
|
if (!res)
|
|
return NULL;
|
|
strcpy(res, s1);
|
|
strcat(res, ":");
|
|
strcat(res, s2);
|
|
GETDNS_FREE(*mfs, s1);
|
|
return res;
|
|
}
|
|
|
|
static int set_connection_ciphers(_getdns_tls_connection* conn)
|
|
{
|
|
char* pri = NULL;
|
|
int res;
|
|
|
|
pri = getdns_priappend(conn->mfs, pri, "NONE:+COMP-ALL:+SIGN-RSA-SHA384");
|
|
|
|
if (conn->cipher_suites)
|
|
pri = getdns_priappend(conn->mfs, pri, conn->cipher_suites);
|
|
else if (conn->ctx->cipher_suites)
|
|
pri = getdns_priappend(conn->mfs, pri, conn->ctx->cipher_suites);
|
|
|
|
if (conn->cipher_list)
|
|
pri = getdns_priappend(conn->mfs, pri, conn->cipher_list);
|
|
else if (conn->ctx->cipher_list)
|
|
pri = getdns_priappend(conn->mfs, pri, conn->ctx->cipher_list);
|
|
|
|
if (conn->curve_list)
|
|
pri = getdns_priappend(conn->mfs, pri, conn->curve_list);
|
|
else if (conn->ctx->curve_list)
|
|
pri = getdns_priappend(conn->mfs, pri, conn->ctx->curve_list);
|
|
else
|
|
pri = getdns_priappend(conn->mfs, pri, "+CURVE-ALL");
|
|
|
|
gnutls_protocol_t min = conn->min_tls;
|
|
gnutls_protocol_t max = conn->max_tls;
|
|
if (!min) min = conn->ctx->min_tls;
|
|
if (!max) max = conn->ctx->max_tls;
|
|
|
|
if (!min && !max) {
|
|
pri = getdns_priappend(conn->mfs, pri, "+VERS-TLS-ALL");
|
|
} else {
|
|
if (!max) max = GNUTLS_TLS_VERSION_MAX;
|
|
|
|
for (gnutls_protocol_t i = min; i <= max; ++i)
|
|
pri = getdns_priappend(conn->mfs, pri, _getdns_tls_priorities[i]);
|
|
}
|
|
|
|
if (pri) {
|
|
res = gnutls_priority_set_direct(conn->tls, pri, NULL);
|
|
if (res != GNUTLS_E_SUCCESS) {
|
|
_getdns_log(conn->log
|
|
, GETDNS_LOG_UPSTREAM_STATS, GETDNS_LOG_ERR
|
|
, "%s: %s %s (%s)\n"
|
|
, STUB_DEBUG_SETUP_TLS
|
|
, "Error configuring TLS connection with "
|
|
, pri
|
|
, gnutls_strerror(res));
|
|
}
|
|
}
|
|
else
|
|
res = gnutls_set_default_priority(conn->tls);
|
|
GETDNS_FREE(*conn->mfs, pri);
|
|
|
|
return res;
|
|
}
|
|
|
|
static getdns_return_t error_may_want_read_write(_getdns_tls_connection* conn, int err)
|
|
{
|
|
switch (err) {
|
|
case GNUTLS_E_INTERRUPTED:
|
|
case GNUTLS_E_AGAIN:
|
|
case GNUTLS_E_WARNING_ALERT_RECEIVED:
|
|
case GNUTLS_E_GOT_APPLICATION_DATA:
|
|
if (gnutls_record_get_direction(conn->tls) == 0)
|
|
return GETDNS_RETURN_TLS_WANT_READ;
|
|
else
|
|
return GETDNS_RETURN_TLS_WANT_WRITE;
|
|
case GNUTLS_E_FATAL_ALERT_RECEIVED:
|
|
DEBUG_STUB("GNUTLS fatal alert: \"%s\"\n",
|
|
gnutls_alert_get_name(gnutls_alert_get(conn->tls)));
|
|
|
|
default:
|
|
return GETDNS_RETURN_GENERIC_ERROR;
|
|
}
|
|
}
|
|
|
|
static getdns_return_t get_gnu_mac_algorithm(int algorithm, gnutls_mac_algorithm_t* gnualg)
|
|
{
|
|
switch (algorithm) {
|
|
case GETDNS_HMAC_MD5 : *gnualg = GNUTLS_MAC_MD5 ; break;
|
|
case GETDNS_HMAC_SHA1 : *gnualg = GNUTLS_MAC_SHA1 ; break;
|
|
case GETDNS_HMAC_SHA224: *gnualg = GNUTLS_MAC_SHA224; break;
|
|
case GETDNS_HMAC_SHA256: *gnualg = GNUTLS_MAC_SHA256; break;
|
|
case GETDNS_HMAC_SHA384: *gnualg = GNUTLS_MAC_SHA384; break;
|
|
case GETDNS_HMAC_SHA512: *gnualg = GNUTLS_MAC_SHA512; break;
|
|
default : return GETDNS_RETURN_GENERIC_ERROR;
|
|
}
|
|
|
|
return GETDNS_RETURN_GOOD;
|
|
}
|
|
|
|
static gnutls_protocol_t _getdns_tls_version2gnutls_version(getdns_tls_version_t v)
|
|
{
|
|
switch (v) {
|
|
case GETDNS_SSL3 : return GNUTLS_SSL3;
|
|
case GETDNS_TLS1 : return GNUTLS_TLS1;
|
|
case GETDNS_TLS1_1: return GNUTLS_TLS1_1;
|
|
case GETDNS_TLS1_2: return GNUTLS_TLS1_2;
|
|
#if GNUTLS_VERSION_NUMBER >= 0x030605
|
|
case GETDNS_TLS1_3: return GNUTLS_TLS1_3;
|
|
#endif
|
|
default : return GNUTLS_TLS_VERSION_MAX;
|
|
}
|
|
}
|
|
|
|
static _getdns_tls_x509* _getdns_tls_x509_new(struct mem_funcs* mfs, gnutls_datum_t cert)
|
|
{
|
|
_getdns_tls_x509* res;
|
|
|
|
res = GETDNS_MALLOC(*mfs, _getdns_tls_x509);
|
|
if (res)
|
|
res->tls = cert;
|
|
|
|
return res;
|
|
}
|
|
|
|
void _getdns_tls_init()
|
|
{
|
|
gnutls_global_init();
|
|
}
|
|
|
|
_getdns_tls_context* _getdns_tls_context_new(struct mem_funcs* mfs, const getdns_log_config* log)
|
|
{
|
|
_getdns_tls_context* res;
|
|
|
|
if (!(res = GETDNS_MALLOC(*mfs, struct _getdns_tls_context)))
|
|
return NULL;
|
|
|
|
res->mfs = mfs;
|
|
res->cipher_list = res->cipher_suites = res->curve_list = NULL;
|
|
res->min_tls = res->max_tls = 0;
|
|
res->ca_trust_file = NULL;
|
|
res->ca_trust_path = NULL;
|
|
res->log = log;
|
|
|
|
return res;
|
|
}
|
|
|
|
getdns_return_t _getdns_tls_context_free(struct mem_funcs* mfs, _getdns_tls_context* ctx)
|
|
{
|
|
if (!ctx)
|
|
return GETDNS_RETURN_INVALID_PARAMETER;
|
|
|
|
GETDNS_FREE(*mfs, ctx->ca_trust_path);
|
|
GETDNS_FREE(*mfs, ctx->ca_trust_file);
|
|
GETDNS_FREE(*mfs, ctx->curve_list);
|
|
GETDNS_FREE(*mfs, ctx->cipher_suites);
|
|
GETDNS_FREE(*mfs, ctx->cipher_list);
|
|
GETDNS_FREE(*mfs, ctx);
|
|
return GETDNS_RETURN_GOOD;
|
|
}
|
|
|
|
void _getdns_tls_context_pinset_init(_getdns_tls_context* ctx)
|
|
{
|
|
(void) ctx;
|
|
}
|
|
|
|
getdns_return_t _getdns_tls_context_set_min_max_tls_version(_getdns_tls_context* ctx, getdns_tls_version_t min, getdns_tls_version_t max)
|
|
{
|
|
if (!ctx)
|
|
return GETDNS_RETURN_INVALID_PARAMETER;
|
|
ctx->min_tls = _getdns_tls_version2gnutls_version(min);
|
|
ctx->max_tls = _getdns_tls_version2gnutls_version(max);
|
|
return GETDNS_RETURN_GOOD;
|
|
}
|
|
|
|
const char* _getdns_tls_context_get_default_cipher_list()
|
|
{
|
|
return _getdns_tls_context_default_cipher_list;
|
|
}
|
|
|
|
getdns_return_t _getdns_tls_context_set_cipher_list(_getdns_tls_context* ctx, const char* list)
|
|
{
|
|
if (!ctx)
|
|
return GETDNS_RETURN_INVALID_PARAMETER;
|
|
|
|
if (!list)
|
|
list = _getdns_tls_context_default_cipher_list;
|
|
|
|
GETDNS_FREE(*ctx->mfs, ctx->cipher_list);
|
|
ctx->cipher_list = getdns_strdup(ctx->mfs, list);
|
|
return GETDNS_RETURN_GOOD;
|
|
}
|
|
|
|
const char* _getdns_tls_context_get_default_cipher_suites()
|
|
{
|
|
return _getdns_tls_context_default_cipher_suites;
|
|
}
|
|
|
|
getdns_return_t _getdns_tls_context_set_cipher_suites(_getdns_tls_context* ctx, const char* list)
|
|
{
|
|
if (!ctx)
|
|
return GETDNS_RETURN_INVALID_PARAMETER;
|
|
|
|
if (!list)
|
|
list = _getdns_tls_context_default_cipher_suites;
|
|
|
|
GETDNS_FREE(*ctx->mfs, ctx->cipher_suites);
|
|
ctx->cipher_suites = getdns_strdup(ctx->mfs, list);
|
|
return GETDNS_RETURN_GOOD;
|
|
}
|
|
|
|
getdns_return_t _getdns_tls_context_set_curves_list(_getdns_tls_context* ctx, const char* list)
|
|
{
|
|
if (!ctx)
|
|
return GETDNS_RETURN_INVALID_PARAMETER;
|
|
|
|
GETDNS_FREE(*ctx->mfs, ctx->curve_list);
|
|
ctx->curve_list = getdns_strdup(ctx->mfs, list);
|
|
return GETDNS_RETURN_GOOD;
|
|
}
|
|
|
|
getdns_return_t _getdns_tls_context_set_ca(_getdns_tls_context* ctx, const char* file, const char* path)
|
|
{
|
|
if (!ctx)
|
|
return GETDNS_RETURN_INVALID_PARAMETER;
|
|
|
|
GETDNS_FREE(*ctx->mfs, ctx->ca_trust_file);
|
|
ctx->ca_trust_file = getdns_strdup(ctx->mfs, file);
|
|
GETDNS_FREE(*ctx->mfs, ctx->ca_trust_path);
|
|
ctx->ca_trust_path = getdns_strdup(ctx->mfs, path);
|
|
return GETDNS_RETURN_GOOD;
|
|
}
|
|
|
|
_getdns_tls_connection* _getdns_tls_connection_new(struct mem_funcs* mfs, _getdns_tls_context* ctx, int fd, const getdns_log_config* log)
|
|
{
|
|
_getdns_tls_connection* res;
|
|
|
|
if (!ctx)
|
|
return NULL;
|
|
|
|
if (!(res = GETDNS_MALLOC(*mfs, struct _getdns_tls_connection)))
|
|
return NULL;
|
|
|
|
res->shutdown = 0;
|
|
res->ctx = ctx;
|
|
res->mfs = mfs;
|
|
res->cred = NULL;
|
|
res->tls = NULL;
|
|
res->cipher_list = res->cipher_suites = res->curve_list = NULL;
|
|
res->min_tls = res->max_tls = 0;
|
|
res->dane_state = NULL;
|
|
res->dane_query = NULL;
|
|
res->tlsa = NULL;
|
|
res->log = log;
|
|
|
|
if (gnutls_certificate_allocate_credentials(&res->cred) != GNUTLS_E_SUCCESS)
|
|
goto failed;
|
|
|
|
if (!ctx->ca_trust_file && !ctx->ca_trust_path)
|
|
gnutls_certificate_set_x509_system_trust(res->cred);
|
|
else {
|
|
if (ctx->ca_trust_file)
|
|
gnutls_certificate_set_x509_trust_file(res->cred, ctx->ca_trust_file, GNUTLS_X509_FMT_PEM);
|
|
if (ctx->ca_trust_path)
|
|
gnutls_certificate_set_x509_trust_dir(res->cred, ctx->ca_trust_path, GNUTLS_X509_FMT_PEM);
|
|
}
|
|
|
|
if (gnutls_init(&res->tls, GNUTLS_CLIENT | GNUTLS_NONBLOCK) != GNUTLS_E_SUCCESS)
|
|
goto failed;
|
|
if (set_connection_ciphers(res) != GNUTLS_E_SUCCESS) {
|
|
|
|
goto failed;
|
|
}
|
|
if (gnutls_credentials_set(res->tls, GNUTLS_CRD_CERTIFICATE, res->cred) != GNUTLS_E_SUCCESS)
|
|
goto failed;
|
|
if (dane_state_init(&res->dane_state, DANE_F_IGNORE_DNSSEC) != DANE_E_SUCCESS)
|
|
goto failed;
|
|
|
|
gnutls_transport_set_int(res->tls, fd);
|
|
return res;
|
|
|
|
failed:
|
|
_getdns_tls_connection_free(mfs, res);
|
|
return NULL;
|
|
}
|
|
|
|
getdns_return_t _getdns_tls_connection_free(struct mem_funcs* mfs, _getdns_tls_connection* conn)
|
|
{
|
|
if (!conn || !conn->tls)
|
|
return GETDNS_RETURN_INVALID_PARAMETER;
|
|
|
|
if (conn->dane_query)
|
|
dane_query_deinit(conn->dane_query);
|
|
if (conn->dane_state)
|
|
dane_state_deinit(conn->dane_state);
|
|
if (conn->tls)
|
|
gnutls_deinit(conn->tls);
|
|
if (conn->cred)
|
|
gnutls_certificate_free_credentials(conn->cred);
|
|
GETDNS_FREE(*mfs, conn->tlsa);
|
|
GETDNS_FREE(*mfs, conn->curve_list);
|
|
GETDNS_FREE(*mfs, conn->cipher_suites);
|
|
GETDNS_FREE(*mfs, conn->cipher_list);
|
|
GETDNS_FREE(*mfs, conn);
|
|
return GETDNS_RETURN_GOOD;
|
|
}
|
|
|
|
getdns_return_t _getdns_tls_connection_shutdown(_getdns_tls_connection* conn)
|
|
{
|
|
if (!conn || !conn->tls)
|
|
return GETDNS_RETURN_INVALID_PARAMETER;
|
|
|
|
if (conn->shutdown == 0) {
|
|
gnutls_bye(conn->tls, GNUTLS_SHUT_WR);
|
|
conn->shutdown++;
|
|
} else {
|
|
gnutls_bye(conn->tls, GNUTLS_SHUT_RDWR);
|
|
conn->shutdown++;
|
|
}
|
|
|
|
return GETDNS_RETURN_GOOD;
|
|
}
|
|
|
|
getdns_return_t _getdns_tls_connection_set_min_max_tls_version(_getdns_tls_connection* conn, getdns_tls_version_t min, getdns_tls_version_t max)
|
|
{
|
|
if (!conn)
|
|
return GETDNS_RETURN_INVALID_PARAMETER;
|
|
conn->min_tls = _getdns_tls_version2gnutls_version(min);
|
|
conn->max_tls = _getdns_tls_version2gnutls_version(max);
|
|
return GETDNS_RETURN_GOOD;
|
|
}
|
|
|
|
getdns_return_t _getdns_tls_connection_set_cipher_list(_getdns_tls_connection* conn, const char* list)
|
|
{
|
|
if (!conn || !conn->tls)
|
|
return GETDNS_RETURN_INVALID_PARAMETER;
|
|
|
|
if (!list)
|
|
list = _getdns_tls_connection_opportunistic_cipher_list;
|
|
|
|
GETDNS_FREE(*conn->mfs, conn->cipher_list);
|
|
conn->cipher_list = getdns_strdup(conn->mfs, list);
|
|
if (set_connection_ciphers(conn) == GNUTLS_E_SUCCESS)
|
|
return GETDNS_RETURN_GOOD;
|
|
else
|
|
return GETDNS_RETURN_GENERIC_ERROR;
|
|
}
|
|
|
|
getdns_return_t _getdns_tls_connection_set_cipher_suites(_getdns_tls_connection* conn, const char* list)
|
|
{
|
|
if (!conn || !conn->tls)
|
|
return GETDNS_RETURN_INVALID_PARAMETER;
|
|
|
|
GETDNS_FREE(*conn->mfs, conn->cipher_list);
|
|
conn->cipher_suites = getdns_strdup(conn->mfs, list);
|
|
if (set_connection_ciphers(conn) == GNUTLS_E_SUCCESS)
|
|
return GETDNS_RETURN_GOOD;
|
|
else
|
|
return GETDNS_RETURN_GENERIC_ERROR;
|
|
}
|
|
|
|
getdns_return_t _getdns_tls_connection_set_curves_list(_getdns_tls_connection* conn, const char* list)
|
|
{
|
|
if (!conn || !conn->tls)
|
|
return GETDNS_RETURN_INVALID_PARAMETER;
|
|
|
|
GETDNS_FREE(*conn->mfs, conn->curve_list);
|
|
conn->curve_list = getdns_strdup(conn->mfs, list);
|
|
if (set_connection_ciphers(conn) == GNUTLS_E_SUCCESS)
|
|
return GETDNS_RETURN_GOOD;
|
|
else
|
|
return GETDNS_RETURN_GENERIC_ERROR;
|
|
}
|
|
|
|
getdns_return_t _getdns_tls_connection_set_session(_getdns_tls_connection* conn, _getdns_tls_session* s)
|
|
{
|
|
int r;
|
|
|
|
if (!conn || !conn->tls || !s)
|
|
return GETDNS_RETURN_INVALID_PARAMETER;
|
|
|
|
r = gnutls_session_set_data(conn->tls, s->tls.data, s->tls.size);
|
|
if (r != GNUTLS_E_SUCCESS)
|
|
return GETDNS_RETURN_GENERIC_ERROR;
|
|
return GETDNS_RETURN_GOOD;
|
|
}
|
|
|
|
_getdns_tls_session* _getdns_tls_connection_get_session(struct mem_funcs* mfs, _getdns_tls_connection* conn)
|
|
{
|
|
_getdns_tls_session* res;
|
|
int r;
|
|
|
|
if (!conn || !conn->tls)
|
|
return NULL;
|
|
|
|
if (!(res = GETDNS_MALLOC(*mfs, struct _getdns_tls_session)))
|
|
return NULL;
|
|
|
|
r = gnutls_session_get_data2(conn->tls, &res->tls);
|
|
if (r != GNUTLS_E_SUCCESS) {
|
|
GETDNS_FREE(*mfs, res);
|
|
return NULL;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
const char* _getdns_tls_connection_get_version(_getdns_tls_connection* conn)
|
|
{
|
|
if (!conn || !conn->tls)
|
|
return NULL;
|
|
|
|
return gnutls_protocol_get_name(gnutls_protocol_get_version(conn->tls));
|
|
}
|
|
|
|
getdns_return_t _getdns_tls_connection_do_handshake(_getdns_tls_connection* conn)
|
|
{
|
|
int r;
|
|
|
|
if (!conn || !conn->tls)
|
|
return GETDNS_RETURN_INVALID_PARAMETER;
|
|
|
|
r = gnutls_handshake(conn->tls);
|
|
if (r == GNUTLS_E_SUCCESS) {
|
|
return GETDNS_RETURN_GOOD;
|
|
}
|
|
else
|
|
return error_may_want_read_write(conn, r);
|
|
}
|
|
|
|
_getdns_tls_x509* _getdns_tls_connection_get_peer_certificate(struct mem_funcs* mfs, _getdns_tls_connection* conn)
|
|
{
|
|
const gnutls_datum_t *cert_list;
|
|
unsigned int cert_list_size;
|
|
|
|
if (!conn || !conn->tls)
|
|
return NULL;
|
|
|
|
cert_list = gnutls_certificate_get_peers(conn->tls, &cert_list_size);
|
|
if (cert_list == NULL)
|
|
return NULL;
|
|
|
|
return _getdns_tls_x509_new(mfs, *cert_list);
|
|
}
|
|
|
|
getdns_return_t _getdns_tls_connection_is_session_reused(_getdns_tls_connection* conn)
|
|
{
|
|
if (!conn || !conn->tls)
|
|
return GETDNS_RETURN_INVALID_PARAMETER;
|
|
|
|
if (gnutls_session_is_resumed(conn->tls) != 0)
|
|
return GETDNS_RETURN_GOOD;
|
|
else
|
|
return GETDNS_RETURN_TLS_CONNECTION_FRESH;
|
|
}
|
|
|
|
getdns_return_t _getdns_tls_connection_setup_hostname_auth(_getdns_tls_connection* conn, const char* auth_name)
|
|
{
|
|
int r;
|
|
|
|
if (!conn || !conn->tls || !auth_name)
|
|
return GETDNS_RETURN_INVALID_PARAMETER;
|
|
|
|
r = gnutls_server_name_set(conn->tls, GNUTLS_NAME_DNS, auth_name, strlen(auth_name));
|
|
if (r != GNUTLS_E_SUCCESS)
|
|
return GETDNS_RETURN_GENERIC_ERROR;
|
|
|
|
gnutls_session_set_verify_cert(conn->tls, auth_name, 0);
|
|
return GETDNS_RETURN_GOOD;
|
|
}
|
|
|
|
getdns_return_t _getdns_tls_connection_set_host_pinset(_getdns_tls_connection* conn, const char* auth_name, const sha256_pin_t* pinset)
|
|
{
|
|
int r;
|
|
|
|
if (!conn || !conn->tls || !auth_name)
|
|
return GETDNS_RETURN_INVALID_PARAMETER;
|
|
|
|
size_t npins = 0;
|
|
for (const sha256_pin_t* pin = pinset; pin; pin = pin->next)
|
|
npins++;
|
|
|
|
GETDNS_FREE(*conn->mfs, conn->tlsa);
|
|
conn->tlsa = GETDNS_XMALLOC(*conn->mfs, char, npins * (SHA256_DIGEST_LENGTH + 3) * 2);
|
|
if (!conn->tlsa)
|
|
return GETDNS_RETURN_GENERIC_ERROR;
|
|
|
|
char** dane_data = GETDNS_XMALLOC(*conn->mfs, char*, npins * 2 + 1);
|
|
if (!dane_data)
|
|
return GETDNS_RETURN_GENERIC_ERROR;
|
|
int* dane_data_len = GETDNS_XMALLOC(*conn->mfs, int, npins * 2 + 1);
|
|
if (!dane_data_len) {
|
|
GETDNS_FREE(*conn->mfs, dane_data);
|
|
return GETDNS_RETURN_GENERIC_ERROR;
|
|
}
|
|
|
|
char** dane_p = dane_data;
|
|
int* dane_len_p = dane_data_len;
|
|
char* p = conn->tlsa;
|
|
for (const sha256_pin_t* pin = pinset; pin; pin = pin->next) {
|
|
*dane_p++ = p;
|
|
*dane_len_p++ = SHA256_DIGEST_LENGTH + 3;
|
|
p[0] = DANE_CERT_USAGE_LOCAL_CA;
|
|
p[1] = DANE_CERT_PK;
|
|
p[2] = DANE_MATCH_SHA2_256;
|
|
memcpy(&p[3], pin->pin, SHA256_DIGEST_LENGTH);
|
|
p += SHA256_DIGEST_LENGTH + 3;
|
|
|
|
*dane_p++ = p;
|
|
*dane_len_p++ = SHA256_DIGEST_LENGTH + 3;
|
|
p[0] = DANE_CERT_USAGE_LOCAL_EE;
|
|
p[1] = DANE_CERT_PK;
|
|
p[2] = DANE_MATCH_SHA2_256;
|
|
memcpy(&p[3], pin->pin, SHA256_DIGEST_LENGTH);
|
|
p += SHA256_DIGEST_LENGTH + 3;
|
|
}
|
|
*dane_p = NULL;
|
|
|
|
if (conn->dane_query)
|
|
dane_query_deinit(conn->dane_query);
|
|
r = dane_raw_tlsa(conn->dane_state, &conn->dane_query, dane_data, dane_data_len, 0, 0);
|
|
GETDNS_FREE(*conn->mfs, dane_data_len);
|
|
GETDNS_FREE(*conn->mfs, dane_data);
|
|
|
|
return (r == DANE_E_SUCCESS) ? GETDNS_RETURN_GOOD : GETDNS_RETURN_GENERIC_ERROR;
|
|
}
|
|
|
|
getdns_return_t _getdns_tls_connection_certificate_verify(_getdns_tls_connection* conn, long* errnum, const char** errmsg)
|
|
{
|
|
if (!conn || !conn->tls)
|
|
return GETDNS_RETURN_INVALID_PARAMETER;
|
|
|
|
/* If no pinset, no DANE info to check. */
|
|
if (!conn->dane_query)
|
|
return GETDNS_RETURN_GOOD;
|
|
|
|
/* Most of the internals of dane_verify_session_crt() */
|
|
|
|
const gnutls_datum_t* cert_list;
|
|
unsigned int cert_list_size = 0;
|
|
unsigned int type;
|
|
int ret;
|
|
const gnutls_datum_t* cl;
|
|
gnutls_datum_t* new_cert_list = NULL;
|
|
int clsize;
|
|
unsigned int verify;
|
|
|
|
cert_list = gnutls_certificate_get_peers(conn->tls, &cert_list_size);
|
|
if (cert_list_size == 0) {
|
|
*errnum = 1;
|
|
*errmsg = "No peer certificate";
|
|
return GETDNS_RETURN_GENERIC_ERROR;
|
|
}
|
|
cl = cert_list;
|
|
|
|
type = gnutls_certificate_type_get(conn->tls);
|
|
|
|
/* this list may be incomplete, try to get the self-signed CA if any */
|
|
if (cert_list_size > 0) {
|
|
gnutls_x509_crt_t crt, ca;
|
|
gnutls_certificate_credentials_t sc;
|
|
|
|
ret = gnutls_x509_crt_init(&crt);
|
|
if (ret < 0)
|
|
goto failsafe;
|
|
|
|
ret = gnutls_x509_crt_import(crt, &cert_list[cert_list_size-1], GNUTLS_X509_FMT_DER);
|
|
if (ret < 0) {
|
|
gnutls_x509_crt_deinit(crt);
|
|
goto failsafe;
|
|
}
|
|
|
|
/* if it is already self signed continue normally */
|
|
ret = gnutls_x509_crt_check_issuer(crt, crt);
|
|
if (ret != 0) {
|
|
gnutls_x509_crt_deinit(crt);
|
|
goto failsafe;
|
|
}
|
|
|
|
/* chain does not finish in a self signed cert, try to obtain the issuer */
|
|
ret = gnutls_credentials_get(conn->tls, GNUTLS_CRD_CERTIFICATE, (void**)&sc);
|
|
if (ret < 0) {
|
|
gnutls_x509_crt_deinit(crt);
|
|
goto failsafe;
|
|
}
|
|
|
|
ret = gnutls_certificate_get_issuer(sc, crt, &ca, 0);
|
|
if (ret < 0) {
|
|
gnutls_x509_crt_deinit(crt);
|
|
goto failsafe;
|
|
}
|
|
|
|
/* make the new list */
|
|
new_cert_list = GETDNS_XMALLOC(*conn->mfs, gnutls_datum_t, cert_list_size + 1);
|
|
if (new_cert_list == NULL) {
|
|
gnutls_x509_crt_deinit(crt);
|
|
goto failsafe;
|
|
}
|
|
|
|
memcpy(new_cert_list, cert_list, cert_list_size*sizeof(gnutls_datum_t));
|
|
cl = new_cert_list;
|
|
|
|
ret = gnutls_x509_crt_export2(ca, GNUTLS_X509_FMT_DER, &new_cert_list[cert_list_size]);
|
|
if (ret < 0) {
|
|
GETDNS_FREE(*conn->mfs, new_cert_list);
|
|
gnutls_x509_crt_deinit(crt);
|
|
goto failsafe;
|
|
}
|
|
}
|
|
|
|
failsafe:
|
|
|
|
clsize = cert_list_size;
|
|
if (cl == new_cert_list)
|
|
clsize += 1;
|
|
|
|
ret = dane_verify_crt_raw(conn->dane_state, cl, clsize, type, conn->dane_query, 0, 0, &verify);
|
|
|
|
if (new_cert_list) {
|
|
gnutls_free(new_cert_list[cert_list_size].data);
|
|
GETDNS_FREE(*conn->mfs, new_cert_list);
|
|
}
|
|
|
|
if (ret != DANE_E_SUCCESS)
|
|
return GETDNS_RETURN_GENERIC_ERROR;
|
|
|
|
if (verify != 0) {
|
|
if (verify & DANE_VERIFY_CERT_DIFFERS) {
|
|
*errnum = 3;
|
|
*errmsg = "Pinset validation: Certificate differs";
|
|
} else if (verify & DANE_VERIFY_CA_CONSTRAINTS_VIOLATED) {
|
|
*errnum = 2;
|
|
*errmsg = "Pinset validation: CA constraints violated";
|
|
} else {
|
|
*errnum = 4;
|
|
*errmsg = "Pinset validation: Unknown DANE info";
|
|
}
|
|
return GETDNS_RETURN_GENERIC_ERROR;
|
|
}
|
|
|
|
return GETDNS_RETURN_GOOD;
|
|
}
|
|
|
|
|
|
getdns_return_t _getdns_tls_connection_read(_getdns_tls_connection* conn, uint8_t* buf, size_t to_read, size_t* read)
|
|
{
|
|
ssize_t sread;
|
|
|
|
if (!conn || !conn->tls || !read)
|
|
return GETDNS_RETURN_INVALID_PARAMETER;
|
|
|
|
sread = gnutls_record_recv(conn->tls, buf, to_read);
|
|
if (sread < 0)
|
|
return error_may_want_read_write(conn, sread);
|
|
|
|
*read = sread;
|
|
return GETDNS_RETURN_GOOD;
|
|
}
|
|
|
|
getdns_return_t _getdns_tls_connection_write(_getdns_tls_connection* conn, uint8_t* buf, size_t to_write, size_t* written)
|
|
{
|
|
int swritten;
|
|
|
|
if (!conn || !conn->tls || !written)
|
|
return GETDNS_RETURN_INVALID_PARAMETER;
|
|
|
|
swritten = gnutls_record_send(conn->tls, buf, to_write);
|
|
if (swritten < 0)
|
|
return error_may_want_read_write(conn, swritten);
|
|
|
|
*written = swritten;
|
|
return GETDNS_RETURN_GOOD;
|
|
}
|
|
|
|
getdns_return_t _getdns_tls_session_free(struct mem_funcs* mfs, _getdns_tls_session* s)
|
|
{
|
|
if (!s)
|
|
return GETDNS_RETURN_INVALID_PARAMETER;
|
|
GETDNS_FREE(*mfs, s);
|
|
return GETDNS_RETURN_GOOD;
|
|
}
|
|
|
|
getdns_return_t _getdns_tls_get_api_information(getdns_dict* dict)
|
|
{
|
|
if (! getdns_dict_set_int(
|
|
dict, "gnutls_version_number", GNUTLS_VERSION_NUMBER)
|
|
|
|
&& ! getdns_dict_util_set_string(
|
|
dict, "gnutls_version_string", GNUTLS_VERSION)
|
|
)
|
|
return GETDNS_RETURN_GOOD;
|
|
return GETDNS_RETURN_GENERIC_ERROR;
|
|
}
|
|
|
|
void _getdns_tls_x509_free(struct mem_funcs* mfs, _getdns_tls_x509* cert)
|
|
{
|
|
if (cert)
|
|
GETDNS_FREE(*mfs, cert);
|
|
}
|
|
|
|
int _getdns_tls_x509_to_der(struct mem_funcs* mfs, _getdns_tls_x509* cert, getdns_bindata* bindata)
|
|
{
|
|
gnutls_x509_crt_t crt;
|
|
size_t s;
|
|
|
|
if (!cert || gnutls_x509_crt_init(&crt) != GNUTLS_E_SUCCESS)
|
|
return 0;
|
|
|
|
gnutls_x509_crt_import(crt, &cert->tls, GNUTLS_X509_FMT_DER);
|
|
gnutls_x509_crt_export(crt, GNUTLS_X509_FMT_DER, NULL, &s);
|
|
|
|
if (!bindata) {
|
|
gnutls_x509_crt_deinit(crt);
|
|
return s;
|
|
}
|
|
|
|
bindata->data = GETDNS_XMALLOC(*mfs, uint8_t, s);
|
|
if (!bindata->data) {
|
|
gnutls_x509_crt_deinit(crt);
|
|
return 0;
|
|
}
|
|
|
|
gnutls_x509_crt_export(crt, GNUTLS_X509_FMT_DER, bindata->data, &s);
|
|
bindata->size = s;
|
|
gnutls_x509_crt_deinit(crt);
|
|
return s;
|
|
}
|
|
|
|
unsigned char* _getdns_tls_hmac_hash(struct mem_funcs* mfs, int algorithm, const void* key, size_t key_size, const void* data, size_t data_size, size_t* output_size)
|
|
{
|
|
gnutls_mac_algorithm_t alg;
|
|
unsigned int md_len;
|
|
unsigned char* res;
|
|
|
|
if (get_gnu_mac_algorithm(algorithm, &alg) != GETDNS_RETURN_GOOD)
|
|
return NULL;
|
|
|
|
md_len = gnutls_hmac_get_len(alg);
|
|
res = (unsigned char*) GETDNS_XMALLOC(*mfs, unsigned char, md_len);
|
|
if (!res)
|
|
return NULL;
|
|
|
|
(void) gnutls_hmac_fast(alg, key, key_size, data, data_size, res);
|
|
|
|
if (output_size)
|
|
*output_size = md_len;
|
|
return res;
|
|
}
|
|
|
|
_getdns_tls_hmac* _getdns_tls_hmac_new(struct mem_funcs* mfs, int algorithm, const void* key, size_t key_size)
|
|
{
|
|
gnutls_mac_algorithm_t alg;
|
|
_getdns_tls_hmac* res;
|
|
|
|
if (get_gnu_mac_algorithm(algorithm, &alg) != GETDNS_RETURN_GOOD)
|
|
return NULL;
|
|
|
|
if (!(res = GETDNS_MALLOC(*mfs, struct _getdns_tls_hmac)))
|
|
return NULL;
|
|
|
|
if (gnutls_hmac_init(&res->tls, alg, key, key_size) < 0) {
|
|
GETDNS_FREE(*mfs, res);
|
|
return NULL;
|
|
}
|
|
res->md_len = gnutls_hmac_get_len(alg);
|
|
return res;
|
|
}
|
|
|
|
getdns_return_t _getdns_tls_hmac_add(_getdns_tls_hmac* h, const void* data, size_t data_size)
|
|
{
|
|
if (!h || !h->tls || !data)
|
|
return GETDNS_RETURN_INVALID_PARAMETER;
|
|
|
|
if (gnutls_hmac(h->tls, data, data_size) < 0)
|
|
return GETDNS_RETURN_GENERIC_ERROR;
|
|
else
|
|
return GETDNS_RETURN_GOOD;
|
|
}
|
|
|
|
unsigned char* _getdns_tls_hmac_end(struct mem_funcs* mfs, _getdns_tls_hmac* h, size_t* output_size)
|
|
{
|
|
unsigned char* res;
|
|
|
|
if (!h || !h->tls)
|
|
return NULL;
|
|
|
|
res = (unsigned char*) GETDNS_XMALLOC(*mfs, unsigned char, h->md_len);
|
|
if (!res)
|
|
return NULL;
|
|
|
|
gnutls_hmac_deinit(h->tls, res);
|
|
if (output_size)
|
|
*output_size = h->md_len;
|
|
|
|
GETDNS_FREE(*mfs, h);
|
|
return res;
|
|
}
|
|
|
|
void _getdns_tls_sha1(const void* data, size_t data_size, unsigned char* buf)
|
|
{
|
|
gnutls_hash_fast(GNUTLS_DIG_SHA1, data, data_size, buf);
|
|
}
|
|
|
|
void _getdns_tls_cookie_sha256(uint32_t secret, void* addr, size_t addrlen, unsigned char* buf, size_t* buflen)
|
|
{
|
|
gnutls_hash_hd_t digest;
|
|
|
|
gnutls_hash_init(&digest, GNUTLS_DIG_SHA256);
|
|
gnutls_hash(digest, &secret, sizeof(secret));
|
|
gnutls_hash(digest, addr, addrlen);
|
|
gnutls_hash_deinit(digest, buf);
|
|
*buflen = gnutls_hash_get_len(GNUTLS_DIG_SHA256);
|
|
}
|
|
|
|
/* tls.c */
|