Add foundational functions to implement Strict/Mutual TLS

This commit adds a set of functions that can be used to implement
Strict and Mutual TLS:

* isc_tlsctx_load_client_ca_names();
* isc_tlsctx_load_certificate();
* isc_tls_verify_peer_result_string();
* isc_tlsctx_enable_peer_verification().
This commit is contained in:
Artem Boldariev
2022-01-11 20:40:19 +02:00
parent 32783d36c2
commit c49a81e27d
2 changed files with 178 additions and 7 deletions

View File

@@ -54,6 +54,17 @@ isc_tlsctx_createclient(isc_tlsctx_t **ctxp);
*\li 'ctxp' != NULL and '*ctxp' == NULL.
*/
isc_result_t
isc_tlsctx_load_certificate(isc_tlsctx_t *ctx, const char *keyfile,
const char *certfile);
/*%<
* Load a TLS certificate into a TLS context.
*
* Requires:
*\li 'ctx' != NULL;
*\li 'keyfile' and 'certfile' are both non-NULL.
*/
typedef enum isc_tls_protocol_version {
/* these must be the powers of two */
ISC_TLS_PROTO_VER_1_2 = 1 << 0,
@@ -156,6 +167,16 @@ isc_tls_free(isc_tls_t **tlsp);
*\li 'tlsp' != NULL and '*tlsp' != NULL.
*/
const char *
isc_tls_verify_peer_result_string(isc_tls_t *tls);
/*%<
* Return a user readable description of a remote peer's certificate
* validation.
*
* Requires:
*\li 'tls' != NULL.
*/
#if HAVE_LIBNGHTTP2
void
isc_tlsctx_enable_http2client_alpn(isc_tlsctx_t *ctx);
@@ -187,6 +208,33 @@ isc_tlsctx_enable_dot_server_alpn(isc_tlsctx_t *ctx);
*\li 'ctx' is not NULL.
*/
isc_result_t
isc_tlsctx_enable_peer_verification(isc_tlsctx_t *ctx, const bool is_server,
isc_tls_cert_store_t *store,
const char *hostname,
bool hostname_ignore_subject);
/*%<
* Enable peer certificate and, optionally, hostname (for client contexts)
* verification.
*
* Requires:
*\li 'ctx' is not NULL;
*\li 'store' is not NULL.
*/
isc_result_t
isc_tlsctx_load_client_ca_names(isc_tlsctx_t *ctx, const char *ca_bundle_file);
/*%<
* Load the list of CA-certificate names from a CA-bundle file to
* send by the server to a client when requesting a peer certificate.
* Usually used in conjunction with
* isc_tlsctx_enable_peer_validation().
*
* Requires:
*\li 'ctx' is not NULL;
*\li 'ca_bundle_file' is not NULL.
*/
isc_result_t
isc_tls_cert_store_create(const char *ca_bundle_filename,
isc_tls_cert_store_t **pstore);

View File

@@ -12,12 +12,14 @@
*/
#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>
@@ -28,6 +30,8 @@
#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/ht.h>
@@ -255,6 +259,26 @@ ssl_error:
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) {
return (ISC_R_TLSERROR);
}
rv = SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM);
if (rv != 1) {
return (ISC_R_TLSERROR);
}
return (ISC_R_SUCCESS);
}
isc_result_t
isc_tlsctx_createserver(const char *keyfile, const char *certfile,
isc_tlsctx_t **ctxp) {
@@ -443,13 +467,9 @@ isc_tlsctx_createserver(const char *keyfile, const char *certfile,
X509_free(cert);
EVP_PKEY_free(pkey);
} else {
rv = SSL_CTX_use_certificate_chain_file(ctx, certfile);
if (rv != 1) {
goto ssl_error;
}
rv = SSL_CTX_use_PrivateKey_file(ctx, keyfile,
SSL_FILETYPE_PEM);
if (rv != 1) {
isc_result_t result;
result = isc_tlsctx_load_certificate(ctx, keyfile, certfile);
if (result != ISC_R_SUCCESS) {
goto ssl_error;
}
}
@@ -740,6 +760,13 @@ isc_tls_free(isc_tls_t **tlsp) {
*tlsp = NULL;
}
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
/*
@@ -900,6 +927,102 @@ isc_tlsctx_enable_dot_server_alpn(isc_tlsctx_t *tls) {
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
}
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) {
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 */
#if defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER >= 0x3050000fL)
(void)X509_STORE_up_ref(store);
SSL_CTX_set_cert_store(tlsctx, store);
#elif defined(CRYPTO_LOCK_X509_STORE)
/*
* That is the case for OpenSSL < 1.1.X and LibreSSL < 3.5.0.
* No SSL_CTX_set1_cert_store(), no X509_STORE_up_ref(). Sigh...
*/
(void)CRYPTO_add(&store->references, 1, CRYPTO_LOCK_X509_STORE);
SSL_CTX_set_cert_store(tlsctx, store);
#else
SSL_CTX_set1_cert_store(tlsctx, store);
#endif
/* 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) {
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) {