From 992f81577011b89fecf67d8672fb4e9743a79487 Mon Sep 17 00:00:00 2001 From: Artem Boldariev Date: Mon, 13 Sep 2021 15:39:36 +0300 Subject: [PATCH] Add "protocols" options to the "tls" clause This commit adds the ability to specify allowed TLS protocols versions within the "tls" clause. If an unsupported TLS protocol version is specified in a file, the configuration file will not pass verification. Also, this commit adds strict checks for "tls" clauses verification, in particular: - it ensures that loading configuration files containing duplicated "tls" clauses is not allowed; - it ensures that loading configuration files containing "tls" clauses missing "cert-file" or "key-file" is not allowed; - it ensures that loading configuration files containing "tls" clauses named as "ephemeral" or "none" is not allowed. --- bin/named/named.conf.rst | 2 +- bin/named/server.c | 57 +++++-- .../system/checkconf/bad-dot-badprotocol.conf | 20 +++ .../checkconf/bad-dot-duplicatetls.conf | 24 +++ .../system/checkconf/bad-dot-ephemeral.conf | 20 +++ .../system/checkconf/bad-dot-nocert.conf | 30 ++++ bin/tests/system/checkconf/bad-dot-nokey.conf | 30 ++++ bin/tests/system/checkconf/bad-dot-none.conf | 20 +++ .../system/checkconf/good-doh-tlsopts.conf | 32 ++++ .../system/checkconf/good-dot-tlsopts.conf | 20 +++ doc/arm/reference.rst | 8 +- doc/man/named.conf.5in | 2 +- doc/misc/options | 2 +- doc/misc/options.active | 2 +- doc/misc/tls.grammar.rst | 2 +- lib/bind9/check.c | 150 ++++++++++++++++++ lib/isc/include/isc/tls.h | 34 ++++ lib/isc/tls.c | 96 +++++++++++ lib/isccfg/namedconf.c | 12 +- lib/ns/include/ns/listenlist.h | 20 ++- lib/ns/listenlist.c | 23 ++- 21 files changed, 571 insertions(+), 35 deletions(-) create mode 100644 bin/tests/system/checkconf/bad-dot-badprotocol.conf create mode 100644 bin/tests/system/checkconf/bad-dot-duplicatetls.conf create mode 100644 bin/tests/system/checkconf/bad-dot-ephemeral.conf create mode 100644 bin/tests/system/checkconf/bad-dot-nocert.conf create mode 100644 bin/tests/system/checkconf/bad-dot-nokey.conf create mode 100644 bin/tests/system/checkconf/bad-dot-none.conf create mode 100644 bin/tests/system/checkconf/good-doh-tlsopts.conf create mode 100644 bin/tests/system/checkconf/good-dot-tlsopts.conf diff --git a/bin/named/named.conf.rst b/bin/named/named.conf.rst index d805ff65ad..051f9bf3fe 100644 --- a/bin/named/named.conf.rst +++ b/bin/named/named.conf.rst @@ -567,7 +567,7 @@ TLS dh-param quoted_string; // experimental hostname quoted_string; key-file quoted_string; - protocols sslprotos; // experimental + protocols { string; ... }; }; TRUST-ANCHORS diff --git a/bin/named/server.c b/bin/named/server.c index 4183786f63..d73210f38e 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -413,9 +413,9 @@ named_server_reload(isc_task_t *task, isc_event_t *event); #ifdef HAVE_LIBNGHTTP2 static isc_result_t -listenelt_http(const cfg_obj_t *http, bool tls, const char *key, - const char *cert, in_port_t port, isc_mem_t *mctx, - ns_listenelt_t **target); +listenelt_http(const cfg_obj_t *http, bool tls, + const ns_listen_tls_params_t *tls_params, in_port_t port, + isc_mem_t *mctx, ns_listenelt_t **target); #endif static isc_result_t @@ -11026,6 +11026,8 @@ listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config, const char *key = NULL, *cert = NULL; bool do_tls = false, no_tls = false, http = false; ns_listenelt_t *delt = NULL; + uint32_t tls_protos = 0; + ns_listen_tls_params_t tls_params = { 0 }; REQUIRE(target != NULL && *target == NULL); @@ -11043,6 +11045,7 @@ listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config, } else { const cfg_obj_t *keyobj = NULL, *certobj = NULL; const cfg_obj_t *tlsmap = NULL; + const cfg_obj_t *tls_proto_list = NULL; do_tls = true; @@ -11059,9 +11062,35 @@ listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config, CHECK(cfg_map_get(tlsmap, "cert-file", &certobj)); cert = cfg_obj_asstring(certobj); + + if (cfg_map_get(tlsmap, "protocols", &tls_proto_list) == + ISC_R_SUCCESS) { + const cfg_listelt_t *proto = NULL; + INSIST(tls_proto_list != NULL); + for (proto = cfg_list_first(tls_proto_list); + proto != 0; proto = cfg_list_next(proto)) + { + const cfg_obj_t *tls_proto_obj = + cfg_listelt_value(proto); + const char *tls_sver = + cfg_obj_asstring(tls_proto_obj); + const isc_tls_protocol_version_t ver = + isc_tls_protocol_name_to_version( + tls_sver); + + INSIST(ver != + ISC_TLS_PROTO_VER_UNDEFINED); + INSIST(isc_tls_protocol_supported(ver)); + tls_protos |= ver; + } + } } } + tls_params = (ns_listen_tls_params_t){ .key = key, + .cert = cert, + .protocols = tls_protos }; + httpobj = cfg_tuple_get(ltup, "http"); if (httpobj != NULL && cfg_obj_isstring(httpobj)) { const char *httpname = cfg_obj_asstring(httpobj); @@ -11144,14 +11173,14 @@ listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config, #ifdef HAVE_LIBNGHTTP2 if (http) { - CHECK(listenelt_http(http_server, do_tls, key, cert, port, mctx, - &delt)); + CHECK(listenelt_http(http_server, do_tls, &tls_params, port, + mctx, &delt)); } #endif /* HAVE_LIBNGHTTP2 */ if (!http) { - CHECK(ns_listenelt_create(mctx, port, dscp, NULL, do_tls, key, - cert, &delt)); + CHECK(ns_listenelt_create(mctx, port, dscp, NULL, do_tls, + &tls_params, &delt)); } result = cfg_acl_fromconfig2(cfg_tuple_get(listener, "acl"), config, @@ -11169,9 +11198,9 @@ cleanup: #ifdef HAVE_LIBNGHTTP2 static isc_result_t -listenelt_http(const cfg_obj_t *http, bool tls, const char *key, - const char *cert, in_port_t port, isc_mem_t *mctx, - ns_listenelt_t **target) { +listenelt_http(const cfg_obj_t *http, bool tls, + const ns_listen_tls_params_t *tls_params, in_port_t port, + isc_mem_t *mctx, ns_listenelt_t **target) { isc_result_t result = ISC_R_SUCCESS; ns_listenelt_t *delt = NULL; char **endpoints = NULL; @@ -11184,7 +11213,11 @@ listenelt_http(const cfg_obj_t *http, bool tls, const char *key, isc_quota_t *quota = NULL; REQUIRE(target != NULL && *target == NULL); - REQUIRE((key == NULL) == (cert == NULL)); + + if (tls) { + INSIST(tls_params != NULL); + INSIST((tls_params->key == NULL) == (tls_params->cert == NULL)); + } if (port == 0) { port = tls ? named_g_httpsport : named_g_httpport; @@ -11239,7 +11272,7 @@ listenelt_http(const cfg_obj_t *http, bool tls, const char *key, isc_quota_init(quota, max_clients); } result = ns_listenelt_create_http(mctx, port, named_g_dscp, NULL, tls, - key, cert, endpoints, len, quota, + tls_params, endpoints, len, quota, max_streams, &delt); if (result != ISC_R_SUCCESS) { goto error; diff --git a/bin/tests/system/checkconf/bad-dot-badprotocol.conf b/bin/tests/system/checkconf/bad-dot-badprotocol.conf new file mode 100644 index 0000000000..da3b5edeb1 --- /dev/null +++ b/bin/tests/system/checkconf/bad-dot-badprotocol.conf @@ -0,0 +1,20 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +tls local-tls { + key-file "key.pem"; + cert-file "cert.pem"; + protocols { unknown; TLSv1.2; }; # bad TLS protocol version name +}; + +options { + listen-on port 853 tls local-tls { 10.53.0.1; }; +}; diff --git a/bin/tests/system/checkconf/bad-dot-duplicatetls.conf b/bin/tests/system/checkconf/bad-dot-duplicatetls.conf new file mode 100644 index 0000000000..63f5874e7e --- /dev/null +++ b/bin/tests/system/checkconf/bad-dot-duplicatetls.conf @@ -0,0 +1,24 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +tls local-tls { + key-file "key.pem"; + cert-file "cert.pem"; +}; + +tls local-tls { + key-file "key.pem"; + cert-file "cert.pem"; +}; + +options { + listen-on port 853 tls local-tls { 10.53.0.1; }; +}; diff --git a/bin/tests/system/checkconf/bad-dot-ephemeral.conf b/bin/tests/system/checkconf/bad-dot-ephemeral.conf new file mode 100644 index 0000000000..3f0273036f --- /dev/null +++ b/bin/tests/system/checkconf/bad-dot-ephemeral.conf @@ -0,0 +1,20 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +# ephemeral is reserved for internal use +tls ephemeral { + key-file "key.pem"; + cert-file "cert.pem"; +}; + +options { + listen-on port 853 tls ephemeral { 10.53.0.1; }; +}; diff --git a/bin/tests/system/checkconf/bad-dot-nocert.conf b/bin/tests/system/checkconf/bad-dot-nocert.conf new file mode 100644 index 0000000000..20c1eaf837 --- /dev/null +++ b/bin/tests/system/checkconf/bad-dot-nocert.conf @@ -0,0 +1,30 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +tls local-tls { + key-file "key.pem"; +}; + +http local-http-server { + endpoints { "/dns-query"; }; + listener-clients 100; + streams-per-connection 100; +}; + +options { + listen-on { 10.53.0.1; }; + http-port 80; + https-port 443; + http-listener-clients 100; + http-streams-per-connection 100; + listen-on port 443 tls local-tls http local-http-server { 10.53.0.1; }; + listen-on port 8080 tls none http local-http-server { 10.53.0.1; }; +}; diff --git a/bin/tests/system/checkconf/bad-dot-nokey.conf b/bin/tests/system/checkconf/bad-dot-nokey.conf new file mode 100644 index 0000000000..a6798bafee --- /dev/null +++ b/bin/tests/system/checkconf/bad-dot-nokey.conf @@ -0,0 +1,30 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +tls local-tls { + cert-file "cert.pem"; +}; + +http local-http-server { + endpoints { "/dns-query"; }; + listener-clients 100; + streams-per-connection 100; +}; + +options { + listen-on { 10.53.0.1; }; + http-port 80; + https-port 443; + http-listener-clients 100; + http-streams-per-connection 100; + listen-on port 443 tls local-tls http local-http-server { 10.53.0.1; }; + listen-on port 8080 tls none http local-http-server { 10.53.0.1; }; +}; diff --git a/bin/tests/system/checkconf/bad-dot-none.conf b/bin/tests/system/checkconf/bad-dot-none.conf new file mode 100644 index 0000000000..1eebc253d3 --- /dev/null +++ b/bin/tests/system/checkconf/bad-dot-none.conf @@ -0,0 +1,20 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +# none is reserved for internal use +tls none { + key-file "key.pem"; + cert-file "cert.pem"; +}; + +options { + listen-on port 853 tls none { 10.53.0.1; }; +}; diff --git a/bin/tests/system/checkconf/good-doh-tlsopts.conf b/bin/tests/system/checkconf/good-doh-tlsopts.conf new file mode 100644 index 0000000000..90a2f95733 --- /dev/null +++ b/bin/tests/system/checkconf/good-doh-tlsopts.conf @@ -0,0 +1,32 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +tls local-tls { + protocols { TLSv1.2; }; + key-file "key.pem"; + cert-file "cert.pem"; +}; + +http local-http-server { + endpoints { "/dns-query"; }; + listener-clients 100; + streams-per-connection 100; +}; + +options { + listen-on { 10.53.0.1; }; + http-port 80; + https-port 443; + http-listener-clients 100; + http-streams-per-connection 100; + listen-on port 443 tls local-tls http local-http-server { 10.53.0.1; }; + listen-on port 8080 tls none http local-http-server { 10.53.0.1; }; +}; diff --git a/bin/tests/system/checkconf/good-dot-tlsopts.conf b/bin/tests/system/checkconf/good-dot-tlsopts.conf new file mode 100644 index 0000000000..3552eef0aa --- /dev/null +++ b/bin/tests/system/checkconf/good-dot-tlsopts.conf @@ -0,0 +1,20 @@ +/* + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +tls local-tls { + protocols { TLSv1.2; }; + key-file "key.pem"; + cert-file "cert.pem"; +}; + +options { + listen-on port 853 tls local-tls { 10.53.0.1; }; +}; diff --git a/doc/arm/reference.rst b/doc/arm/reference.rst index 0a4627bf40..b36db11467 100644 --- a/doc/arm/reference.rst +++ b/doc/arm/reference.rst @@ -293,7 +293,7 @@ The following statements are supported: Declares communication channels to get access to ``named`` statistics. ``tls`` - Specifies configuration information for a TLS connection, including a ``key-file``, ``cert-file``, ``ca-file`` and ``hostname``. + Specifies configuration information for a TLS connection, including a ``key-file``, ``cert-file``, ``ca-file``, ``hostname``, and ``protocols``. ``http`` Specifies configuration information for an HTTP connection, including ``endponts``, ``listener-clients`` and ``streams-per-connection``. @@ -4772,6 +4772,12 @@ The following options can be specified in a ``tls`` statement: ``hostname`` The hostname associated with the certificate. + ``protocols`` + Allowed versions of the TLS protocol. TLS version 1.2 and higher are + supported, depending on the cryptographic library in use. Multiple + versions might be specified (e.g. + ``protocols { TLSv1.2; TLSv1.3; };``). + There are two built-in TLS connection configurations: ``ephemeral``, uses a temporary key and certificate created for the current ``named`` session only, and ``none``, which can be used when setting up an HTTP diff --git a/doc/man/named.conf.5in b/doc/man/named.conf.5in index d22ad8022d..0cce71b677 100644 --- a/doc/man/named.conf.5in +++ b/doc/man/named.conf.5in @@ -658,7 +658,7 @@ tls string { dh\-param quoted_string; // experimental hostname quoted_string; key\-file quoted_string; - protocols sslprotos; // experimental + protocols { string; ... }; }; .ft P .fi diff --git a/doc/misc/options b/doc/misc/options index 7b82fcec62..aa89cd4ed1 100644 --- a/doc/misc/options +++ b/doc/misc/options @@ -463,7 +463,7 @@ tls { dh-param ; // experimental hostname ; key-file ; - protocols ; // experimental + protocols { ; ... }; }; // may occur multiple times trust-anchors { ( static-key | diff --git a/doc/misc/options.active b/doc/misc/options.active index b76a310443..a745dc6327 100644 --- a/doc/misc/options.active +++ b/doc/misc/options.active @@ -460,7 +460,7 @@ tls { dh-param ; // experimental hostname ; key-file ; - protocols ; // experimental + protocols { ; ... }; }; // may occur multiple times trust-anchors { ( static-key | diff --git a/doc/misc/tls.grammar.rst b/doc/misc/tls.grammar.rst index f610a77d59..c0b5a48819 100644 --- a/doc/misc/tls.grammar.rst +++ b/doc/misc/tls.grammar.rst @@ -7,5 +7,5 @@ dh-param ; // experimental hostname ; key-file ; - protocols ; // experimental + protocols { ; ... }; }; diff --git a/lib/bind9/check.c b/lib/bind9/check.c index 88dcc4d73c..d9c14127dd 100644 --- a/lib/bind9/check.c +++ b/lib/bind9/check.c @@ -16,6 +16,8 @@ #include #include +#include + #ifdef HAVE_DNSTAP #include #endif @@ -2116,6 +2118,149 @@ done: } #endif /* HAVE_LIBNGHTTP2 */ +static isc_result_t +bind9_check_tls_defintion(const cfg_obj_t *tlsobj, const char *name, + isc_log_t *logctx, isc_symtab_t *symtab) { + isc_result_t result, tresult; + const cfg_obj_t *tls_proto_list = NULL, *tls_key = NULL, + *tls_cert = NULL; + uint32_t tls_protos = 0; + isc_symvalue_t symvalue; + + if (strcasecmp(name, "ephemeral") == 0 || strcasecmp(name, "none") == 0) + { + cfg_obj_log(tlsobj, logctx, ISC_LOG_ERROR, + "tls clause name '%s' is reserved for internal use", + name); + result = ISC_R_FAILURE; + } else { + /* Check for duplicates */ + symvalue.as_cpointer = tlsobj; + result = isc_symtab_define(symtab, name, 1, symvalue, + isc_symexists_reject); + if (result == ISC_R_EXISTS) { + const char *file = NULL; + unsigned int line; + + tresult = isc_symtab_lookup(symtab, name, 1, &symvalue); + RUNTIME_CHECK(tresult == ISC_R_SUCCESS); + + line = cfg_obj_line(symvalue.as_cpointer); + file = cfg_obj_file(symvalue.as_cpointer); + if (file == NULL) { + file = ""; + } + + cfg_obj_log(tlsobj, logctx, ISC_LOG_ERROR, + "tls clause '%s' is duplicated: " + "also defined at %s:%u", + name, file, line); + } + } + + if (cfg_map_get(tlsobj, "key-file", &tls_key) != ISC_R_SUCCESS) { + cfg_obj_log(tlsobj, logctx, ISC_LOG_ERROR, + "'key-file' is required in tls clause '%s'", name); + result = ISC_R_FAILURE; + } + + if (cfg_map_get(tlsobj, "cert-file", &tls_cert) != ISC_R_SUCCESS) { + cfg_obj_log(tlsobj, logctx, ISC_LOG_ERROR, + "'cert-file' is required in tls clause '%s'", name); + result = ISC_R_FAILURE; + } + + /* Check protocols are valid */ + tresult = cfg_map_get(tlsobj, "protocols", &tls_proto_list); + if (tresult == ISC_R_SUCCESS) { + const cfg_listelt_t *proto = NULL; + INSIST(tls_proto_list != NULL); + for (proto = cfg_list_first(tls_proto_list); proto != 0; + proto = cfg_list_next(proto)) + { + const cfg_obj_t *tls_proto_obj = + cfg_listelt_value(proto); + const char *tls_sver = cfg_obj_asstring(tls_proto_obj); + const isc_tls_protocol_version_t ver = + isc_tls_protocol_name_to_version(tls_sver); + + if (ver == ISC_TLS_PROTO_VER_UNDEFINED) { + cfg_obj_log(tls_proto_obj, logctx, + ISC_LOG_ERROR, + "'%s' is not a valid " + "TLS protocol version", + tls_sver); + result = ISC_R_FAILURE; + continue; + } else if (!isc_tls_protocol_supported(ver)) { + cfg_obj_log(tls_proto_obj, logctx, + ISC_LOG_ERROR, + "'%s' is not " + "supported by the " + "cryptographic library version in " + "use (%s)", + tls_sver, OPENSSL_VERSION_TEXT); + result = ISC_R_FAILURE; + } + + if ((tls_protos & ver) != 0) { + cfg_obj_log(tls_proto_obj, logctx, + ISC_LOG_WARNING, + "'%s' is specified more than once " + "in '%s'", + tls_sver, name); + result = ISC_R_FAILURE; + } + + tls_protos |= ver; + } + + if (tls_protos == 0) { + cfg_obj_log(tlsobj, logctx, ISC_LOG_ERROR, + "tls '%s' does not contain any valid " + "TLS protocol versions definitions", + name); + result = ISC_R_FAILURE; + } + } + + return (result); +} + +static isc_result_t +bind9_check_tls_definitions(const cfg_obj_t *config, isc_log_t *logctx, + isc_mem_t *mctx) { + isc_result_t result, tresult; + const cfg_obj_t *obj = NULL; + const cfg_listelt_t *elt = NULL; + isc_symtab_t *symtab = NULL; + + result = cfg_map_get(config, "tls", &obj); + if (result != ISC_R_SUCCESS) { + result = ISC_R_SUCCESS; + return (result); + } + + result = isc_symtab_create(mctx, 100, NULL, NULL, false, &symtab); + if (result != ISC_R_SUCCESS) { + return (result); + } + + for (elt = cfg_list_first(obj); elt != NULL; elt = cfg_list_next(elt)) { + const char *name; + obj = cfg_listelt_value(elt); + name = cfg_obj_asstring(cfg_map_getname(obj)); + tresult = bind9_check_tls_defintion(obj, name, logctx, symtab); + if (result == ISC_R_SUCCESS) { + result = tresult; + } + } + + isc_symtab_destroy(&symtab); + + return (result); +} + static isc_result_t get_remotes(const cfg_obj_t *cctx, const char *list, const char *name, const cfg_obj_t **ret) { @@ -5494,6 +5639,11 @@ bind9_check_namedconf(const cfg_obj_t *config, bool check_plugins, } #endif /* HAVE_LIBNGHTTP2 */ + if (bind9_check_tls_definitions(config, logctx, mctx) != ISC_R_SUCCESS) + { + result = ISC_R_FAILURE; + } + (void)cfg_map_get(config, "view", &views); if (views != NULL && options != NULL) { diff --git a/lib/isc/include/isc/tls.h b/lib/isc/include/isc/tls.h index 38c990648b..4520795644 100644 --- a/lib/isc/include/isc/tls.h +++ b/lib/isc/include/isc/tls.h @@ -50,6 +50,40 @@ isc_tlsctx_createclient(isc_tlsctx_t **ctxp); *\li 'ctxp' != NULL and '*ctxp' == NULL. */ +typedef enum isc_tls_protocol_version { + /* these must be the powers of two */ + ISC_TLS_PROTO_VER_1_2 = 1 << 0, + ISC_TLS_PROTO_VER_1_3 = 1 << 1, + ISC_TLS_PROTO_VER_UNDEFINED, +} isc_tls_protocol_version_t; + +void +isc_tlsctx_set_protocols(isc_tlsctx_t *ctx, const uint32_t tls_versions); +/*%< + * Sets the supported TLS protocol versions via the 'tls_versions' bit + * set argument (see `isc_tls_protocol_version_t` enum for the + * expected values). + * + * Requires: + *\li 'ctx' != NULL; + *\li 'tls_versions' != 0. + */ + +bool +isc_tls_protocol_supported(const isc_tls_protocol_version_t tls_ver); +/*%< + * Check in runtime that the specified TLS protocol versions is supported. + */ + +isc_tls_protocol_version_t +isc_tls_protocol_name_to_version(const char *name); +/*%< + * Convert the protocol version string into the version of + * 'isc_tls_protocol_version_t' type. + * Requires: + *\li 'name' != NULL. + */ + isc_tls_t * isc_tls_create(isc_tlsctx_t *ctx); /*%< diff --git a/lib/isc/tls.c b/lib/isc/tls.c index 1f9d7f9319..76fbe28690 100644 --- a/lib/isc/tls.c +++ b/lib/isc/tls.c @@ -10,6 +10,7 @@ */ #include +#include #if HAVE_LIBNGHTTP2 #include #endif /* HAVE_LIBNGHTTP2 */ @@ -364,6 +365,101 @@ ssl_error: 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: + INSIST(0); + ISC_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) + { + /* Only supported versions should ever be passed to the + * function. The configuration file was not verified + * properly, if we are trying to enable an unsupported + * TLS version */ + INSIST(isc_tls_protocol_supported(tls_ver)); + if ((tls_versions & tls_ver) == 0) { + set_options |= get_tls_version_disable_bit(tls_ver); + } else { + 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); +} + isc_tls_t * isc_tls_create(isc_tlsctx_t *ctx) { isc_tls_t *newctx = NULL; diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index 8cd23814d0..1427d4fd5e 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -3875,10 +3875,12 @@ cfg_print_zonegrammar(const unsigned int zonetype, unsigned int flags, /*% * "tls" and related statement syntax. */ -static cfg_type_t cfg_type_sslprotos = { - "sslprotos", cfg_parse_spacelist, cfg_print_spacelist, - cfg_doc_terminal, &cfg_rep_list, &cfg_type_astring -}; +static cfg_type_t cfg_type_tlsprotos = { "tls_protocols", + cfg_parse_bracketed_list, + cfg_print_bracketed_list, + cfg_doc_bracketed_list, + &cfg_rep_list, + &cfg_type_astring }; static cfg_clausedef_t tls_clauses[] = { { "key-file", &cfg_type_qstring, 0 }, @@ -3886,7 +3888,7 @@ static cfg_clausedef_t tls_clauses[] = { { "ca-file", &cfg_type_qstring, 0 }, { "hostname", &cfg_type_qstring, 0 }, { "dh-param", &cfg_type_qstring, CFG_CLAUSEFLAG_EXPERIMENTAL }, - { "protocols", &cfg_type_sslprotos, CFG_CLAUSEFLAG_EXPERIMENTAL }, + { "protocols", &cfg_type_tlsprotos, 0 }, { "ciphers", &cfg_type_astring, CFG_CLAUSEFLAG_EXPERIMENTAL }, { NULL, NULL, 0 } }; diff --git a/lib/ns/include/ns/listenlist.h b/lib/ns/include/ns/listenlist.h index 348b1ca894..ae13f67327 100644 --- a/lib/ns/include/ns/listenlist.h +++ b/lib/ns/include/ns/listenlist.h @@ -59,22 +59,34 @@ struct ns_listenlist { ISC_LIST(ns_listenelt_t) elts; }; +typedef struct ns_listen_tls_params { + const char *key; + const char *cert; + uint32_t protocols; +} ns_listen_tls_params_t; + /*** *** Functions ***/ isc_result_t ns_listenelt_create(isc_mem_t *mctx, in_port_t port, isc_dscp_t dscp, - dns_acl_t *acl, bool tls, const char *key, const char *cert, - ns_listenelt_t **target); + dns_acl_t *acl, bool tls, + const ns_listen_tls_params_t *tls_params, + ns_listenelt_t ** target); /*%< * Create a listen-on list element. + * + * Requires: + * \li 'targetp' is a valid pointer to a pointer containing 'NULL'; + * \li 'tls_params' is a valid, non-'NULL' pointer if 'tls' equals 'true'. */ isc_result_t ns_listenelt_create_http(isc_mem_t *mctx, in_port_t http_port, isc_dscp_t dscp, - dns_acl_t *acl, bool tls, const char *key, - const char *cert, char **endpoints, size_t nendpoints, + dns_acl_t *acl, bool tls, + const ns_listen_tls_params_t *tls_params, + char **endpoints, size_t nendpoints, isc_quota_t *quota, const uint32_t max_streams, ns_listenelt_t **target); /*%< diff --git a/lib/ns/listenlist.c b/lib/ns/listenlist.c index 9c445a710a..92ffac7979 100644 --- a/lib/ns/listenlist.c +++ b/lib/ns/listenlist.c @@ -26,19 +26,26 @@ destroy(ns_listenlist_t *list); isc_result_t ns_listenelt_create(isc_mem_t *mctx, in_port_t port, isc_dscp_t dscp, - dns_acl_t *acl, bool tls, const char *key, const char *cert, + dns_acl_t *acl, bool tls, + const ns_listen_tls_params_t *tls_params, ns_listenelt_t **target) { ns_listenelt_t *elt = NULL; isc_result_t result = ISC_R_SUCCESS; isc_tlsctx_t *sslctx = NULL; REQUIRE(target != NULL && *target == NULL); + REQUIRE(!tls || tls_params != NULL); if (tls) { - result = isc_tlsctx_createserver(key, cert, &sslctx); + result = isc_tlsctx_createserver(tls_params->key, + tls_params->cert, &sslctx); if (result != ISC_R_SUCCESS) { return (result); } + + if (tls_params->protocols != 0) { + isc_tlsctx_set_protocols(sslctx, tls_params->protocols); + } } elt = isc_mem_get(mctx, sizeof(*elt)); @@ -59,8 +66,9 @@ ns_listenelt_create(isc_mem_t *mctx, in_port_t port, isc_dscp_t dscp, isc_result_t ns_listenelt_create_http(isc_mem_t *mctx, in_port_t http_port, isc_dscp_t dscp, - dns_acl_t *acl, bool tls, const char *key, - const char *cert, char **endpoints, size_t nendpoints, + dns_acl_t *acl, bool tls, + const ns_listen_tls_params_t *tls_params, + char **endpoints, size_t nendpoints, isc_quota_t *quota, const uint32_t max_streams, ns_listenelt_t **target) { isc_result_t result; @@ -69,8 +77,8 @@ ns_listenelt_create_http(isc_mem_t *mctx, in_port_t http_port, isc_dscp_t dscp, REQUIRE(endpoints != NULL && *endpoints != NULL); REQUIRE(nendpoints > 0); - result = ns_listenelt_create(mctx, http_port, dscp, acl, tls, key, cert, - target); + result = ns_listenelt_create(mctx, http_port, dscp, acl, tls, + tls_params, target); if (result == ISC_R_SUCCESS) { (*target)->is_http = true; (*target)->http_endpoints = endpoints; @@ -164,8 +172,7 @@ ns_listenlist_default(isc_mem_t *mctx, in_port_t port, isc_dscp_t dscp, goto cleanup; } - result = ns_listenelt_create(mctx, port, dscp, acl, false, NULL, NULL, - &elt); + result = ns_listenelt_create(mctx, port, dscp, acl, false, NULL, &elt); if (result != ISC_R_SUCCESS) { goto cleanup_acl; }