This commit completes the support for DNS-over-HTTP(S) built on top of nghttp2 and plugs it into the BIND. Support for both GET and POST requests is present, as required by RFC8484. Both encrypted (via TLS) and unencrypted HTTP/2 connections are supported. The latter are mostly there for debugging/troubleshooting purposes and for the means of encryption offloading to third-party software (as might be desirable in some environments to simplify TLS certificates management).
299 lines
6.6 KiB
C
299 lines
6.6 KiB
C
/*
|
|
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
|
*
|
|
* 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 <openssl/err.h>
|
|
#include <openssl/opensslv.h>
|
|
|
|
#include <isc/atomic.h>
|
|
#include <isc/log.h>
|
|
#include <isc/mutex.h>
|
|
#include <isc/mutexblock.h>
|
|
#include <isc/once.h>
|
|
#include <isc/thread.h>
|
|
#include <isc/tls.h>
|
|
#include <isc/util.h>
|
|
|
|
#include "openssl_shim.h"
|
|
|
|
static isc_once_t init_once = ISC_ONCE_INIT;
|
|
static atomic_bool init_done = ATOMIC_VAR_INIT(false);
|
|
|
|
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
|
static isc_mem_t *isc__tls_mctx = NULL;
|
|
static isc_mutex_t *locks = NULL;
|
|
static int nlocks;
|
|
|
|
static void
|
|
isc__tls_lock_callback(int mode, int type, const char *file, int line) {
|
|
UNUSED(file);
|
|
UNUSED(line);
|
|
if ((mode & CRYPTO_LOCK) != 0) {
|
|
LOCK(&locks[type]);
|
|
} else {
|
|
UNLOCK(&locks[type]);
|
|
}
|
|
}
|
|
|
|
static void
|
|
isc__tls_set_thread_id(CRYPTO_THREADID *id) {
|
|
CRYPTO_THREADID_set_numeric(id, (unsigned long)isc_thread_self());
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
isc__tls_initialize(void) {
|
|
REQUIRE(!atomic_load(&init_done));
|
|
RUNTIME_CHECK(OPENSSL_init_ssl(0, NULL) == 1);
|
|
|
|
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
|
isc_mem_create(&isc__tls_mctx);
|
|
|
|
nlocks = CRYPTO_num_locks();
|
|
locks = isc_mem_get(isc__tls_mctx, nlocks * sizeof(locks[0]));
|
|
isc_mutexblock_init(locks, nlocks);
|
|
CRYPTO_set_locking_callback(isc__tls_lock_callback);
|
|
CRYPTO_THREADID_set_callback(isc__tls_set_thread_id);
|
|
ERR_load_crypto_strings();
|
|
#endif
|
|
atomic_store(&init_done, true);
|
|
}
|
|
|
|
void
|
|
isc_tls_initialize(void) {
|
|
isc_result_t result = isc_once_do(&init_once, isc__tls_initialize);
|
|
REQUIRE(result == ISC_R_SUCCESS);
|
|
REQUIRE(atomic_load(&init_done));
|
|
}
|
|
|
|
void
|
|
isc_tls_destroy(void) {
|
|
REQUIRE(atomic_load(&init_done));
|
|
#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
|
|
ERR_free_strings();
|
|
|
|
ERR_remove_thread_state(NULL);
|
|
CRYPTO_set_locking_callback(NULL);
|
|
|
|
if (locks != NULL) {
|
|
INSIST(isc__tls_mctx != NULL);
|
|
isc_mutexblock_destroy(locks, nlocks);
|
|
isc_mem_put(isc__tls_mctx, locks, nlocks * sizeof(locks[0]));
|
|
locks = NULL;
|
|
}
|
|
|
|
if (isc__tls_mctx != NULL) {
|
|
isc_mem_detach(&isc__tls_mctx);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
#if HAVE_SSL_CTX_SET_MIN_PROTO_VERSION
|
|
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
|
|
#else
|
|
SSL_CTX_set_options(
|
|
ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 |
|
|
SSL_OP_NO_TLSv1_1 | SSL_OP_NO_COMPRESSION |
|
|
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
|
|
#endif
|
|
|
|
*ctxp = ctx;
|
|
|
|
return (ISC_R_SUCCESS);
|
|
|
|
ssl_error:
|
|
err = ERR_get_error();
|
|
ERR_error_string_n(err, errbuf, sizeof(errbuf));
|
|
isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, ISC_LOGMODULE_NETMGR,
|
|
ISC_LOG_ERROR, "Error initializing TLS context: %s",
|
|
errbuf);
|
|
|
|
return (ISC_R_TLSERROR);
|
|
}
|
|
|
|
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;
|
|
BIGNUM *bn = NULL;
|
|
SSL_CTX *ctx = NULL;
|
|
RSA *rsa = NULL;
|
|
char errbuf[256];
|
|
const SSL_METHOD *method = NULL;
|
|
|
|
REQUIRE(ctxp != NULL && *ctxp == NULL);
|
|
|
|
if (ephemeral) {
|
|
INSIST(keyfile == NULL);
|
|
INSIST(certfile == NULL);
|
|
} else {
|
|
INSIST(keyfile != NULL);
|
|
INSIST(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);
|
|
|
|
#if HAVE_SSL_CTX_SET_MIN_PROTO_VERSION
|
|
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
|
|
#else
|
|
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
|
|
SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1);
|
|
#endif
|
|
|
|
if (ephemeral) {
|
|
rsa = RSA_new();
|
|
if (rsa == NULL) {
|
|
goto ssl_error;
|
|
}
|
|
bn = BN_new();
|
|
if (bn == NULL) {
|
|
goto ssl_error;
|
|
}
|
|
BN_set_word(bn, RSA_F4);
|
|
rv = RSA_generate_key_ex(rsa, 4096, bn, NULL);
|
|
if (rv != 1) {
|
|
goto ssl_error;
|
|
}
|
|
cert = X509_new();
|
|
if (cert == NULL) {
|
|
goto ssl_error;
|
|
}
|
|
pkey = EVP_PKEY_new();
|
|
if (pkey == NULL) {
|
|
goto ssl_error;
|
|
}
|
|
|
|
/*
|
|
* EVP_PKEY_assign_*() set the referenced key to key
|
|
* however these use the supplied key internally and so
|
|
* key will be freed when the parent pkey is freed.
|
|
*/
|
|
EVP_PKEY_assign(pkey, EVP_PKEY_RSA, rsa);
|
|
rsa = NULL;
|
|
ASN1_INTEGER_set(X509_get_serialNumber(cert), 1);
|
|
|
|
X509_gmtime_adj(X509_get_notBefore(cert), 0);
|
|
/*
|
|
* We set the vailidy for 10 years.
|
|
*/
|
|
X509_gmtime_adj(X509_get_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, EVP_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);
|
|
BN_free(bn);
|
|
} else {
|
|
rv = SSL_CTX_use_certificate_file(ctx, certfile,
|
|
SSL_FILETYPE_PEM);
|
|
if (rv != 1) {
|
|
goto ssl_error;
|
|
}
|
|
rv = SSL_CTX_use_PrivateKey_file(ctx, keyfile,
|
|
SSL_FILETYPE_PEM);
|
|
if (rv != 1) {
|
|
goto ssl_error;
|
|
}
|
|
}
|
|
|
|
*ctxp = ctx;
|
|
return (ISC_R_SUCCESS);
|
|
|
|
ssl_error:
|
|
err = ERR_get_error();
|
|
ERR_error_string_n(err, errbuf, sizeof(errbuf));
|
|
isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, ISC_LOGMODULE_NETMGR,
|
|
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 (bn != NULL) {
|
|
BN_free(bn);
|
|
}
|
|
if (rsa != NULL) {
|
|
RSA_free(rsa);
|
|
}
|
|
|
|
return (ISC_R_TLSERROR);
|
|
}
|