2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-22 18:19:42 +00:00
bind/lib/isc/tls.c
Ondřej Surý 42496f3f4a
Use ControlStatementsExceptControlMacros for SpaceBeforeParens
> Put a space before opening parentheses only after control statement
> keywords (for/if/while...) except this option doesn’t apply to ForEach
> and If macros. This is useful in projects where ForEach/If macros are
> treated as function calls instead of control statements.
2025-08-19 07:58:33 +02:00

1626 lines
40 KiB
C

/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
#include <inttypes.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#if HAVE_LIBNGHTTP2
#include <nghttp2/nghttp2.h>
#endif /* HAVE_LIBNGHTTP2 */
#include <arpa/inet.h>
#include <openssl/bn.h>
#include <openssl/conf.h>
#include <openssl/crypto.h>
#include <openssl/dh.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/opensslv.h>
#include <openssl/rand.h>
#include <openssl/rsa.h>
#include <openssl/x509_vfy.h>
#include <openssl/x509v3.h>
#include <isc/atomic.h>
#include <isc/crypto.h>
#include <isc/ht.h>
#include <isc/log.h>
#include <isc/magic.h>
#include <isc/mem.h>
#include <isc/mutex.h>
#include <isc/once.h>
#include <isc/random.h>
#include <isc/refcount.h>
#include <isc/rwlock.h>
#include <isc/sockaddr.h>
#include <isc/thread.h>
#include <isc/tls.h>
#include <isc/util.h>
#include "openssl_shim.h"
#define COMMON_SSL_OPTIONS \
(SSL_OP_NO_COMPRESSION | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION)
void
isc_tlsctx_free(isc_tlsctx_t **ctxp) {
SSL_CTX *ctx = NULL;
REQUIRE(ctxp != NULL && *ctxp != NULL);
ctx = *ctxp;
*ctxp = NULL;
SSL_CTX_free(ctx);
}
void
isc_tlsctx_attach(isc_tlsctx_t *src, isc_tlsctx_t **ptarget) {
REQUIRE(src != NULL);
REQUIRE(ptarget != NULL && *ptarget == NULL);
RUNTIME_CHECK(SSL_CTX_up_ref(src) == 1);
*ptarget = src;
}
/*
* Callback invoked by the SSL library whenever a new TLS pre-master secret
* needs to be logged.
*/
static void
sslkeylogfile_append(const SSL *ssl ISC_ATTR_UNUSED, const char *line) {
isc_log_write(ISC_LOGCATEGORY_SSLKEYLOG, ISC_LOGMODULE_CRYPTO,
ISC_LOG_INFO, "%s", line);
}
/*
* Enable TLS pre-master secret logging if the SSLKEYLOGFILE environment
* variable is set. This needs to be done on a per-context basis as that is
* how SSL_CTX_set_keylog_callback() works.
*/
static void
sslkeylogfile_init(isc_tlsctx_t *ctx) {
if (getenv("SSLKEYLOGFILE") != NULL) {
SSL_CTX_set_keylog_callback(ctx, sslkeylogfile_append);
}
}
isc_result_t
isc_tlsctx_createclient(isc_tlsctx_t **ctxp) {
unsigned long err;
char errbuf[256];
SSL_CTX *ctx = NULL;
const SSL_METHOD *method = NULL;
REQUIRE(ctxp != NULL && *ctxp == NULL);
method = TLS_client_method();
if (method == NULL) {
goto ssl_error;
}
ctx = SSL_CTX_new(method);
if (ctx == NULL) {
goto ssl_error;
}
SSL_CTX_set_options(ctx, COMMON_SSL_OPTIONS);
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
sslkeylogfile_init(ctx);
*ctxp = ctx;
return ISC_R_SUCCESS;
ssl_error:
err = ERR_get_error();
ERR_error_string_n(err, errbuf, sizeof(errbuf));
isc_log_write(ISC_LOGCATEGORY_GENERAL, ISC_LOGMODULE_CRYPTO,
ISC_LOG_ERROR, "Error initializing TLS context: %s",
errbuf);
return ISC_R_TLSERROR;
}
isc_result_t
isc_tlsctx_load_certificate(isc_tlsctx_t *ctx, const char *keyfile,
const char *certfile) {
int rv;
REQUIRE(ctx != NULL);
REQUIRE(keyfile != NULL);
REQUIRE(certfile != NULL);
rv = SSL_CTX_use_certificate_chain_file(ctx, certfile);
if (rv != 1) {
unsigned long err = ERR_peek_last_error();
char errbuf[1024] = { 0 };
ERR_error_string_n(err, errbuf, sizeof(errbuf));
isc_log_write(
ISC_LOGCATEGORY_GENERAL, ISC_LOGMODULE_NETMGR,
ISC_LOG_ERROR,
"SSL_CTX_use_certificate_chain_file: '%s' failed: %s",
certfile, errbuf);
return ISC_R_TLSERROR;
}
rv = SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM);
if (rv != 1) {
unsigned long err = ERR_peek_last_error();
char errbuf[1024] = { 0 };
ERR_error_string_n(err, errbuf, sizeof(errbuf));
isc_log_write(ISC_LOGCATEGORY_GENERAL, ISC_LOGMODULE_NETMGR,
ISC_LOG_ERROR,
"SSL_CTX_use_PrivateKey_file: '%s' failed: %s",
keyfile, errbuf);
return ISC_R_TLSERROR;
}
return ISC_R_SUCCESS;
}
isc_result_t
isc_tlsctx_createserver(const char *keyfile, const char *certfile,
isc_tlsctx_t **ctxp) {
int rv;
unsigned long err;
bool ephemeral = (keyfile == NULL && certfile == NULL);
X509 *cert = NULL;
EVP_PKEY *pkey = NULL;
SSL_CTX *ctx = NULL;
#if OPENSSL_VERSION_NUMBER < 0x30000000L
EC_KEY *eckey = NULL;
#else
EVP_PKEY_CTX *pkey_ctx = NULL;
EVP_PKEY *params_pkey = NULL;
#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L */
char errbuf[256];
const SSL_METHOD *method = NULL;
REQUIRE(ctxp != NULL && *ctxp == NULL);
REQUIRE((keyfile == NULL) == (certfile == NULL));
method = TLS_server_method();
if (method == NULL) {
goto ssl_error;
}
ctx = SSL_CTX_new(method);
if (ctx == NULL) {
goto ssl_error;
}
RUNTIME_CHECK(ctx != NULL);
SSL_CTX_set_options(ctx, COMMON_SSL_OPTIONS);
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
if (ephemeral) {
const int group_nid = NID_X9_62_prime256v1;
#if OPENSSL_VERSION_NUMBER < 0x30000000L
eckey = EC_KEY_new_by_curve_name(group_nid);
if (eckey == NULL) {
goto ssl_error;
}
/* Generate the key. */
rv = EC_KEY_generate_key(eckey);
if (rv != 1) {
goto ssl_error;
}
pkey = EVP_PKEY_new();
if (pkey == NULL) {
goto ssl_error;
}
rv = EVP_PKEY_set1_EC_KEY(pkey, eckey);
if (rv != 1) {
goto ssl_error;
}
/* Use a named curve and uncompressed point conversion form. */
EC_KEY_set_asn1_flag(EVP_PKEY_get0_EC_KEY(pkey),
OPENSSL_EC_NAMED_CURVE);
EC_KEY_set_conv_form(EVP_PKEY_get0_EC_KEY(pkey),
POINT_CONVERSION_UNCOMPRESSED);
/* Cleanup */
EC_KEY_free(eckey);
eckey = NULL;
#else
/* Generate the key's parameters. */
pkey_ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL);
if (pkey_ctx == NULL) {
goto ssl_error;
}
rv = EVP_PKEY_paramgen_init(pkey_ctx);
if (rv != 1) {
goto ssl_error;
}
rv = EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pkey_ctx,
group_nid);
if (rv != 1) {
goto ssl_error;
}
rv = EVP_PKEY_paramgen(pkey_ctx, &params_pkey);
if (rv != 1 || params_pkey == NULL) {
goto ssl_error;
}
EVP_PKEY_CTX_free(pkey_ctx);
/* Generate the key. */
pkey_ctx = EVP_PKEY_CTX_new(params_pkey, NULL);
if (pkey_ctx == NULL) {
goto ssl_error;
}
rv = EVP_PKEY_keygen_init(pkey_ctx);
if (rv != 1) {
goto ssl_error;
}
rv = EVP_PKEY_keygen(pkey_ctx, &pkey);
if (rv != 1 || pkey == NULL) {
goto ssl_error;
}
/* Cleanup */
EVP_PKEY_free(params_pkey);
params_pkey = NULL;
EVP_PKEY_CTX_free(pkey_ctx);
pkey_ctx = NULL;
#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L */
cert = X509_new();
if (cert == NULL) {
goto ssl_error;
}
ASN1_INTEGER_set(X509_get_serialNumber(cert),
(long)isc_random32());
/*
* Set the "not before" property 5 minutes into the past to
* accommodate with some possible clock skew across systems.
*/
X509_gmtime_adj(X509_getm_notBefore(cert), -300);
/*
* We set the vailidy for 10 years.
*/
X509_gmtime_adj(X509_getm_notAfter(cert), 3650 * 24 * 3600);
X509_set_pubkey(cert, pkey);
X509_NAME *name = X509_get_subject_name(cert);
X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC,
(const unsigned char *)"AQ", -1, -1,
0);
X509_NAME_add_entry_by_txt(
name, "O", MBSTRING_ASC,
(const unsigned char *)"BIND9 ephemeral "
"certificate",
-1, -1, 0);
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
(const unsigned char *)"bind9.local",
-1, -1, 0);
X509_set_issuer_name(cert, name);
X509_sign(cert, pkey, isc__crypto_sha256);
rv = SSL_CTX_use_certificate(ctx, cert);
if (rv != 1) {
goto ssl_error;
}
rv = SSL_CTX_use_PrivateKey(ctx, pkey);
if (rv != 1) {
goto ssl_error;
}
X509_free(cert);
EVP_PKEY_free(pkey);
} else {
isc_result_t result;
result = isc_tlsctx_load_certificate(ctx, keyfile, certfile);
if (result != ISC_R_SUCCESS) {
goto ssl_error;
}
}
sslkeylogfile_init(ctx);
*ctxp = ctx;
return ISC_R_SUCCESS;
ssl_error:
err = ERR_get_error();
ERR_error_string_n(err, errbuf, sizeof(errbuf));
isc_log_write(ISC_LOGCATEGORY_GENERAL, ISC_LOGMODULE_CRYPTO,
ISC_LOG_ERROR, "Error initializing TLS context: %s",
errbuf);
if (ctx != NULL) {
SSL_CTX_free(ctx);
}
if (cert != NULL) {
X509_free(cert);
}
if (pkey != NULL) {
EVP_PKEY_free(pkey);
}
#if OPENSSL_VERSION_NUMBER < 0x30000000L
if (eckey != NULL) {
EC_KEY_free(eckey);
}
#else
if (params_pkey != NULL) {
EVP_PKEY_free(params_pkey);
}
if (pkey_ctx != NULL) {
EVP_PKEY_CTX_free(pkey_ctx);
}
#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L */
return ISC_R_TLSERROR;
}
static long
get_tls_version_disable_bit(const isc_tls_protocol_version_t tls_ver) {
long bit = 0;
switch (tls_ver) {
case ISC_TLS_PROTO_VER_1_2:
#ifdef SSL_OP_NO_TLSv1_2
bit = SSL_OP_NO_TLSv1_2;
#else
bit = 0;
#endif
break;
case ISC_TLS_PROTO_VER_1_3:
#ifdef SSL_OP_NO_TLSv1_3
bit = SSL_OP_NO_TLSv1_3;
#else
bit = 0;
#endif
break;
default:
UNREACHABLE();
break;
};
return bit;
}
bool
isc_tls_protocol_supported(const isc_tls_protocol_version_t tls_ver) {
return get_tls_version_disable_bit(tls_ver) != 0;
}
isc_tls_protocol_version_t
isc_tls_protocol_name_to_version(const char *name) {
REQUIRE(name != NULL);
if (strcasecmp(name, "TLSv1.2") == 0) {
return ISC_TLS_PROTO_VER_1_2;
} else if (strcasecmp(name, "TLSv1.3") == 0) {
return ISC_TLS_PROTO_VER_1_3;
}
return ISC_TLS_PROTO_VER_UNDEFINED;
}
void
isc_tlsctx_set_protocols(isc_tlsctx_t *ctx, const uint32_t tls_versions) {
REQUIRE(ctx != NULL);
REQUIRE(tls_versions != 0);
long set_options = 0;
long clear_options = 0;
uint32_t versions = tls_versions;
/*
* The code below might be initially hard to follow because of the
* double negation that OpenSSL enforces.
*
* Taking into account that OpenSSL provides bits to *disable*
* specific protocol versions, like SSL_OP_NO_TLSv1_2,
* SSL_OP_NO_TLSv1_3, etc., the code has the following logic:
*
* If a protocol version is not specified in the bitmask, get the
* bit that disables it and add it to the set of TLS options to
* set ('set_options'). Otherwise, if a protocol version is set,
* add the bit to the set of options to clear ('clear_options').
*/
/* TLS protocol versions are defined as powers of two. */
for (uint32_t tls_ver = ISC_TLS_PROTO_VER_1_2;
tls_ver < ISC_TLS_PROTO_VER_UNDEFINED; tls_ver <<= 1)
{
if ((tls_versions & tls_ver) == 0) {
set_options |= get_tls_version_disable_bit(tls_ver);
} else {
/*
* Only supported versions should ever be passed to the
* function SSL_CTX_clear_options. For example, in order
* to enable TLS v1.2, we have to clear
* SSL_OP_NO_TLSv1_2. Insist that the configuration file
* was verified properly, so we are not trying to enable
* an unsupported TLS version.
*/
INSIST(isc_tls_protocol_supported(tls_ver));
clear_options |= get_tls_version_disable_bit(tls_ver);
}
versions &= ~(tls_ver);
}
/* All versions should be processed at this point, thus the value
* must equal zero. If it is not, then some garbage has been
* passed to the function; this situation is worth
* investigation. */
INSIST(versions == 0);
(void)SSL_CTX_set_options(ctx, set_options);
(void)SSL_CTX_clear_options(ctx, clear_options);
}
bool
isc_tlsctx_load_dhparams(isc_tlsctx_t *ctx, const char *dhparams_file) {
REQUIRE(ctx != NULL);
REQUIRE(dhparams_file != NULL);
REQUIRE(*dhparams_file != '\0');
#if OPENSSL_VERSION_NUMBER < 0x30000000L
/* OpenSSL < 3.0 */
DH *dh = NULL;
FILE *paramfile;
paramfile = fopen(dhparams_file, "r");
if (paramfile) {
int check = 0;
dh = PEM_read_DHparams(paramfile, NULL, NULL, NULL);
fclose(paramfile);
if (dh == NULL) {
return false;
} else if (DH_check(dh, &check) != 1 || check != 0) {
DH_free(dh);
return false;
}
} else {
return false;
}
if (SSL_CTX_set_tmp_dh(ctx, dh) != 1) {
DH_free(dh);
return false;
}
DH_free(dh);
#else
/* OpenSSL >= 3.0: low level DH APIs are deprecated in OpenSSL 3.0 */
EVP_PKEY *dh = NULL;
BIO *bio = NULL;
bio = BIO_new_file(dhparams_file, "r");
if (bio == NULL) {
return false;
}
dh = PEM_read_bio_Parameters(bio, NULL);
if (dh == NULL) {
BIO_free(bio);
return false;
}
if (SSL_CTX_set0_tmp_dh_pkey(ctx, dh) != 1) {
BIO_free(bio);
EVP_PKEY_free(dh);
return false;
}
/* No need to call EVP_PKEY_free(dh) as the "dh" is owned by the
* SSL context at this point. */
BIO_free(bio);
#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L */
return true;
}
bool
isc_tls_cipherlist_valid(const char *cipherlist) {
isc_tlsctx_t *tmp_ctx = NULL;
const SSL_METHOD *method = NULL;
bool result;
REQUIRE(cipherlist != NULL);
if (*cipherlist == '\0') {
return false;
}
method = TLS_server_method();
if (method == NULL) {
return false;
}
tmp_ctx = SSL_CTX_new(method);
if (tmp_ctx == NULL) {
return false;
}
result = SSL_CTX_set_cipher_list(tmp_ctx, cipherlist) == 1;
isc_tlsctx_free(&tmp_ctx);
return result;
}
void
isc_tlsctx_set_cipherlist(isc_tlsctx_t *ctx, const char *cipherlist) {
REQUIRE(ctx != NULL);
REQUIRE(cipherlist != NULL);
REQUIRE(*cipherlist != '\0');
RUNTIME_CHECK(SSL_CTX_set_cipher_list(ctx, cipherlist) == 1);
}
bool
isc_tls_cipher_suites_valid(const char *cipher_suites) {
isc_tlsctx_t *tmp_ctx = NULL;
const SSL_METHOD *method = NULL;
bool result;
REQUIRE(cipher_suites != NULL);
if (*cipher_suites == '\0') {
return false;
}
method = TLS_server_method();
if (method == NULL) {
return false;
}
tmp_ctx = SSL_CTX_new(method);
if (tmp_ctx == NULL) {
return false;
}
result = SSL_CTX_set_ciphersuites(tmp_ctx, cipher_suites) == 1;
isc_tlsctx_free(&tmp_ctx);
return result;
}
void
isc_tlsctx_set_cipher_suites(isc_tlsctx_t *ctx, const char *cipher_suites) {
REQUIRE(ctx != NULL);
REQUIRE(cipher_suites != NULL);
REQUIRE(*cipher_suites != '\0');
RUNTIME_CHECK(SSL_CTX_set_ciphersuites(ctx, cipher_suites) == 1);
}
void
isc_tlsctx_prefer_server_ciphers(isc_tlsctx_t *ctx, const bool prefer) {
REQUIRE(ctx != NULL);
if (prefer) {
(void)SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
} else {
(void)SSL_CTX_clear_options(ctx,
SSL_OP_CIPHER_SERVER_PREFERENCE);
}
}
void
isc_tlsctx_session_tickets(isc_tlsctx_t *ctx, const bool use) {
REQUIRE(ctx != NULL);
if (!use) {
(void)SSL_CTX_set_options(ctx, SSL_OP_NO_TICKET);
} else {
(void)SSL_CTX_clear_options(ctx, SSL_OP_NO_TICKET);
}
}
isc_tls_t *
isc_tls_create(isc_tlsctx_t *ctx) {
isc_tls_t *newctx = NULL;
REQUIRE(ctx != NULL);
newctx = SSL_new(ctx);
if (newctx == NULL) {
char errbuf[256];
unsigned long err = ERR_get_error();
ERR_error_string_n(err, errbuf, sizeof(errbuf));
fprintf(stderr, "%s:SSL_new(%p) -> %s\n", __func__, ctx,
errbuf);
}
return newctx;
}
void
isc_tls_free(isc_tls_t **tlsp) {
isc_tls_t *tls = NULL;
REQUIRE(tlsp != NULL && *tlsp != NULL);
tls = *tlsp;
*tlsp = NULL;
SSL_free(tls);
}
const char *
isc_tls_verify_peer_result_string(isc_tls_t *tls) {
REQUIRE(tls != NULL);
return X509_verify_cert_error_string(SSL_get_verify_result(tls));
}
#if HAVE_LIBNGHTTP2
#ifndef OPENSSL_NO_NEXTPROTONEG
/*
* NPN TLS extension client callback.
*/
static int
select_next_proto_cb(SSL *ssl, unsigned char **out, unsigned char *outlen,
const unsigned char *in, unsigned int inlen, void *arg) {
UNUSED(ssl);
UNUSED(arg);
if (nghttp2_select_next_protocol(out, outlen, in, inlen) <= 0) {
return SSL_TLSEXT_ERR_NOACK;
}
return SSL_TLSEXT_ERR_OK;
}
#endif /* !OPENSSL_NO_NEXTPROTONEG */
void
isc_tlsctx_enable_http2client_alpn(isc_tlsctx_t *ctx) {
REQUIRE(ctx != NULL);
#ifndef OPENSSL_NO_NEXTPROTONEG
SSL_CTX_set_next_proto_select_cb(ctx, select_next_proto_cb, NULL);
#endif /* !OPENSSL_NO_NEXTPROTONEG */
SSL_CTX_set_alpn_protos(ctx, (const unsigned char *)NGHTTP2_PROTO_ALPN,
NGHTTP2_PROTO_ALPN_LEN);
}
#ifndef OPENSSL_NO_NEXTPROTONEG
static int
next_proto_cb(isc_tls_t *ssl, const unsigned char **data, unsigned int *len,
void *arg) {
UNUSED(ssl);
UNUSED(arg);
*data = (const unsigned char *)NGHTTP2_PROTO_ALPN;
*len = (unsigned int)NGHTTP2_PROTO_ALPN_LEN;
return SSL_TLSEXT_ERR_OK;
}
#endif /* !OPENSSL_NO_NEXTPROTONEG */
static int
alpn_select_proto_cb(SSL *ssl, const unsigned char **out, unsigned char *outlen,
const unsigned char *in, unsigned int inlen, void *arg) {
int ret;
UNUSED(ssl);
UNUSED(arg);
ret = nghttp2_select_next_protocol((unsigned char **)(uintptr_t)out,
outlen, in, inlen);
if (ret != 1) {
return SSL_TLSEXT_ERR_NOACK;
}
return SSL_TLSEXT_ERR_OK;
}
void
isc_tlsctx_enable_http2server_alpn(isc_tlsctx_t *tls) {
REQUIRE(tls != NULL);
#ifndef OPENSSL_NO_NEXTPROTONEG
SSL_CTX_set_next_protos_advertised_cb(tls, next_proto_cb, NULL);
#endif // OPENSSL_NO_NEXTPROTONEG
SSL_CTX_set_alpn_select_cb(tls, alpn_select_proto_cb, NULL);
}
#endif /* HAVE_LIBNGHTTP2 */
void
isc_tls_get_selected_alpn(isc_tls_t *tls, const unsigned char **alpn,
unsigned int *alpnlen) {
REQUIRE(tls != NULL);
REQUIRE(alpn != NULL);
REQUIRE(alpnlen != NULL);
#ifndef OPENSSL_NO_NEXTPROTONEG
SSL_get0_next_proto_negotiated(tls, alpn, alpnlen);
#endif
if (*alpn == NULL) {
SSL_get0_alpn_selected(tls, alpn, alpnlen);
}
}
static bool
protoneg_check_protocol(const uint8_t **pout, uint8_t *pout_len,
const uint8_t *in, size_t in_len, const uint8_t *key,
size_t key_len) {
for (size_t i = 0; i + key_len <= in_len; i += (size_t)(in[i] + 1)) {
if (memcmp(&in[i], key, key_len) == 0) {
*pout = (const uint8_t *)(&in[i + 1]);
*pout_len = in[i];
return true;
}
}
return false;
}
/* dot prepended by its length (3 bytes) */
#define DOT_PROTO_ALPN "\x3" ISC_TLS_DOT_PROTO_ALPN_ID
#define DOT_PROTO_ALPN_LEN (sizeof(DOT_PROTO_ALPN) - 1)
static bool
dot_select_next_protocol(const uint8_t **pout, uint8_t *pout_len,
const uint8_t *in, size_t in_len) {
return protoneg_check_protocol(pout, pout_len, in, in_len,
(const uint8_t *)DOT_PROTO_ALPN,
DOT_PROTO_ALPN_LEN);
}
void
isc_tlsctx_enable_dot_client_alpn(isc_tlsctx_t *ctx) {
REQUIRE(ctx != NULL);
SSL_CTX_set_alpn_protos(ctx, (const uint8_t *)DOT_PROTO_ALPN,
DOT_PROTO_ALPN_LEN);
}
static int
dot_alpn_select_proto_cb(SSL *ssl, const unsigned char **out,
unsigned char *outlen, const unsigned char *in,
unsigned int inlen, void *arg) {
bool ret;
UNUSED(ssl);
UNUSED(arg);
ret = dot_select_next_protocol(out, outlen, in, inlen);
if (!ret) {
return SSL_TLSEXT_ERR_NOACK;
}
return SSL_TLSEXT_ERR_OK;
}
void
isc_tlsctx_enable_dot_server_alpn(isc_tlsctx_t *tls) {
REQUIRE(tls != NULL);
SSL_CTX_set_alpn_select_cb(tls, dot_alpn_select_proto_cb, NULL);
}
isc_result_t
isc_tlsctx_enable_peer_verification(isc_tlsctx_t *tlsctx, const bool is_server,
isc_tls_cert_store_t *store,
const char *hostname,
bool hostname_ignore_subject) {
int ret = 0;
REQUIRE(tlsctx != NULL);
REQUIRE(store != NULL);
/* Set the hostname/IP address. */
if (!is_server && hostname != NULL && *hostname != '\0') {
struct in6_addr sa6;
struct in_addr sa;
X509_VERIFY_PARAM *param = SSL_CTX_get0_param(tlsctx);
unsigned int hostflags = X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS;
/* It might be an IP address. */
if (inet_pton(AF_INET6, hostname, &sa6) == 1 ||
inet_pton(AF_INET, hostname, &sa) == 1)
{
ret = X509_VERIFY_PARAM_set1_ip_asc(param, hostname);
} else {
/* It seems that it is a host name. Let's set it. */
ret = X509_VERIFY_PARAM_set1_host(param, hostname, 0);
}
if (ret != 1) {
ERR_clear_error();
return ISC_R_FAILURE;
}
#ifdef X509_CHECK_FLAG_NEVER_CHECK_SUBJECT
/*
* According to the RFC 8310, Section 8.1, Subject field MUST
* NOT be inspected when verifying a hostname when using
* DoT. Only SubjectAltName must be checked instead. That is
* not the case for HTTPS, though.
*
* Unfortunately, some quite old versions of OpenSSL (< 1.1.1)
* might lack the functionality to implement that. It should
* have very little real-world consequences, as most of the
* production-ready certificates issued by real CAs will have
* SubjectAltName set. In such a case, the Subject field is
* ignored.
*/
if (hostname_ignore_subject) {
hostflags |= X509_CHECK_FLAG_NEVER_CHECK_SUBJECT;
}
#else
UNUSED(hostname_ignore_subject);
#endif
X509_VERIFY_PARAM_set_hostflags(param, hostflags);
}
/* "Attach" the cert store to the context */
SSL_CTX_set1_cert_store(tlsctx, store);
/* enable verification */
if (is_server) {
SSL_CTX_set_verify(tlsctx,
SSL_VERIFY_PEER |
SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
NULL);
} else {
SSL_CTX_set_verify(tlsctx, SSL_VERIFY_PEER, NULL);
}
return ISC_R_SUCCESS;
}
isc_result_t
isc_tlsctx_load_client_ca_names(isc_tlsctx_t *ctx, const char *ca_bundle_file) {
STACK_OF(X509_NAME) * cert_names;
REQUIRE(ctx != NULL);
REQUIRE(ca_bundle_file != NULL);
cert_names = SSL_load_client_CA_file(ca_bundle_file);
if (cert_names == NULL) {
ERR_clear_error();
return ISC_R_FAILURE;
}
SSL_CTX_set_client_CA_list(ctx, cert_names);
return ISC_R_SUCCESS;
}
isc_result_t
isc_tls_cert_store_create(const char *ca_bundle_filename,
isc_tls_cert_store_t **pstore) {
int ret = 0;
isc_tls_cert_store_t *store = NULL;
REQUIRE(pstore != NULL && *pstore == NULL);
store = X509_STORE_new();
if (store == NULL) {
goto error;
}
/* Let's treat empty string as the default (system wide) store */
if (ca_bundle_filename != NULL && *ca_bundle_filename == '\0') {
ca_bundle_filename = NULL;
}
if (ca_bundle_filename == NULL) {
ret = X509_STORE_set_default_paths(store);
} else {
ret = X509_STORE_load_locations(store, ca_bundle_filename,
NULL);
}
if (ret == 0) {
goto error;
}
*pstore = store;
return ISC_R_SUCCESS;
error:
ERR_clear_error();
if (store != NULL) {
X509_STORE_free(store);
}
return ISC_R_FAILURE;
}
void
isc_tls_cert_store_free(isc_tls_cert_store_t **pstore) {
isc_tls_cert_store_t *store;
REQUIRE(pstore != NULL && *pstore != NULL);
store = *pstore;
X509_STORE_free(store);
*pstore = NULL;
}
#define TLSCTX_CACHE_MAGIC ISC_MAGIC('T', 'l', 'S', 'c')
#define VALID_TLSCTX_CACHE(t) ISC_MAGIC_VALID(t, TLSCTX_CACHE_MAGIC)
#define TLSCTX_CLIENT_SESSION_CACHE_MAGIC ISC_MAGIC('T', 'l', 'C', 'c')
#define VALID_TLSCTX_CLIENT_SESSION_CACHE(t) \
ISC_MAGIC_VALID(t, TLSCTX_CLIENT_SESSION_CACHE_MAGIC)
typedef struct isc_tlsctx_cache_entry {
/*
* We need a TLS context entry for each transport on both IPv4 and
* IPv6 in order to avoid cluttering a context-specific
* session-resumption cache.
*/
isc_tlsctx_t *ctx[isc_tlsctx_cache_count - 1][2];
isc_tlsctx_client_session_cache_t
*client_sess_cache[isc_tlsctx_cache_count - 1][2];
/*
* One certificate store is enough for all the contexts defined
* above. We need that for peer validation.
*/
isc_tls_cert_store_t *ca_store;
} isc_tlsctx_cache_entry_t;
struct isc_tlsctx_cache {
uint32_t magic;
isc_refcount_t references;
isc_mem_t *mctx;
isc_rwlock_t rwlock;
isc_ht_t *data;
};
void
isc_tlsctx_cache_create(isc_mem_t *mctx, isc_tlsctx_cache_t **cachep) {
isc_tlsctx_cache_t *nc;
REQUIRE(cachep != NULL && *cachep == NULL);
nc = isc_mem_get(mctx, sizeof(*nc));
*nc = (isc_tlsctx_cache_t){ .magic = TLSCTX_CACHE_MAGIC };
isc_refcount_init(&nc->references, 1);
isc_mem_attach(mctx, &nc->mctx);
isc_ht_init(&nc->data, mctx, 5, ISC_HT_CASE_SENSITIVE);
isc_rwlock_init(&nc->rwlock);
*cachep = nc;
}
void
isc_tlsctx_cache_attach(isc_tlsctx_cache_t *source,
isc_tlsctx_cache_t **targetp) {
REQUIRE(VALID_TLSCTX_CACHE(source));
REQUIRE(targetp != NULL && *targetp == NULL);
isc_refcount_increment(&source->references);
*targetp = source;
}
static void
tlsctx_cache_entry_destroy(isc_mem_t *mctx, isc_tlsctx_cache_entry_t *entry) {
size_t i, k;
for (i = 0; i < (isc_tlsctx_cache_count - 1); i++) {
for (k = 0; k < 2; k++) {
if (entry->ctx[i][k] != NULL) {
isc_tlsctx_free(&entry->ctx[i][k]);
}
if (entry->client_sess_cache[i][k] != NULL) {
isc_tlsctx_client_session_cache_detach(
&entry->client_sess_cache[i][k]);
}
}
}
if (entry->ca_store != NULL) {
isc_tls_cert_store_free(&entry->ca_store);
}
isc_mem_put(mctx, entry, sizeof(*entry));
}
static void
tlsctx_cache_destroy(isc_tlsctx_cache_t *cache) {
isc_ht_iter_t *it = NULL;
isc_result_t result;
cache->magic = 0;
isc_refcount_destroy(&cache->references);
isc_ht_iter_create(cache->data, &it);
for (result = isc_ht_iter_first(it); result == ISC_R_SUCCESS;
result = isc_ht_iter_delcurrent_next(it))
{
isc_tlsctx_cache_entry_t *entry = NULL;
isc_ht_iter_current(it, (void **)&entry);
tlsctx_cache_entry_destroy(cache->mctx, entry);
}
isc_ht_iter_destroy(&it);
isc_ht_destroy(&cache->data);
isc_rwlock_destroy(&cache->rwlock);
isc_mem_putanddetach(&cache->mctx, cache, sizeof(*cache));
}
void
isc_tlsctx_cache_detach(isc_tlsctx_cache_t **cachep) {
isc_tlsctx_cache_t *cache = NULL;
REQUIRE(cachep != NULL);
cache = *cachep;
*cachep = NULL;
REQUIRE(VALID_TLSCTX_CACHE(cache));
if (isc_refcount_decrement(&cache->references) == 1) {
tlsctx_cache_destroy(cache);
}
}
isc_result_t
isc_tlsctx_cache_add(
isc_tlsctx_cache_t *cache, const char *name,
const isc_tlsctx_cache_transport_t transport, const uint16_t family,
isc_tlsctx_t *ctx, isc_tls_cert_store_t *store,
isc_tlsctx_client_session_cache_t *client_sess_cache,
isc_tlsctx_t **pfound, isc_tls_cert_store_t **pfound_store,
isc_tlsctx_client_session_cache_t **pfound_client_sess_cache) {
isc_result_t result = ISC_R_FAILURE;
size_t name_len, tr_offset;
isc_tlsctx_cache_entry_t *entry = NULL;
bool ipv6;
REQUIRE(VALID_TLSCTX_CACHE(cache));
REQUIRE(client_sess_cache == NULL ||
VALID_TLSCTX_CLIENT_SESSION_CACHE(client_sess_cache));
REQUIRE(name != NULL && *name != '\0');
REQUIRE(transport > isc_tlsctx_cache_none &&
transport < isc_tlsctx_cache_count);
REQUIRE(family == AF_INET || family == AF_INET6);
REQUIRE(ctx != NULL);
tr_offset = (transport - 1);
ipv6 = (family == AF_INET6);
RWLOCK(&cache->rwlock, isc_rwlocktype_write);
name_len = strlen(name);
result = isc_ht_find(cache->data, (const uint8_t *)name, name_len,
(void **)&entry);
if (result == ISC_R_SUCCESS && entry->ctx[tr_offset][ipv6] != NULL) {
isc_tlsctx_client_session_cache_t *found_client_sess_cache;
/* The entry exists. */
if (pfound != NULL) {
INSIST(*pfound == NULL);
*pfound = entry->ctx[tr_offset][ipv6];
}
if (pfound_store != NULL && entry->ca_store != NULL) {
INSIST(*pfound_store == NULL);
*pfound_store = entry->ca_store;
}
found_client_sess_cache =
entry->client_sess_cache[tr_offset][ipv6];
if (pfound_client_sess_cache != NULL &&
found_client_sess_cache != NULL)
{
INSIST(*pfound_client_sess_cache == NULL);
*pfound_client_sess_cache = found_client_sess_cache;
}
result = ISC_R_EXISTS;
} else if (result == ISC_R_SUCCESS &&
entry->ctx[tr_offset][ipv6] == NULL)
{
/*
* The hash table entry exists, but is not filled for this
* particular transport/IP type combination.
*/
entry->ctx[tr_offset][ipv6] = ctx;
entry->client_sess_cache[tr_offset][ipv6] = client_sess_cache;
/*
* As the passed certificates store object is supposed
* to be internally managed by the cache object anyway,
* we might destroy the unneeded store object right now.
*/
if (store != NULL && store != entry->ca_store) {
isc_tls_cert_store_free(&store);
}
result = ISC_R_SUCCESS;
} else {
/*
* The hash table entry does not exist, let's create one.
*/
INSIST(result != ISC_R_SUCCESS);
entry = isc_mem_get(cache->mctx, sizeof(*entry));
*entry = (isc_tlsctx_cache_entry_t){
.ca_store = store,
};
entry->ctx[tr_offset][ipv6] = ctx;
entry->client_sess_cache[tr_offset][ipv6] = client_sess_cache;
RUNTIME_CHECK(isc_ht_add(cache->data, (const uint8_t *)name,
name_len,
(void *)entry) == ISC_R_SUCCESS);
result = ISC_R_SUCCESS;
}
RWUNLOCK(&cache->rwlock, isc_rwlocktype_write);
return result;
}
isc_result_t
isc_tlsctx_cache_find(
isc_tlsctx_cache_t *cache, const char *name,
const isc_tlsctx_cache_transport_t transport, const uint16_t family,
isc_tlsctx_t **pctx, isc_tls_cert_store_t **pstore,
isc_tlsctx_client_session_cache_t **pfound_client_sess_cache) {
isc_result_t result = ISC_R_FAILURE;
size_t tr_offset;
isc_tlsctx_cache_entry_t *entry = NULL;
bool ipv6;
REQUIRE(VALID_TLSCTX_CACHE(cache));
REQUIRE(name != NULL && *name != '\0');
REQUIRE(transport > isc_tlsctx_cache_none &&
transport < isc_tlsctx_cache_count);
REQUIRE(family == AF_INET || family == AF_INET6);
REQUIRE(pctx != NULL && *pctx == NULL);
tr_offset = (transport - 1);
ipv6 = (family == AF_INET6);
RWLOCK(&cache->rwlock, isc_rwlocktype_read);
result = isc_ht_find(cache->data, (const uint8_t *)name, strlen(name),
(void **)&entry);
if (result == ISC_R_SUCCESS && pstore != NULL &&
entry->ca_store != NULL)
{
*pstore = entry->ca_store;
}
if (result == ISC_R_SUCCESS && entry->ctx[tr_offset][ipv6] != NULL) {
isc_tlsctx_client_session_cache_t *found_client_sess_cache =
entry->client_sess_cache[tr_offset][ipv6];
*pctx = entry->ctx[tr_offset][ipv6];
if (pfound_client_sess_cache != NULL &&
found_client_sess_cache != NULL)
{
INSIST(*pfound_client_sess_cache == NULL);
*pfound_client_sess_cache = found_client_sess_cache;
}
} else if (result == ISC_R_SUCCESS &&
entry->ctx[tr_offset][ipv6] == NULL)
{
result = ISC_R_NOTFOUND;
} else {
INSIST(result != ISC_R_SUCCESS);
}
RWUNLOCK(&cache->rwlock, isc_rwlocktype_read);
return result;
}
typedef struct client_session_cache_entry client_session_cache_entry_t;
typedef struct client_session_cache_bucket {
char *bucket_key;
size_t bucket_key_len;
/* Cache entries within the bucket (from the oldest to the newest). */
ISC_LIST(client_session_cache_entry_t) entries;
} client_session_cache_bucket_t;
struct client_session_cache_entry {
SSL_SESSION *session;
client_session_cache_bucket_t *bucket; /* "Parent" bucket pointer. */
ISC_LINK(client_session_cache_entry_t) bucket_link;
ISC_LINK(client_session_cache_entry_t) cache_link;
};
struct isc_tlsctx_client_session_cache {
uint32_t magic;
isc_refcount_t references;
isc_mem_t *mctx;
/*
* We need to keep a reference to the related TLS context in order
* to ensure that it remains valid while the TLS client sessions
* cache object is valid, as every TLS session object
* (SSL_SESSION) is "tied" to a particular context.
*/
isc_tlsctx_t *ctx;
/*
* The idea is to have one bucket per remote server. Each bucket,
* can maintain multiple TLS sessions to that server, as BIND
* might want to establish multiple TLS connections to the remote
* server at once.
*/
isc_ht_t *buckets;
/*
* The list of all current entries within the cache maintained in
* LRU-manner, so that the oldest entry might be efficiently
* removed.
*/
ISC_LIST(client_session_cache_entry_t) lru_entries;
/* Number of the entries within the cache. */
size_t nentries;
/* Maximum number of the entries within the cache. */
size_t max_entries;
isc_mutex_t lock;
};
void
isc_tlsctx_client_session_cache_create(
isc_mem_t *mctx, isc_tlsctx_t *ctx, const size_t max_entries,
isc_tlsctx_client_session_cache_t **cachep) {
isc_tlsctx_client_session_cache_t *nc;
REQUIRE(ctx != NULL);
REQUIRE(max_entries > 0);
REQUIRE(cachep != NULL && *cachep == NULL);
nc = isc_mem_get(mctx, sizeof(*nc));
*nc = (isc_tlsctx_client_session_cache_t){ .max_entries = max_entries };
isc_refcount_init(&nc->references, 1);
isc_mem_attach(mctx, &nc->mctx);
isc_tlsctx_attach(ctx, &nc->ctx);
isc_ht_init(&nc->buckets, mctx, 5, ISC_HT_CASE_SENSITIVE);
ISC_LIST_INIT(nc->lru_entries);
isc_mutex_init(&nc->lock);
nc->magic = TLSCTX_CLIENT_SESSION_CACHE_MAGIC;
*cachep = nc;
}
void
isc_tlsctx_client_session_cache_attach(
isc_tlsctx_client_session_cache_t *source,
isc_tlsctx_client_session_cache_t **targetp) {
REQUIRE(VALID_TLSCTX_CLIENT_SESSION_CACHE(source));
REQUIRE(targetp != NULL && *targetp == NULL);
isc_refcount_increment(&source->references);
*targetp = source;
}
static void
client_cache_entry_delete(isc_tlsctx_client_session_cache_t *restrict cache,
client_session_cache_entry_t *restrict entry) {
client_session_cache_bucket_t *restrict bucket = entry->bucket;
/* Unlink and free the cache entry */
ISC_LIST_UNLINK(bucket->entries, entry, bucket_link);
ISC_LIST_UNLINK(cache->lru_entries, entry, cache_link);
cache->nentries--;
(void)SSL_SESSION_free(entry->session);
isc_mem_put(cache->mctx, entry, sizeof(*entry));
/* The bucket is empty - let's remove it */
if (ISC_LIST_EMPTY(bucket->entries)) {
RUNTIME_CHECK(isc_ht_delete(cache->buckets,
(const uint8_t *)bucket->bucket_key,
bucket->bucket_key_len) ==
ISC_R_SUCCESS);
isc_mem_free(cache->mctx, bucket->bucket_key);
isc_mem_put(cache->mctx, bucket, sizeof(*bucket));
}
}
void
isc_tlsctx_client_session_cache_detach(
isc_tlsctx_client_session_cache_t **cachep) {
isc_tlsctx_client_session_cache_t *cache = NULL;
REQUIRE(cachep != NULL);
cache = *cachep;
*cachep = NULL;
REQUIRE(VALID_TLSCTX_CLIENT_SESSION_CACHE(cache));
if (isc_refcount_decrement(&cache->references) != 1) {
return;
}
cache->magic = 0;
isc_refcount_destroy(&cache->references);
ISC_LIST_FOREACH(cache->lru_entries, entry, cache_link) {
client_cache_entry_delete(cache, entry);
}
RUNTIME_CHECK(isc_ht_count(cache->buckets) == 0);
isc_ht_destroy(&cache->buckets);
isc_mutex_destroy(&cache->lock);
isc_tlsctx_free(&cache->ctx);
isc_mem_putanddetach(&cache->mctx, cache, sizeof(*cache));
}
void
isc_tlsctx_client_session_cache_keep(isc_tlsctx_client_session_cache_t *cache,
char *remote_peer_name, isc_tls_t *tls) {
size_t name_len;
isc_result_t result;
SSL_SESSION *sess;
client_session_cache_bucket_t *restrict bucket = NULL;
client_session_cache_entry_t *restrict entry = NULL;
REQUIRE(VALID_TLSCTX_CLIENT_SESSION_CACHE(cache));
REQUIRE(remote_peer_name != NULL && *remote_peer_name != '\0');
REQUIRE(tls != NULL);
sess = SSL_get1_session(tls);
if (sess == NULL) {
ERR_clear_error();
return;
} else if (SSL_SESSION_is_resumable(sess) == 0) {
SSL_SESSION_free(sess);
return;
}
SSL_set_session(tls, NULL);
isc_mutex_lock(&cache->lock);
name_len = strlen(remote_peer_name);
result = isc_ht_find(cache->buckets, (const uint8_t *)remote_peer_name,
name_len, (void **)&bucket);
if (result != ISC_R_SUCCESS) {
/* Let's create a new bucket */
INSIST(bucket == NULL);
bucket = isc_mem_get(cache->mctx, sizeof(*bucket));
*bucket = (client_session_cache_bucket_t){
.bucket_key = isc_mem_strdup(cache->mctx,
remote_peer_name),
.bucket_key_len = name_len
};
ISC_LIST_INIT(bucket->entries);
RUNTIME_CHECK(isc_ht_add(cache->buckets,
(const uint8_t *)remote_peer_name,
name_len,
(void *)bucket) == ISC_R_SUCCESS);
}
/* Let's add a new cache entry to the new/found bucket */
entry = isc_mem_get(cache->mctx, sizeof(*entry));
*entry = (client_session_cache_entry_t){ .session = sess,
.bucket = bucket };
ISC_LINK_INIT(entry, bucket_link);
ISC_LINK_INIT(entry, cache_link);
ISC_LIST_APPEND(bucket->entries, entry, bucket_link);
ISC_LIST_APPEND(cache->lru_entries, entry, cache_link);
cache->nentries++;
if (cache->nentries > cache->max_entries) {
/*
* Cache overrun. We need to remove the oldest entry from the
* cache
*/
client_session_cache_entry_t *restrict oldest;
INSIST((cache->nentries - 1) == cache->max_entries);
oldest = ISC_LIST_HEAD(cache->lru_entries);
client_cache_entry_delete(cache, oldest);
}
isc_mutex_unlock(&cache->lock);
}
void
isc_tlsctx_client_session_cache_reuse(isc_tlsctx_client_session_cache_t *cache,
char *remote_peer_name, isc_tls_t *tls) {
client_session_cache_bucket_t *restrict bucket = NULL;
client_session_cache_entry_t *restrict entry;
size_t name_len;
isc_result_t result;
REQUIRE(VALID_TLSCTX_CLIENT_SESSION_CACHE(cache));
REQUIRE(remote_peer_name != NULL && *remote_peer_name != '\0');
REQUIRE(tls != NULL);
isc_mutex_lock(&cache->lock);
/* Let's find the bucket */
name_len = strlen(remote_peer_name);
result = isc_ht_find(cache->buckets, (const uint8_t *)remote_peer_name,
name_len, (void **)&bucket);
if (result != ISC_R_SUCCESS) {
goto exit;
}
INSIST(bucket != NULL);
/*
* If the bucket has been found, let's use the newest session from
* the bucket, as it has the highest chance to be successfully
* resumed.
*/
INSIST(!ISC_LIST_EMPTY(bucket->entries));
entry = ISC_LIST_TAIL(bucket->entries);
RUNTIME_CHECK(SSL_set_session(tls, entry->session) == 1);
client_cache_entry_delete(cache, entry);
exit:
isc_mutex_unlock(&cache->lock);
}
void
isc_tlsctx_client_session_cache_keep_sockaddr(
isc_tlsctx_client_session_cache_t *cache, isc_sockaddr_t *remote_peer,
isc_tls_t *tls) {
char peername[ISC_SOCKADDR_FORMATSIZE] = { 0 };
REQUIRE(remote_peer != NULL);
isc_sockaddr_format(remote_peer, peername, sizeof(peername));
isc_tlsctx_client_session_cache_keep(cache, peername, tls);
}
void
isc_tlsctx_client_session_cache_reuse_sockaddr(
isc_tlsctx_client_session_cache_t *cache, isc_sockaddr_t *remote_peer,
isc_tls_t *tls) {
char peername[ISC_SOCKADDR_FORMATSIZE] = { 0 };
REQUIRE(remote_peer != NULL);
isc_sockaddr_format(remote_peer, peername, sizeof(peername));
isc_tlsctx_client_session_cache_reuse(cache, peername, tls);
}
const isc_tlsctx_t *
isc_tlsctx_client_session_cache_getctx(
isc_tlsctx_client_session_cache_t *cache) {
REQUIRE(VALID_TLSCTX_CLIENT_SESSION_CACHE(cache));
return cache->ctx;
}
void
isc_tlsctx_set_random_session_id_context(isc_tlsctx_t *ctx) {
uint8_t session_id_ctx[SSL_MAX_SID_CTX_LENGTH] = { 0 };
const size_t len = ISC_MIN(20, sizeof(session_id_ctx));
REQUIRE(ctx != NULL);
RUNTIME_CHECK(RAND_bytes(session_id_ctx, len) == 1);
RUNTIME_CHECK(
SSL_CTX_set_session_id_context(ctx, session_id_ctx, len) == 1);
}
bool
isc_tls_valid_sni_hostname(const char *hostname) {
struct sockaddr_in sa_v4 = { 0 };
struct sockaddr_in6 sa_v6 = { 0 };
int ret = 0;
if (hostname == NULL) {
return false;
}
ret = inet_pton(AF_INET, hostname, &sa_v4.sin_addr);
if (ret == 1) {
return false;
}
ret = inet_pton(AF_INET6, hostname, &sa_v6.sin6_addr);
if (ret == 1) {
return false;
}
return true;
}
static isc_result_t
isc__tls_toresult(isc_result_t fallback) {
isc_result_t result = fallback;
unsigned long err = ERR_peek_error();
#if defined(ECDSA_R_RANDOM_NUMBER_GENERATION_FAILED)
int lib = ERR_GET_LIB(err);
#endif /* if defined(ECDSA_R_RANDOM_NUMBER_GENERATION_FAILED) */
int reason = ERR_GET_REASON(err);
switch (reason) {
/*
* ERR_* errors are globally unique; others
* are unique per sublibrary
*/
case ERR_R_MALLOC_FAILURE:
result = ISC_R_NOMEMORY;
break;
default:
#if defined(ECDSA_R_RANDOM_NUMBER_GENERATION_FAILED)
if (lib == ERR_R_ECDSA_LIB &&
reason == ECDSA_R_RANDOM_NUMBER_GENERATION_FAILED)
{
result = ISC_R_NOENTROPY;
break;
}
#endif /* if defined(ECDSA_R_RANDOM_NUMBER_GENERATION_FAILED) */
break;
}
return result;
}
isc_result_t
isc__tlserr2result(isc_logcategory_t category, isc_logmodule_t module,
const char *funcname, isc_result_t fallback,
const char *file, int line) {
isc_result_t result = isc__tls_toresult(fallback);
/*
* This is an exception - normally, we don't allow this, but the
* compatibility shims in dst_openssl.h needs a call that just
* translates the error code and don't do any logging.
*/
if (category == ISC_LOGCATEGORY_INVALID) {
goto done;
}
isc_log_write(category, module, ISC_LOG_WARNING,
"%s (%s:%d) failed (%s)", funcname, file, line,
isc_result_totext(result));
if (result == ISC_R_NOMEMORY) {
goto done;
}
for (;;) {
const char *func, *data;
int flags;
unsigned long err = ERR_get_error_all(&file, &line, &func,
&data, &flags);
if (err == 0U) {
break;
}
char buf[256];
ERR_error_string_n(err, buf, sizeof(buf));
isc_log_write(category, module, ISC_LOG_INFO, "%s:%s:%d:%s",
buf, file, line,
((flags & ERR_TXT_STRING) != 0) ? data : "");
}
done:
ERR_clear_error();
return result;
}