getdns/src/gnutls/tls.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 */