Merge branch 'artem/tls-protocols-conf' into 'main'

Resolve #2795, #2796: implement TLS configuration options to make it possible to specify supported TLS versions and implement perfect forward secrecy for DoH and DoT

Closes #2796 and #2795

See merge request isc-projects/bind9!5444
This commit is contained in:
Artem Boldariev 2021-10-01 13:28:14 +00:00
commit 39584a5226
27 changed files with 995 additions and 54 deletions

19
CHANGES
View file

@ -1,3 +1,22 @@
5729. [func] Allow finer control over the TLS protocol by
implementing more options within "tls" clauses, namely:
- Diffie-Hellman parameters via
'dhparam-file "<path_to_file>";'
- OpenSSL cipher list string via
'ciphers "<cipher_list>";'
- Server or client ciphers preference via
'prefer-server-ciphers yes|no;'
- Ability to explicitly enable or disable stateless
TLS session tickets via 'session-tickets yes|no;'
The options are enough to implement perfect forward
secrecy in DNS-over-TLS, DNS-over-HTTPS transports.
Most of these options were no-op before this
change. [GL #2796]
5728. [func] Allow specifying supported TLS protocol
versions within "tls" clauses
(e.g. protocols { TLSv1.2; TLSv1.3; };). [GL #2795]
5727. [bug] Ignore the missing zones when doing a reload on a
catalog zone, and make sure to restore them later on.
[GL #2308]

View file

@ -563,11 +563,13 @@ TLS
tls string {
ca-file quoted_string;
cert-file quoted_string;
ciphers string; // experimental
dh-param quoted_string; // experimental
ciphers string;
dhparam-file quoted_string;
hostname quoted_string;
key-file quoted_string;
protocols sslprotos; // experimental
prefer-server-ciphers boolean;
protocols { string; ... };
session-tickets boolean;
};
TRUST-ANCHORS

View file

@ -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
@ -11023,9 +11023,15 @@ listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config,
const cfg_obj_t *http_server = NULL;
in_port_t port = 0;
isc_dscp_t dscp = -1;
const char *key = NULL, *cert = NULL;
const char *key = NULL, *cert = NULL, *dhparam_file = NULL,
*ciphers = NULL;
bool tls_prefer_server_ciphers = false,
tls_prefer_server_ciphers_set = false;
bool tls_session_tickets = false, tls_session_tickets_set = false;
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);
@ -11041,8 +11047,13 @@ listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config,
} else if (strcasecmp(tlsname, "ephemeral") == 0) {
do_tls = true;
} else {
const cfg_obj_t *keyobj = NULL, *certobj = NULL;
const cfg_obj_t *keyobj = NULL, *certobj = NULL,
*dhparam_obj = NULL;
const cfg_obj_t *tlsmap = NULL;
const cfg_obj_t *tls_proto_list = NULL;
const cfg_obj_t *ciphers_obj = NULL;
const cfg_obj_t *prefer_server_ciphers_obj = NULL;
const cfg_obj_t *session_tickets_obj = NULL;
do_tls = true;
@ -11059,9 +11070,70 @@ 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;
}
}
if (cfg_map_get(tlsmap, "dhparam-file", &dhparam_obj) ==
ISC_R_SUCCESS) {
dhparam_file = cfg_obj_asstring(dhparam_obj);
}
if (cfg_map_get(tlsmap, "ciphers", &ciphers_obj) ==
ISC_R_SUCCESS) {
ciphers = cfg_obj_asstring(ciphers_obj);
}
if (cfg_map_get(tlsmap, "prefer-server-ciphers",
&prefer_server_ciphers_obj) ==
ISC_R_SUCCESS)
{
tls_prefer_server_ciphers = cfg_obj_asboolean(
prefer_server_ciphers_obj);
tls_prefer_server_ciphers_set = true;
}
if (cfg_map_get(tlsmap, "session-tickets",
&session_tickets_obj) == ISC_R_SUCCESS)
{
tls_session_tickets =
cfg_obj_asboolean(session_tickets_obj);
tls_session_tickets_set = true;
}
}
}
tls_params = (ns_listen_tls_params_t){
.key = key,
.cert = cert,
.protocols = tls_protos,
.dhparam_file = dhparam_file,
.ciphers = ciphers,
.prefer_server_ciphers = tls_prefer_server_ciphers,
.prefer_server_ciphers_set = tls_prefer_server_ciphers_set,
.session_tickets = tls_session_tickets,
.session_tickets_set = tls_session_tickets_set
};
httpobj = cfg_tuple_get(ltup, "http");
if (httpobj != NULL && cfg_obj_isstring(httpobj)) {
const char *httpname = cfg_obj_asstring(httpobj);
@ -11144,14 +11216,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 +11241,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 +11256,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 +11315,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;

View file

@ -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";
ciphers "$bad:ciphers";
};
options {
listen-on port 853 tls local-tls { 10.53.0.1; };
};

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,36 @@
/*
* 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";
dhparam-file "dhparam.pem";
ciphers "HIGH:!aNULL:!MD5:!RC4";
prefer-server-ciphers yes;
session-tickets no;
};
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; };
};

View file

@ -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 {
protocols { TLSv1.2; };
key-file "key.pem";
cert-file "cert.pem";
dhparam-file "dhparam.pem";
ciphers "HIGH:!aNULL:!MD5:!RC4";
prefer-server-ciphers yes;
session-tickets no;
};
options {
listen-on port 853 tls local-tls { 10.53.0.1; };
};

View file

@ -0,0 +1,11 @@
-----BEGIN DH PARAMETERS-----
MIIBiAKCAYEA5D/Oioe+G+EMf/9RVxmcV4rZAtqZpVTFHcX0ZulvdiQGCQmopm6K
3+0uoU2J6WVMjhna5nHD2NO9miRDI/jIxX9g9k6PedSB4o3fSTtkAnGtUbB8S+Ab
EHtWfd7FTES8P1n16HN7BfPXVbP8zTcK+jO63KdQoxueYoETcrw0Myi9Lm8ri8os
O4oQ+XAH7GzZ60bcYV9jge0XIRUGVnYZDjWMlnwMvZyjLivxKXTC9HPNA6FF1/0H
0LPhsfjdoLNsVHFzfQz7QELMfHbTd0C8y0UMDQw9FqUp0esHZ5gsTlqnDHp2ZHoR
JDfNl4yVO5Gv4HiFJ0NSdggefhESU3FRAOhMmUkctOCxk5hyPqGMsvofOajY2MBp
eCffrKuAU6/dGUeq8inwrZlAMIZ20WyskHmbHnc4DXo2Uo6xSZo3xyEq1ofXXwTZ
vPw4e12so3RJAT2a8UsHf7DG1tH+9ke7HCAJQWxUizRFRsMi1Nl/7ikS4f3zgIbX
GKz9+uk5eS6jAgEC
-----END DH PARAMETERS-----

View file

@ -18,6 +18,7 @@ controls {
tls local {
key-file "key.pem";
cert-file "cert.pem";
dhparam-file "dhparam3072.pem";
};
http local {

View file

@ -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``, ``dhparam-file``, ``hostname``, ``ciphers``, ``protocols``, ``prefer-server-ciphers``, and ``session-tickets``.
``http``
Specifies configuration information for an HTTP connection, including ``endponts``, ``listener-clients`` and ``streams-per-connection``.
@ -4769,9 +4769,72 @@ The following options can be specified in a ``tls`` statement:
``ca-file``
Path to a file containing trusted TLS certificates.
``dhparam-file``
Path to a file containing Diffie-Hellman parameters,
which is needed to enable the cipher suites depending on the
Diffie-Hellman ephemeral key exchange (DHE). Having these parameters
specified is essential for enabling perfect forward secrecy capable
ciphers in TLSv1.2.
``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; };``).
``ciphers``
Cipher list which defines allowed ciphers, such as
``HIGH:!aNULL:!MD5:!SHA1:!SHA256:!SHA384``. The string must be
formed according to the rules specified in the OpenSSL documentation
(see https://www.openssl.org/docs/man1.1.1/man1/ciphers.html
for details).
``prefer-server-ciphers``
Specifies that server ciphers should be preferred over client ones.
``session-tickets``
Enables or disables session resumption through TLS session tickets,
as defined in RFC5077. Disabling the stateless session tickets
might be required in the cases when forward secrecy is needed,
or the TLS certificate and key pair is planned to be used across
multiple BIND instances.
The options described above are used to control different aspects of
TLS functioning. Thus, most of them have no well-defined default
values, as these depend on the cryptographic library version in use
and system-wide cryptographic policy. On the other hand, by specifying
the needed options one could have a uniform configuration deployable
across a range of platforms.
An example of privacy-oriented, perfect forward secrecy enabled
configuration can be found below. It can be used as a
starting point.
::
tls local-tls {
key-file "/path/to/key.pem";
cert-file "/path/to/fullchain_cert.pem";
dhparam-file "/path/to/dhparam.pem";
ciphers "HIGH:!kRSA:!aNULL:!eNULL:!RC4:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS:!SHA1:!SHA256:!SHA384";
prefer-server-ciphers yes;
session-tickets no;
};
A Diffie-Hellman parameters file can be generated using e.g. OpenSSL,
like follows:
::
openssl dhparam -out /path/to/dhparam.pem <3072_or_4096>
Ensure that it gets generated on a machine with enough entropy from
external sources (e.g. the computer you work on should be fine,
the remote virtual machine or server might be not). These files do
not contain any sensitive data and can be shared if required.
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

View file

@ -654,11 +654,13 @@ statistics\-channels {
tls string {
ca\-file quoted_string;
cert\-file quoted_string;
ciphers string; // experimental
dh\-param quoted_string; // experimental
ciphers string;
dhparam\-file quoted_string;
hostname quoted_string;
key\-file quoted_string;
protocols sslprotos; // experimental
prefer\-server\-ciphers boolean;
protocols { string; ... };
session\-tickets boolean;
};
.ft P
.fi

View file

@ -459,11 +459,13 @@ statistics-channels {
tls <string> {
ca-file <quoted_string>;
cert-file <quoted_string>;
ciphers <string>; // experimental
dh-param <quoted_string>; // experimental
ciphers <string>;
dhparam-file <quoted_string>;
hostname <quoted_string>;
key-file <quoted_string>;
protocols <sslprotos>; // experimental
prefer-server-ciphers <boolean>;
protocols { <string>; ... };
session-tickets <boolean>;
}; // may occur multiple times
trust-anchors { <string> ( static-key |

View file

@ -456,11 +456,13 @@ statistics-channels {
tls <string> {
ca-file <quoted_string>;
cert-file <quoted_string>;
ciphers <string>; // experimental
dh-param <quoted_string>; // experimental
ciphers <string>;
dhparam-file <quoted_string>;
hostname <quoted_string>;
key-file <quoted_string>;
protocols <sslprotos>; // experimental
prefer-server-ciphers <boolean>;
protocols { <string>; ... };
session-tickets <boolean>;
}; // may occur multiple times
trust-anchors { <string> ( static-key |

View file

@ -3,9 +3,11 @@
tls <string> {
ca-file <quoted_string>;
cert-file <quoted_string>;
ciphers <string>; // experimental
dh-param <quoted_string>; // experimental
ciphers <string>;
dhparam-file <quoted_string>;
hostname <quoted_string>;
key-file <quoted_string>;
protocols <sslprotos>; // experimental
prefer-server-ciphers <boolean>;
protocols { <string>; ... };
session-tickets <boolean>;
};

View file

@ -24,7 +24,17 @@ Known Issues
New Features
~~~~~~~~~~~~
- None.
- Ability to specify supported TLS protocol versions within ``tls``
clauses (e.g. ``protocols { TLSv1.2; TLSv1.3; };``). :gl:`#2795`
- New options within ``tls`` clauses were implemented, namely:
- ``dhparam-file "<path_to_file>";`` to specify Diffie-Hellman parameters;
- ``ciphers "<cipher_list>";`` to specify OpenSSL ciphers list;
- ``prefer-server-ciphers yes|no;`` to assert server or client ciphers preference;
- ``session-tickets yes|no;`` to explicitly enable or disable stateless TLS session tickets (see RFC5077).
These options allow finer control over TLS protocol features and make it
possible to achieve perfect forward secrecy for DNS-over-TLS and
DNS-over-HTTPS. :gl:`#2796`
Removed Features
~~~~~~~~~~~~~~~~

View file

@ -16,6 +16,8 @@
#include <stdbool.h>
#include <stdlib.h>
#include <openssl/opensslv.h>
#ifdef HAVE_DNSTAP
#include <fstrm.h>
#endif
@ -2116,6 +2118,163 @@ 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, *tls_ciphers = 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 = "<unknown 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;
}
}
/* Check cipher list string is valid */
tresult = cfg_map_get(tlsobj, "ciphers", &tls_ciphers);
if (tresult == ISC_R_SUCCESS) {
const char *ciphers = cfg_obj_asstring(tls_ciphers);
if (!isc_tls_cipherlist_valid(ciphers)) {
cfg_obj_log(tls_ciphers, logctx, ISC_LOG_ERROR,
"'ciphers' in the 'tls' clause '%s' is "
"not a "
"valid cipher list string",
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 +5653,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) {

View file

@ -50,6 +50,90 @@ 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.
*/
bool
isc_tlsctx_load_dhparams(isc_tlsctx_t *ctx, const char *dhparams_file);
/*%<
* Load Diffie-Hellman parameters file and apply it to the given TLS context
* 'ctx'.
*
* Requires:
* \li 'ctx' != NULL;
* \li 'dhaprams_file' a valid pointer to a non empty string.
*/
bool
isc_tls_cipherlist_valid(const char *cipherlist);
/*%<
* Check if cipher list string is valid.
*
* Requires:
* \li 'cipherlist' a valid pointer to a non empty string.
*/
void
isc_tlsctx_set_cipherlist(isc_tlsctx_t *ctx, const char *cipherlist);
/*%<
* Set cipher list string for on the given TLS context 'ctx'.
*
* Requires:
* \li 'ctx' != NULL;
* \li 'cipherlist' a valid pointer to a non empty string.
*/
void
isc_tlsctx_prefer_server_ciphers(isc_tlsctx_t *ctx, const bool prefer);
/*%<
* Make the given TLS context 'ctx' to prefer or to not prefer
* server side ciphers during the ciphers negotiation.
*
* Requires:
* \li 'ctx' != NULL.
*/
void
isc_tlsctx_session_tickets(isc_tlsctx_t *ctx, const bool use);
/*%<
* Enable/Disable stateless session resumptions tickets on the given
* TLS context 'ctx' (see RFC5077).
*
* Requires:
* \li 'ctx' != NULL.
*/
isc_tls_t *
isc_tls_create(isc_tlsctx_t *ctx);
/*%<

View file

@ -10,12 +10,14 @@
*/
#include <inttypes.h>
#include <string.h>
#if HAVE_LIBNGHTTP2
#include <nghttp2/nghttp2.h>
#endif /* HAVE_LIBNGHTTP2 */
#include <openssl/bn.h>
#include <openssl/conf.h>
#include <openssl/dh.h>
#include <openssl/err.h>
#include <openssl/opensslv.h>
#include <openssl/rand.h>
@ -33,6 +35,9 @@
#include "openssl_shim.h"
#include "tls_p.h"
#define COMMON_SSL_OPTIONS \
(SSL_OP_NO_COMPRESSION | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION)
static isc_once_t init_once = ISC_ONCE_INIT;
static isc_once_t shut_once = ISC_ONCE_INIT;
static atomic_bool init_done = ATOMIC_VAR_INIT(false);
@ -185,13 +190,13 @@ isc_tlsctx_createclient(isc_tlsctx_t **ctxp) {
goto ssl_error;
}
SSL_CTX_set_options(ctx, COMMON_SSL_OPTIONS);
#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);
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1);
#endif
*ctxp = ctx;
@ -235,6 +240,8 @@ isc_tlsctx_createserver(const char *keyfile, const char *certfile,
}
RUNTIME_CHECK(ctx != NULL);
SSL_CTX_set_options(ctx, COMMON_SSL_OPTIONS);
#if HAVE_SSL_CTX_SET_MIN_PROTO_VERSION
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
#else
@ -359,6 +366,225 @@ 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);
}
bool
isc_tlsctx_load_dhparams(isc_tlsctx_t *ctx, const char *dhparams_file) {
REQUIRE(ctx != NULL);
REQUIRE(dhparams_file != NULL);
REQUIRE(*dhparams_file != '\0');
#ifdef SSL_CTX_set_tmp_dh
/* 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: SSL_CTX_set_tmp_dh() is 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
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);
}
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;

View file

@ -3875,19 +3875,23 @@ 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 },
{ "cert-file", &cfg_type_qstring, 0 },
{ "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 },
{ "ciphers", &cfg_type_astring, CFG_CLAUSEFLAG_EXPERIMENTAL },
{ "dhparam-file", &cfg_type_qstring, 0 },
{ "protocols", &cfg_type_tlsprotos, 0 },
{ "ciphers", &cfg_type_astring, 0 },
{ "prefer-server-ciphers", &cfg_type_boolean, 0 },
{ "session-tickets", &cfg_type_boolean, 0 },
{ NULL, NULL, 0 }
};

View file

@ -59,22 +59,40 @@ struct ns_listenlist {
ISC_LIST(ns_listenelt_t) elts;
};
typedef struct ns_listen_tls_params {
const char *key;
const char *cert;
uint32_t protocols;
const char *dhparam_file;
const char *ciphers;
bool prefer_server_ciphers;
bool prefer_server_ciphers_set;
bool session_tickets;
bool session_tickets_set;
} 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);
/*%<

View file

@ -26,19 +26,49 @@ 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);
}
if (tls_params->dhparam_file != NULL) {
if (!isc_tlsctx_load_dhparams(sslctx,
tls_params->dhparam_file))
{
isc_tlsctx_free(&sslctx);
return (ISC_R_FAILURE);
}
}
if (tls_params->ciphers != NULL) {
isc_tlsctx_set_cipherlist(sslctx, tls_params->ciphers);
}
if (tls_params->prefer_server_ciphers_set) {
isc_tlsctx_prefer_server_ciphers(
sslctx, tls_params->prefer_server_ciphers);
}
if (tls_params->session_tickets_set) {
isc_tlsctx_session_tickets(sslctx,
tls_params->session_tickets);
}
}
elt = isc_mem_get(mctx, sizeof(*elt));
@ -59,8 +89,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 +100,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 +195,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;
}

View file

@ -276,6 +276,7 @@
./bin/tests/system/doth/clean.sh SH 2020,2021
./bin/tests/system/doth/example.axfr.good X 2021
./bin/tests/system/doth/ns2/cert.pem X 2021
./bin/tests/system/doth/ns2/dhparam3072.pem X 2021
./bin/tests/system/doth/ns2/key.pem X 2021
./bin/tests/system/doth/setup.sh SH 2021
./bin/tests/system/doth/stress_http_quota.py PYTHON-BIN 2021