From: Aki Tuomi Date: Tue, 14 May 2024 16:32:01 +0000 (+0300) Subject: lib-ssl-iostream: Support application protocol negotiation X-Git-Tag: 2.4.0~201 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=bfd72cd57ca031bc6e1629af65b1ca0f30f1fcce;p=thirdparty%2Fdovecot%2Fcore.git lib-ssl-iostream: Support application protocol negotiation --- diff --git a/m4/ssl.m4 b/m4/ssl.m4 index d94c84f636..169d3d260d 100644 --- a/m4/ssl.m4 +++ b/m4/ssl.m4 @@ -110,6 +110,7 @@ AC_DEFUN([DOVECOT_SSL], [ DOVECOT_CHECK_SSL_FUNC([SSL_CTX_set_client_hello_cb]) DOVECOT_CHECK_SSL_FUNC([SSL_CTX_select_current_cert]) DOVECOT_CHECK_SSL_FUNC([SSL_client_hello_get0_ciphers]) + DOVECOT_CHECK_SSL_FUNC([SSL_CTX_set_alpn_select_cb]) CFLAGS="$old_CFLAGS" ]) diff --git a/src/lib-ssl-iostream/iostream-openssl-context.c b/src/lib-ssl-iostream/iostream-openssl-context.c index 00357761dd..6e64586f65 100644 --- a/src/lib-ssl-iostream/iostream-openssl-context.c +++ b/src/lib-ssl-iostream/iostream-openssl-context.c @@ -2,6 +2,7 @@ #include "lib.h" #include "str.h" +#include "array.h" #include "connection.h" #include "hex-binary.h" #include "safe-memset.h" @@ -18,6 +19,8 @@ #include #include +#define ALPN_MAX_PROTOCOLS 10 + struct ssl_iostream_password_context { const char *password; const char *error; @@ -543,6 +546,58 @@ static int ssl_clienthello_callback(SSL *ssl, int *al, #endif +#if defined(HAVE_SSL_CTX_set_alpn_select_cb) +static int +openssl_iostream_alpn_select(SSL *ssl ATTR_UNUSED, const unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) +{ + struct ssl_iostream_context *ctx = arg; + ARRAY(struct ssl_alpn_protocol) in_protos; + const unsigned char *end = in + inlen; + const struct ssl_alpn_protocol *in_proto, *proto; + size_t count_in = 0; + + t_array_init(&in_protos, 1); + + if (ctx->protos == NULL) { + /* nothing available */ + return SSL_TLSEXT_ERR_NOACK; + } + + /* allow max 10 protocols */ + while (in < end && count_in < ALPN_MAX_PROTOCOLS) + { + unsigned char len = *in; + if (in + len > end) + return SSL_TLSEXT_ERR_ALERT_FATAL; + in++; + struct ssl_alpn_protocol *value = array_append_space(&in_protos); + /* Normalize protocol into lowercase string, and ensure it does + not contain NUL bytes. */ + const char *proto = t_str_lcase(t_strndup(in, len)); + value->proto = (const unsigned char *)proto; + value->proto_len = strlen(proto); + in += len; + count_in++; + } + + array_foreach(&in_protos, in_proto) { + for (proto = ctx->protos; proto->proto != NULL; proto++) { + if (in_proto->proto_len == proto->proto_len && + memcmp(in_proto->proto, proto->proto, proto->proto_len) == 0) { + *out = proto->proto; + *outlen = proto->proto_len; + return SSL_TLSEXT_ERR_OK; + } + } + } + + return SSL_TLSEXT_ERR_ALERT_FATAL; +} +#endif + + static int ssl_iostream_context_load_ca(struct ssl_iostream_context *ctx, const struct ssl_iostream_settings *set, @@ -685,6 +740,13 @@ ssl_iostream_context_set(struct ssl_iostream_context *ctx, ssl_servername_callback) != 1) i_unreached(); #endif +#ifdef HAVE_SSL_CTX_set_alpn_select_cb + SSL_CTX_set_alpn_select_cb(ctx->ssl_ctx, openssl_iostream_alpn_select, ctx); +#endif + } + if (set->application_protocols != NULL) { + openssl_iostream_context_set_application_protocols(ctx, + set->application_protocols); } return 0; } @@ -708,6 +770,33 @@ ssl_proxy_ctx_set_crypto_params(SSL_CTX *ssl_ctx, return 0; } +void openssl_iostream_context_set_application_protocols(struct ssl_iostream_context *ctx, + const char *const *names) +{ + i_assert(names != NULL); + ARRAY(struct ssl_alpn_protocol) protos; + p_array_init(&protos, ctx->pool, str_array_length(names) + 1); + for (; *names != NULL; names++) { + struct ssl_alpn_protocol *proto = array_append_space(&protos); + proto->proto_len = strlen(*names); + i_assert(proto->proto_len <= UINT8_MAX); + proto->proto = p_memdup(ctx->pool, *names, proto->proto_len); + } + array_append_zero(&protos); + ctx->protos = array_front(&protos); +#ifdef HAVE_SSL_CTX_set_alpn_select_cb + if (ctx->client_ctx) { + buffer_t *protos = buffer_create_dynamic(ctx->pool, 32); + for (size_t i = 0; ctx->protos[i].proto != NULL; i++) { + unsigned char len = ctx->protos[i].proto_len; + buffer_append_c(protos, len); + buffer_append(protos, ctx->protos[i].proto, len); + } + SSL_CTX_set_alpn_protos(ctx->ssl_ctx, protos->data, protos->used); + } +#endif +} + static int ssl_iostream_context_init_common(struct ssl_iostream_context *ctx, const struct ssl_iostream_settings *set, diff --git a/src/lib-ssl-iostream/iostream-openssl.c b/src/lib-ssl-iostream/iostream-openssl.c index 2faf906ff0..2f67a8a7ea 100644 --- a/src/lib-ssl-iostream/iostream-openssl.c +++ b/src/lib-ssl-iostream/iostream-openssl.c @@ -618,6 +618,9 @@ static int openssl_iostream_handshake(struct ssl_iostream *ssl_io) i_free_and_null(ssl_io->last_error); ssl_io->handshaked = TRUE; + const char *alpn_proto = ssl_iostream_get_application_protocol(ssl_io); + if (alpn_proto != NULL && *alpn_proto != '\0') + e_debug(ssl_io->event, "SSL: Chosen application protocol %s", alpn_proto); if (ssl_io->ssl_output != NULL) (void)o_stream_flush(ssl_io->ssl_output); return 1; @@ -816,6 +819,20 @@ openssl_iostream_get_ja3(struct ssl_iostream *ssl_io) return ssl_io->ja3_str; } +static const char * +openssl_iostream_get_application_protocol(struct ssl_iostream *ssl_io) +{ + if (!ssl_io->handshaked || ssl_io->handshake_failed) + return NULL; + const unsigned char *data; + unsigned int len; + + SSL_get0_alpn_selected(ssl_io->ssl, &data, &len); + if (data != NULL) + return t_strndup(data, len); + return NULL; +} + static const struct iostream_ssl_vfuncs ssl_vfuncs = { .global_init = openssl_iostream_global_init, .context_init_client = openssl_iostream_context_init_client, @@ -848,6 +865,9 @@ static const struct iostream_ssl_vfuncs ssl_vfuncs = { .get_pfs = openssl_iostream_get_pfs, .get_protocol_name = openssl_iostream_get_protocol_name, .get_ja3 = openssl_iostream_get_ja3, + + .get_application_protocol = openssl_iostream_get_application_protocol, + .set_application_protocols = openssl_iostream_context_set_application_protocols, }; void ssl_iostream_openssl_init(void) diff --git a/src/lib-ssl-iostream/iostream-openssl.h b/src/lib-ssl-iostream/iostream-openssl.h index 4dbc51a71c..2a7e9648c4 100644 --- a/src/lib-ssl-iostream/iostream-openssl.h +++ b/src/lib-ssl-iostream/iostream-openssl.h @@ -22,6 +22,11 @@ struct ssl_iostream_context { pool_t pool; + const struct ssl_alpn_protocol { + const unsigned char *proto; + size_t proto_len; + } *protos; + int username_nid; bool client_ctx:1; @@ -83,6 +88,10 @@ int openssl_iostream_context_init_client(const struct ssl_iostream_settings *set int openssl_iostream_context_init_server(const struct ssl_iostream_settings *set, struct ssl_iostream_context **ctx_r, const char **error_r); + +void openssl_iostream_context_set_application_protocols(struct ssl_iostream_context *ssl_ctx, + const char *const *names); + void openssl_iostream_context_ref(struct ssl_iostream_context *ctx); void openssl_iostream_context_unref(struct ssl_iostream_context *ctx); void openssl_iostream_global_deinit(void); diff --git a/src/lib-ssl-iostream/iostream-ssl-private.h b/src/lib-ssl-iostream/iostream-ssl-private.h index f0680c887d..0b55811edf 100644 --- a/src/lib-ssl-iostream/iostream-ssl-private.h +++ b/src/lib-ssl-iostream/iostream-ssl-private.h @@ -52,6 +52,10 @@ struct iostream_ssl_vfuncs { const char *(*get_pfs)(struct ssl_iostream *ssl_io); const char *(*get_protocol_name)(struct ssl_iostream *ssl_io); const char *(*get_ja3)(struct ssl_iostream *ssl_io); + + const char *(*get_application_protocol)(struct ssl_iostream *ssl_io); + void (*set_application_protocols)(struct ssl_iostream_context *ctx, + const char *const *names); }; void iostream_ssl_module_init(const struct iostream_ssl_vfuncs *vfuncs); diff --git a/src/lib-ssl-iostream/iostream-ssl.c b/src/lib-ssl-iostream/iostream-ssl.c index 604a5eba48..33b5e8e336 100644 --- a/src/lib-ssl-iostream/iostream-ssl.c +++ b/src/lib-ssl-iostream/iostream-ssl.c @@ -403,3 +403,14 @@ const char *ssl_iostream_get_ja3(struct ssl_iostream *ssl_io) { return ssl_vfuncs->get_ja3(ssl_io); } + +const char *ssl_iostream_get_application_protocol(struct ssl_iostream *ssl_io) +{ + return ssl_vfuncs->get_application_protocol(ssl_io); +} + +void ssl_iostream_context_set_application_protocols(struct ssl_iostream_context *ssl_ctx, + const char *const *names) +{ + ssl_vfuncs->set_application_protocols(ssl_ctx, names); +} diff --git a/src/lib-ssl-iostream/iostream-ssl.h b/src/lib-ssl-iostream/iostream-ssl.h index 578058ff19..a65f30c337 100644 --- a/src/lib-ssl-iostream/iostream-ssl.h +++ b/src/lib-ssl-iostream/iostream-ssl.h @@ -45,6 +45,9 @@ struct ssl_iostream_settings { const char *cert_username_field; const char *crypto_device; + /* List of application protocol names */ + const char *const *application_protocols; + /* If FALSE, check for CA CRLs. */ bool skip_crl_check; /* server-only: Request client certificate. */ @@ -206,6 +209,11 @@ const char *ssl_iostream_get_protocol_name(struct ssl_iostream *ssl_io); const char *ssl_iostream_get_last_error(struct ssl_iostream *ssl_io); +const char *ssl_iostream_get_application_protocol(struct ssl_iostream *ssl_io); + +void ssl_iostream_context_set_application_protocols(struct ssl_iostream_context *ssl_ctx, + const char *const *names); + int ssl_iostream_context_init_client(const struct ssl_iostream_settings *set, struct ssl_iostream_context **ctx_r, const char **error_r);