From: Alan T. DeKok Date: Tue, 11 Apr 2023 13:49:09 +0000 (-0400) Subject: rearrange negotiation of radiusv11 so that it works X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1a16e780b85a8fc5ab32aa5e41dab232cdb81193;p=thirdparty%2Ffreeradius-server.git rearrange negotiation of radiusv11 so that it works --- diff --git a/src/main/listen.c b/src/main/listen.c index ab85e587f2..ead48fbdfe 100644 --- a/src/main/listen.c +++ b/src/main/listen.c @@ -708,6 +708,116 @@ static int tls_sni_callback(SSL *ssl, UNUSED int *al, void *arg) } #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); + + /* + * The RADIUSv11 configuration for this socket is a combination of what we require, and what we + * require of the client. + */ + 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; + + 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'); + + DEBUG("(TLS) Negotiated ALPN as %.*s", (int) s[0], s + 1); + return SSL_TLSEXT_ERR_OK; + } + } + + /* + * No common ALPN. + */ + DEBUG("(TLS) no common radiusv11 settings"); + 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 + + static int dual_tcp_accept(rad_listen_t *listener) { int newfd; @@ -904,11 +1014,15 @@ static int dual_tcp_accept(rad_listen_t *listener) #ifdef WITH_RADIUSV11 /* - * We've checked above for conflicts between require/forbid and forbid/require. - * So either the settings agree (forbid/forbid, require/require), OR this listener - * is marker "allow". In which case we just use whatever the client has set. + * Default is "forbid" (0). In which case we don't set any ALPN callbacks, and + * the ServerHello does not contain an ALPN section. */ - this->radiusv11 = client->radiusv11; + if (client->radiusv11 != FR_RADIUSV11_FORBID) { + SSL_CTX_set_alpn_select_cb(this->tls->ctx, radiusv11_server_alpn_cb, this); + DEBUG("(TLS) LISTEN Maybe negotiating radiusv11"); + } else { + DEBUG("(TLS) LISTEN Not doing radiusv11"); + } #endif } #endif @@ -1184,111 +1298,6 @@ 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. @@ -3286,6 +3295,38 @@ rad_listen_t *proxy_new_listener(TALLOC_CTX *ctx, home_server_t *home, uint16_t (void) SSL_set_tlsext_host_name(sock->ssn->ssl, (void *) (uintptr_t) home->tls->client_hostname); } +#ifdef WITH_RADIUSV11 + /* + * Tell the TLS code that we have a list of ALPN protocols to send over, and set the + * callbacks to decide what we negotiated. + * + * @todo - this code should arguably be in src/main/realms.c, as manging SSL_CTX isn't thread-safe. + */ + this->radiusv11 = home->tls->radiusv11; + switch (home->tls->radiusv11) { + case FR_RADIUSV11_ALLOW: + if (SSL_CTX_set_alpn_protos(home->tls->ctx, radiusv11_allow_protos, sizeof(radiusv11_allow_protos)) != 0) { + fail_protos: + ERROR("Failed setting RADIUSv11 negotiation flags"); + listen_free(&this); + return 0; + } + SSL_CTX_set_next_proto_select_cb(home->tls->ctx, radiusv11_client_alpn_cb, this); + DEBUG("(TLS) LISTEN-PROXY Maybe negotiating allow radiusv11"); + break; + + case FR_RADIUSV11_REQUIRE: + if (SSL_CTX_set_alpn_protos(home->tls->ctx, radiusv11_require_protos, sizeof(radiusv11_require_protos)) != 0) goto fail_protos; + SSL_CTX_set_next_proto_select_cb(home->tls->ctx, radiusv11_client_alpn_cb, this); + DEBUG("(TLS) LISTEN-PROXY Maybe negotiating require radiusv11"); + break; + + default: + DEBUG("(TLS) LISTEN-PROXY Forbidding radiusv11"); + break; + } +#endif + this->nonblock |= home->nonblock; /* @@ -3327,30 +3368,6 @@ 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. *