]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-ssl-iostream: Support application protocol negotiation
authorAki Tuomi <aki.tuomi@open-xchange.com>
Tue, 14 May 2024 16:32:01 +0000 (19:32 +0300)
committerAki Tuomi <aki.tuomi@open-xchange.com>
Fri, 17 Jan 2025 08:40:01 +0000 (10:40 +0200)
m4/ssl.m4
src/lib-ssl-iostream/iostream-openssl-context.c
src/lib-ssl-iostream/iostream-openssl.c
src/lib-ssl-iostream/iostream-openssl.h
src/lib-ssl-iostream/iostream-ssl-private.h
src/lib-ssl-iostream/iostream-ssl.c
src/lib-ssl-iostream/iostream-ssl.h

index d94c84f636f741ac89d2474f27466bc0a7b1451b..169d3d260d3e900d1d36bef70559644d49bb7ae1 100644 (file)
--- 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"
 ])
index 00357761dda424d77c9e03cfefca6afc50308318..6e64586f65ce932e14eb627f1c986b5f74281265 100644 (file)
@@ -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 <openssl/err.h>
 #include <arpa/inet.h>
 
+#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,
index 2faf906ff076768f7364da0f4c5fa3f36fa8e834..2f67a8a7ea96158e8fcccc059923c41a81bfff93 100644 (file)
@@ -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)
index 4dbc51a71cfa5a5947d41fd357ac3c1a0db2a6c4..2a7e9648c436b96bda959fe7ba4092157be669ed 100644 (file)
@@ -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);
index f0680c887da44cb59b68617d06e300e9b2c0fe55..0b55811edff874c90e5d20fbe0e88febef792003 100644 (file)
@@ -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);
index 604a5eba48fa44b3344dac86579fe64741c4c1e5..33b5e8e336370dc45cbd27c0ce6e499609db4527 100644 (file)
@@ -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);
+}
index 578058ff1911323c8fe4a2996d485a2a174dd847..a65f30c337339e0f24bad0affc7c796262145296 100644 (file)
@@ -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);