]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
do SNI on the server side.
authorAlan T. DeKok <aland@freeradius.org>
Sun, 25 Jul 2021 19:30:40 +0000 (15:30 -0400)
committerAlan T. DeKok <aland@freeradius.org>
Sun, 25 Jul 2021 19:40:33 +0000 (15:40 -0400)
And cache the name in TLS-Server-Name-Indication

doc/ChangeLog
raddb/certs/realms/REAMDE.md
share/dictionary.freeradius.internal
src/main/listen.c

index 8fec499ed018feab3c78aa58ef3ccdf6bb6412ef..006fc8ddf4a033e898163262e9ce54e203de4fa2 100644 (file)
@@ -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
index 87d22e69e632420584d884fd9e0506c0d5c39c88..f4dee04dc00b29a222caa588e0f61bb6a5a399a8 100644 (file)
@@ -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.
index 499634bd857c4efc85a89edba0198147f3f09084..347e3e59f35e88cb194d582be4aebb542002f063 100644 (file)
@@ -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
index c9c27226146fc8c16e5364e2b48eafeb6de5ce8a..fc9af01f011f5ff655984f280ca733989a6fa270 100644 (file)
@@ -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;
                }