From: Alan T. DeKok Date: Sun, 25 Jul 2021 19:30:40 +0000 (-0400) Subject: do SNI on the server side. X-Git-Tag: release_3_0_24~109 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0159e1f0682cb5280a84eb8db1772ffedbc88cf5;p=thirdparty%2Ffreeradius-server.git do SNI on the server side. And cache the name in TLS-Server-Name-Indication --- diff --git a/doc/ChangeLog b/doc/ChangeLog index 8fec499ed0..006fc8ddf4 100644 --- a/doc/ChangeLog +++ b/doc/ChangeLog @@ -12,6 +12,9 @@ FreeRADIUS 3.0.24 Thu 10 Jun 2021 12:00:00 EDT urgency=low See raddb/certs/realms/README.md * Add Server Name Indication (SNI) for outbound RadSec connections. See raddb/sites-available/tls, and the home server tls configuration. + * Support SNI for inbound RadSec connections. Certificates will + be loaded from "realm_dir" in the "tls" section. SNI will be + cached in the TLS-Server-Name-Indication attribute. Bug fixes * Fix crash in trustrouter module (#4115). Patch from Alejandro Perez diff --git a/raddb/certs/realms/REAMDE.md b/raddb/certs/realms/REAMDE.md index 87d22e69e6..f4dee04dc0 100644 --- a/raddb/certs/realms/REAMDE.md +++ b/raddb/certs/realms/REAMDE.md @@ -166,7 +166,32 @@ configuration in the server is for all TLS functionality, and not just EAP. This means that the server can accept RadSec connections, and then -present different server certificates to different clients. However, -there is currently no standard way for RadSec clients to send a Server -Name Indicator (SNI) as with HTTPS. As a result, certificate -selection has to be done on something else, such as source IP address. +present different server certificates to different clients. + +For this functionality to work, the certificates for EAP and RadSec +*should* be in separate directories. + +### Clients + +RadSec clients can set the SNI to send in the `tls` subsection of the +`home_server` definition. See `sites-available/tls` for examples. + +### Servers + +See the `realm_dir` configuration item in the `tls` subsection for the +location of the server certificates. + +If the server receives an SNI for a realm it does not recognize, it +will just use the default TLS configuration. + +If the realm is recognized (i.e. there is a file in +`${realm_dir}/%{TLS-Server-Name-Indication}.pem`, then that certificate will be chosen, and +present to the RadSec client. If there is no such file, then the +default TLS configuration is used. + +The current behavior is to _require_ that the server certificate is in +a file which taken from +`${realm_dir}/%{TLS-Server-Name-Indication}.pem`. Only the +`realm_dir` portion of the filename is configurable. The SNI portion +is taken from the TLS messages, and the `.pem` suffix is hard-coded in +the source code. diff --git a/share/dictionary.freeradius.internal b/share/dictionary.freeradius.internal index 499634bd85..347e3e59f3 100644 --- a/share/dictionary.freeradius.internal +++ b/share/dictionary.freeradius.internal @@ -599,6 +599,8 @@ ATTRIBUTE TLS-Session-Cipher-Suite 1948 string ATTRIBUTE TLS-Session-Cert-File 1949 string ATTRIBUTE TLS-Session-Cert-Private-Key-File 1950 string +ATTRIBUTE TLS-Server-Name-Indication 1951 string + # # Range: 1960-2099 # Free diff --git a/src/main/listen.c b/src/main/listen.c index c9c2722614..fc9af01f01 100644 --- a/src/main/listen.c +++ b/src/main/listen.c @@ -615,6 +615,68 @@ static int dual_tcp_recv(rad_listen_t *listener) return 1; } +typedef struct { + char const *name; + SSL_CTX *ctx; +} fr_realm_ctx_t; /* hack from tls. */ + +static int tls_sni_callback(SSL *ssl, UNUSED int *al, void *arg) +{ + fr_tls_server_conf_t *conf = arg; + char const *name, *p; + int type; + fr_realm_ctx_t my_r, *r; + REQUEST *request; + char buffer[PATH_MAX]; + + /* + * No SNI, that's fine. + */ + type = SSL_get_servername_type(ssl); + if (type < 0) return SSL_TLSEXT_ERR_OK; + + /* + * No realms configured, just use the default context. + */ + if (!conf->realms) return SSL_TLSEXT_ERR_OK; + + name = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + if (!name) return SSL_TLSEXT_ERR_OK; + + /* + * RFC Section 6066 Section 3 says that the names are + * ASCII, without a trailing dot. i.e. punycode. + */ + for (p = name; *p != '\0'; p++) { + if (*p == '-') continue; + if (*p == '.') continue; + if ((*p >= 'A') && (*p <= 'Z')) continue; + if ((*p >= 'a') && (*p <= 'z')) continue; + if ((*p >= '0') && (*p <= '9')) continue; + + /* + * Anything else, ignore it. + */ + return SSL_TLSEXT_ERR_OK; + } + + snprintf(buffer, sizeof(buffer), "%s/%s.pem", conf->realm_dir, name); + + my_r.name = buffer; + r = fr_hash_table_finddata(conf->realms, &my_r); + if (!r) return SSL_TLSEXT_ERR_OK; + + /* + * Set an attribute saying which server has been selected. + */ + request = (REQUEST *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_REQUEST); + if (request) { + (void) pair_make_config("TLS-Server-Name-Indication", name, T_OP_SET); + } + + (void) SSL_set_SSL_CTX(ssl, r->ctx); + return SSL_TLSEXT_ERR_OK; +} static int dual_tcp_accept(rad_listen_t *listener) { @@ -770,6 +832,14 @@ static int dual_tcp_accept(rad_listen_t *listener) #ifdef WITH_TLS if (this->tls) { + /* + * Set up SNI callback. We don't do it + * in the main TLS code, because EAP + * doesn't need or use SNI. + */ + SSL_CTX_set_tlsext_servername_callback(this->tls->ctx, tls_sni_callback); + SSL_CTX_set_tlsext_servername_arg(this->tls->ctx, this->tls); + this->recv = dual_tls_recv; this->send = dual_tls_send; }