]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
first pass at getting ALPN negotiation working
authorAlan T. DeKok <aland@freeradius.org>
Tue, 11 Apr 2023 12:46:11 +0000 (08:46 -0400)
committerAlan T. DeKok <aland@freeradius.org>
Tue, 11 Apr 2023 13:09:45 +0000 (09:09 -0400)
src/include/listen.h
src/main/listen.c
src/main/tls_listen.c

index 0ba82b839122aba17229a2cf2fa7c0bb5a0c537f..723cea46e40959815607d40ee57a2f41d26da990 100644 (file)
@@ -193,6 +193,10 @@ typedef struct listen_socket_t {
                LISTEN_TLS_SETUP,
                LISTEN_TLS_RUNNING,
        } state;
+
+#ifdef WITH_RADIUSV11
+       bool            radiusv11;              //!< defaults to "no"!
+#endif
 #endif
 
        RADCLIENT_LIST  *clients;
index 06a5de6d26004b6bbb020823303ffe21e2891192..e3d0b72d4725c4b68f3bf50d27c21bee816c87b7 100644 (file)
@@ -772,28 +772,30 @@ static int dual_tcp_accept(rad_listen_t *listener)
        }
 
 #ifdef WITH_RADIUSV11
-       switch (listener->radiusv11) {
-       case FR_RADIUSV11_FORBID:
-               if (client->radiusv11 == FR_RADIUSV11_REQUIRE) {
-                       INFO("Ignoring new connection as client is marked as 'radiusv11 = require', and this socket has 'radiusv11 = forbid'");
-                       close(newfd);
-                       return 0;
-               }
-               break;
+       if (listener->tls) {
+               switch (listener->radiusv11) {
+               case FR_RADIUSV11_FORBID:
+                       if (client->radiusv11 == FR_RADIUSV11_REQUIRE) {
+                               INFO("Ignoring new connection as client is marked as 'radiusv11 = require', and this socket has 'radiusv11 = forbid'");
+                               close(newfd);
+                               return 0;
+                       }
+                       break;
 
-       case FR_RADIUSV11_ALLOW:
-               /*
-                *      We negotiate it as per the client recommendations (forbid, allow, require)
-                */
-               break;
+               case FR_RADIUSV11_ALLOW:
+                       /*
+                        *      We negotiate it as per the client recommendations (forbid, allow, require)
+                        */
+                       break;
 
-       case FR_RADIUSV11_REQUIRE:
-               if (client->radiusv11 == FR_RADIUSV11_FORBID) {
-                       INFO("Ignoring new connection as client is marked as 'radiusv11 = forbid', and this socket has 'radiusv11 = require'");
-                       close(newfd);
-                       return 0;
+               case FR_RADIUSV11_REQUIRE:
+                       if (client->radiusv11 == FR_RADIUSV11_FORBID) {
+                               INFO("Ignoring new connection as client is marked as 'radiusv11 = forbid', and this socket has 'radiusv11 = require'");
+                               close(newfd);
+                               return 0;
+                       }
+                       break;
                }
-               break;
        }
 #endif
 
@@ -1182,6 +1184,111 @@ static int listener_unlink(UNUSED void *ctx, UNUSED void *data)
 }
 #endif
 
+#ifdef WITH_RADIUSV11
+static const unsigned char radiusv11_allow_protos[] = {
+       10, 'r', 'a', 'd', 'i', 'u', 's', '/', '1', '.', '0',
+       10, 'r', 'a', 'd', 'i', 'u', 's', '/', '1', '.', '1',
+};
+
+static const unsigned char radiusv11_require_protos[] = {
+       10, 'r', 'a', 'd', 'i', 'u', 's', '/', '1', '.', '1',
+};
+
+/*
+ *     On the server, get the ALPN list requested by the client.
+ */
+static int radiusv11_server_alpn_cb(UNUSED SSL *ssl,
+                                   const unsigned char **out,
+                                   unsigned char *outlen,
+                                   const unsigned char *in,
+                                   unsigned int inlen,
+                                   void *arg)
+{
+       rad_listen_t *this = arg;
+       listen_socket_t *sock = this->data;
+       const unsigned char *server, *client;
+       unsigned int server_len, i, j;
+
+       fr_assert(inlen > 0);
+
+       if (inlen > 2) inlen = 2; /* catch crazy clients. */
+
+       switch (this->radiusv11) {
+               /*
+                *      If we forbid RADIUSv11, then we never advertised it via ALPN, and this callback should
+                *      never have been registered.
+                */
+       case FR_RADIUSV11_FORBID:
+               fr_assert(0);
+               return SSL_TLSEXT_ERR_NOACK; /* no ALPN was negotiated for this connection */
+
+       case FR_RADIUSV11_ALLOW:
+               server = radiusv11_allow_protos;
+               server_len = sizeof(radiusv11_allow_protos);
+               break;
+
+       case FR_RADIUSV11_REQUIRE:
+               server = radiusv11_require_protos;
+               server_len = sizeof(radiusv11_require_protos);
+               break;
+       }
+
+       /*
+        *      SSL_select_next_proto() doesn't quite give us what we need, which is an easy way to know which
+        *      protocol was selected.
+        */
+       client = in;
+       for (i = 0; i < inlen; i += client[0] + 1) {
+               const unsigned char *s;
+
+               client = in + i;
+
+               if ((client + client[0] + 1) > (in + inlen)) break; /* paranoia */
+
+               for (j = 0; j < server_len; j += s[0] + 1) {
+                       s = server + j;
+
+                       if (client[0] != s[0]) continue;
+
+                       if (memcmp(client + 1, s + 1, s[0]) != 0) continue;
+
+                      *out = s;
+                      *outlen = s[0] + 1;
+
+                      /*
+                       *       Tell our socket which protocol we negotiated.
+                       */
+                      fr_assert(s[0] == 10);
+                      sock->radiusv11 = (s[10] == '1');
+
+                      return SSL_TLSEXT_ERR_OK;
+               }
+       }
+
+       /*
+        *      No common ALPN.
+        */
+       return SSL_TLSEXT_ERR_ALERT_FATAL;
+}
+
+static int radiusv11_client_alpn_cb(UNUSED SSL *ssl,
+                                   unsigned char **out,
+                                   unsigned char *outlen,
+                                   const unsigned char *in,
+                                   unsigned int inlen,
+                                   void *arg)
+{
+       const unsigned char **my_out;
+
+       /*
+        *      The OpenSSL functions randonly take "const" (or not).
+        */
+       memcpy(&my_out, &out, sizeof(out)); /* const issues */
+
+       return radiusv11_server_alpn_cb(ssl, my_out, outlen, in, inlen, arg);
+}
+#endif
+
 
 /*
  *     Parse an authentication or accounting socket.
@@ -1324,6 +1431,18 @@ int common_socket_parse(CONF_SECTION *cs, rad_listen_t *this)
 
                                this->radiusv11 = rcode;
                        }
+
+                       /*
+                        *      Default is "forbid" (0).  In which case we don't set any ALPN callbacks, and
+                        *      the ServerHello does not contain an ALPN section.
+                        */
+                       if (this->radiusv11 != FR_RADIUSV11_FORBID) {
+                               if (this->type != RAD_LISTEN_PROXY) {
+                                       SSL_CTX_set_alpn_select_cb(this->tls->ctx, radiusv11_server_alpn_cb, this);
+                               } else {
+                                       SSL_CTX_set_next_proto_select_cb(this->tls->ctx, radiusv11_client_alpn_cb, this);
+                               }
+                       }
 #endif
                }
 #else  /* WITH_TLS */
@@ -3086,6 +3205,7 @@ static rad_listen_t *listen_alloc(TALLOC_CTX *ctx, RAD_LISTEN_TYPE type)
 }
 
 #ifdef WITH_PROXY
+
 /*
  *     Externally visible function for creating a new proxy LISTENER.
  *
@@ -3224,6 +3344,30 @@ rad_listen_t *proxy_new_listener(TALLOC_CTX *ctx, home_server_t *home, uint16_t
                }
 #endif
 
+#ifdef WITH_RADIUSV11
+               /*
+                *      Tell the TLS code that we have a list of ALPN protocols to send over.
+                */
+               switch (this->radiusv11) {
+               case FR_RADIUSV11_ALLOW:
+                       if (SSL_set_alpn_protos(sock->ssn->ssl, radiusv11_allow_protos, sizeof(radiusv11_allow_protos)) != 0) {
+                       fail_protos:
+                               ERROR("Failed setting RADIUSv11 negotiation flags");
+                               listen_free(&this);
+                               return 0;
+                       }
+                       break;
+
+               case FR_RADIUSV11_REQUIRE:
+                       if (SSL_set_alpn_protos(sock->ssn->ssl, radiusv11_require_protos, sizeof(radiusv11_require_protos)) != 0) goto fail_protos;
+                       break;
+
+
+               default:
+                       break;
+               }
+#endif
+
                /*
                 *      Make sure that this listener is associated with the home server.
                 *
index 6808e6b7bb9ef0a512c444705265cb7a1869eb52..a138740e622f27438af5fc4929b790a67a7c76b5 100644 (file)
@@ -396,15 +396,6 @@ static int tls_socket_recv(rad_listen_t *listener)
                sock->packet->dst_port = sock->my_port;
 
                if (sock->request) sock->request->packet = talloc_steal(sock->request, sock->packet);
-
-#ifdef WITH_RADIUSV11
-               /*
-                *      If the flag is "allow", then the ALPN negotiation updates it to "require" if RADIUSv11
-                *      is negotiated.
-                */
-               sock->packet->radiusv11 = (listener->radiusv11 == FR_RADIUSV11_REQUIRE);
-#endif
-
        }
 
        /*
@@ -439,6 +430,7 @@ static int tls_socket_recv(rad_listen_t *listener)
                SSL_set_ex_data(sock->ssn->ssl, FR_TLS_EX_INDEX_REQUEST, (void *)request);
                SSL_set_ex_data(sock->ssn->ssl, fr_tls_ex_index_certs, (void *) &sock->certs);
                SSL_set_ex_data(sock->ssn->ssl, FR_TLS_EX_INDEX_TALLOC, sock);
+
                sock->ssn->quick_session_tickets = true; /* we don't have inner-tunnel authentication */
 
                doing_init = true;
@@ -683,6 +675,10 @@ read_application_data:
        packet->vps = NULL;
        PTHREAD_MUTEX_UNLOCK(&TLS_MUTEX);
 
+#ifdef WITH_RADIUSV11
+       packet->radiusv11 = sock->radiusv11;
+#endif
+
        if (!rad_packet_ok(packet, 0, NULL)) {
                if (DEBUG_ENABLED) ERROR("Receive - %s", fr_strerror());
                DEBUG("(TLS) Closing TLS socket from client");