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; }