Commit 6d2f848f94 for openssl.org

commit 6d2f848f9463d05d1c00f70b1d024fad9ef03a5d
Author: Daniel Kubec <kubec@openssl.org>
Date:   Thu Jan 15 14:18:31 2026 +0000

    Added SSL_CTX_get0_alpn_protos() and SSL_get0_alpn_protos()

    Fixes #4952

    Co-authored-by: Pauli <ppzgs1@gmail.com>
    Co-authored-by: Tomáš Mráz <tm@t8m.info>

    Reviewed-by: Paul Dale <paul.dale@oracle.com>
    Reviewed-by: Nikola Pajkovsky <nikolap@openssl.org>
    Reviewed-by: Tomas Mraz <tomas@openssl.org>
    MergeDate: Mon Jan 26 15:26:21 2026
    (Merged from https://github.com/openssl/openssl/pull/29646)

diff --git a/CHANGES.md b/CHANGES.md
index fbf06a1029..c2193abbd3 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -127,6 +127,10 @@ OpenSSL 4.0

    *Igor Ustinov*

+ * Added SSL_CTX_get0_alpn_protos() and SSL_get0_alpn_protos().
+
+   *Daniel Kubec*
+
  * Enabled Server verification by default in `s_server` when option
    verify_return_error is enabled.

diff --git a/doc/man3/SSL_CTX_set_alpn_select_cb.pod b/doc/man3/SSL_CTX_set_alpn_select_cb.pod
index dd5517df4d..b97ebd1a5a 100644
--- a/doc/man3/SSL_CTX_set_alpn_select_cb.pod
+++ b/doc/man3/SSL_CTX_set_alpn_select_cb.pod
@@ -2,7 +2,8 @@

 =head1 NAME

-SSL_CTX_set_alpn_protos, SSL_set_alpn_protos, SSL_CTX_set_alpn_select_cb,
+SSL_CTX_set_alpn_protos, SSL_set_alpn_protos, SSL_CTX_get0_alpn_protos,
+SSL_get0_alpn_protos, SSL_CTX_set_alpn_select_cb,
 SSL_CTX_set_next_proto_select_cb, SSL_CTX_set_next_protos_advertised_cb,
 SSL_select_next_proto, SSL_get0_alpn_selected, SSL_get0_next_proto_negotiated
 - handle application layer protocol negotiation (ALPN)
@@ -47,6 +48,11 @@ SSL_select_next_proto, SSL_get0_alpn_selected, SSL_get0_next_proto_negotiated
  void SSL_get0_next_proto_negotiated(const SSL *s, const unsigned char **data,
                              unsigned *len);

+ void SSL_CTX_get0_alpn_protos(SSL_CTX *ctx, const unsigned char **protos,
+    unsigned int *protos_len);
+ void SSL_get0_alpn_protos(SSL *ssl, const unsigned char **protos,
+    unsigned int *protos_len);
+
 =head1 DESCRIPTION

 SSL_CTX_set_alpn_protos() and SSL_set_alpn_protos() are used by the client to
@@ -55,6 +61,12 @@ protocol-list format, described below. The length of B<protos> is specified in
 B<protos_len>. Setting B<protos_len> to 0 clears any existing list of ALPN
 protocols and no ALPN extension will be sent to the server.

+SSL_CTX_get0_alpn_protos() and SSL_get0_alpn_protos() are used by the client to
+get the list of protocols available to be negotiated. The B<protos> are in
+protocol-list format, described below. Returns a pointer to protocol list in
+B<protos> with length B<protos_len>. It is not NUL-terminated. B<protos> must
+not be freed.
+
 SSL_CTX_set_alpn_select_cb() sets the application callback B<cb> used by a
 server to select which protocol to use for the incoming connection. When B<cb>
 is NULL, ALPN is not used. The B<arg> value is a pointer which is passed to
diff --git a/include/openssl/ssl.h.in b/include/openssl/ssl.h.in
index 83d72e6fa8..529c2b71da 100644
--- a/include/openssl/ssl.h.in
+++ b/include/openssl/ssl.h.in
@@ -825,6 +825,10 @@ void SSL_CTX_set_alpn_select_cb(SSL_CTX *ctx,
     void *arg);
 void SSL_get0_alpn_selected(const SSL *ssl, const unsigned char **data,
     unsigned int *len);
+void SSL_CTX_get0_alpn_protos(SSL_CTX *ctx, const unsigned char **protos,
+    unsigned int *protos_len);
+void SSL_get0_alpn_protos(SSL *ssl, const unsigned char **protos,
+    unsigned int *protos_len);

 #ifndef OPENSSL_NO_PSK
 /*
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c
index 43ca1e46d7..9efda38100 100644
--- a/ssl/ssl_lib.c
+++ b/ssl/ssl_lib.c
@@ -3778,6 +3778,47 @@ int SSL_set_alpn_protos(SSL *ssl, const unsigned char *protos,
     return 0;
 }

+/*
+ * SSL_CTX_set_get0_protos gets the ALPN protocol list on |ctx| to |protos|.
+ */
+void SSL_CTX_get0_alpn_protos(SSL_CTX *ctx, const unsigned char **protos,
+    unsigned int *protos_len)
+{
+    unsigned char *p = NULL;
+    unsigned int len = 0;
+
+    if (ctx != NULL) {
+        p = ctx->ext.alpn;
+        len = (unsigned int)ctx->ext.alpn_len;
+    }
+
+    if (protos != NULL)
+        *protos = p;
+    if (protos_len != NULL)
+        *protos_len = len;
+}
+
+/*
+ * SSL_get0_alpn_protos gets the ALPN protocol list on |ssl| to |protos|.
+ */
+void SSL_get0_alpn_protos(SSL *ssl, const unsigned char **protos,
+    unsigned int *protos_len)
+{
+    SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(ssl);
+    unsigned char *p = NULL;
+    unsigned int len = 0;
+
+    if (sc != NULL) {
+        p = sc->ext.alpn;
+        len = (unsigned int)sc->ext.alpn_len;
+    }
+
+    if (protos != NULL)
+        *protos = p;
+    if (protos_len != NULL)
+        *protos_len = len;
+}
+
 /*
  * SSL_CTX_set_alpn_select_cb sets a callback function on |ctx| that is
  * called during ClientHello processing in order to select an ALPN protocol
diff --git a/test/sslapitest.c b/test/sslapitest.c
index 1fb0477a8c..07a101313c 100644
--- a/test/sslapitest.c
+++ b/test/sslapitest.c
@@ -11623,6 +11623,57 @@ end:
     return testresult;
 }

+static int test_get_alpn(void)
+{
+    SSL_CTX *ctx = NULL;
+    SSL *ssl = NULL;
+    int testresult = 0;
+    unsigned char good[] = { 0x04, 'g', 'o', 'o', 'd' };
+    const unsigned char *expect;
+    unsigned int expect_len;
+
+    /* Create an initial SSL_CTX with no certificate configured */
+    ctx = SSL_CTX_new_ex(libctx, NULL, TLS_server_method());
+    if (!TEST_ptr(ctx))
+        goto end;
+
+    SSL_CTX_get0_alpn_protos(NULL, &expect, &expect_len);
+    if (!TEST_ptr_null(expect))
+        goto end;
+    if (!TEST_int_eq(expect_len, 0))
+        goto end;
+    if (!TEST_false(SSL_CTX_set_alpn_protos(ctx, good, sizeof(good))))
+        goto end;
+
+    SSL_CTX_get0_alpn_protos(ctx, &expect, &expect_len);
+    if (!TEST_mem_eq(expect, expect_len, good, sizeof(good)))
+        goto end;
+
+    ssl = SSL_new(ctx);
+    if (!TEST_ptr(ssl))
+        goto end;
+
+    SSL_get0_alpn_protos(NULL, &expect, &expect_len);
+    if (!TEST_ptr_null(expect))
+        goto end;
+    if (!TEST_int_eq(expect_len, 0))
+        goto end;
+
+    if (!TEST_false(SSL_set_alpn_protos(ssl, good, sizeof(good))))
+        goto end;
+
+    SSL_get0_alpn_protos(ssl, &expect, &expect_len);
+    if (!TEST_mem_eq(expect, expect_len, good, sizeof(good)))
+        goto end;
+
+    testresult = 1;
+
+end:
+    SSL_free(ssl);
+    SSL_CTX_free(ctx);
+    return testresult;
+}
+
 #if !defined(OPENSSL_NO_EC) && !defined(OPENSSL_NO_TLS1_2)
 /*
  * Complete a connection with legacy EC point format configuration
@@ -14154,6 +14205,7 @@ int setup_tests(void)
 #endif
     ADD_TEST(test_inherit_verify_param);
     ADD_TEST(test_set_alpn);
+    ADD_TEST(test_get_alpn);
 #if !defined(OPENSSL_NO_EC) && !defined(OPENSSL_NO_TLS1_2)
     ADD_TEST(test_legacy_ec_point_formats);
 #endif
diff --git a/util/libssl.num b/util/libssl.num
index f8ca36f67d..86083e5133 100644
--- a/util/libssl.num
+++ b/util/libssl.num
@@ -54,6 +54,8 @@ SSL_get0_next_proto_negotiated          ?	4_0_0	EXIST::FUNCTION:NEXTPROTONEG
 SSL_select_next_proto                   ?	4_0_0	EXIST::FUNCTION:
 SSL_CTX_set_alpn_protos                 ?	4_0_0	EXIST::FUNCTION:
 SSL_set_alpn_protos                     ?	4_0_0	EXIST::FUNCTION:
+SSL_CTX_get0_alpn_protos                ?	4_0_0	EXIST::FUNCTION:
+SSL_get0_alpn_protos                    ?	4_0_0	EXIST::FUNCTION:
 SSL_CTX_set_alpn_select_cb              ?	4_0_0	EXIST::FUNCTION:
 SSL_get0_alpn_selected                  ?	4_0_0	EXIST::FUNCTION:
 SSL_CTX_set_psk_client_callback         ?	4_0_0	EXIST::FUNCTION:PSK