From: Alan T. DeKok Date: Tue, 11 Apr 2023 12:46:11 +0000 (-0400) Subject: first pass at getting ALPN negotiation working X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=25a3b538c966ae22906fcb8a084a014a20a31ca1;p=thirdparty%2Ffreeradius-server.git first pass at getting ALPN negotiation working --- diff --git a/src/include/listen.h b/src/include/listen.h index 0ba82b8391..723cea46e4 100644 --- a/src/include/listen.h +++ b/src/include/listen.h @@ -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; diff --git a/src/main/listen.c b/src/main/listen.c index 06a5de6d26..e3d0b72d47 100644 --- a/src/main/listen.c +++ b/src/main/listen.c @@ -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. * diff --git a/src/main/tls_listen.c b/src/main/tls_listen.c index 6808e6b7bb..a138740e62 100644 --- a/src/main/tls_listen.c +++ b/src/main/tls_listen.c @@ -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");