Commit d9461c0d26 for openssl.org

commit d9461c0d26af7d0be9c571ee1be284cd8f3213f1
Author: Daniel Kubec <kubec@openssl.org>
Date:   Tue Feb 10 17:18:07 2026 +0100

    Improved reporting of shared and peer sigalgs

    The existing SSL_get_sigalgs() and SSL_get_shared_sigalgs() are not a
    good fit for TLS 1.3, because signature schemes are no longer generally
    combinations of separate digest and signing algorithms encoded in the
    two byte codepoint.

    The new SSL_get0_sigalg() and SSL_get0_shared_sigalg() functions just
    report the signature scheme name and codepoint.

    Reviewed-by: Matt Caswell <matt@openssl.org>
    Reviewed-by: Tim Hudson <tjh@openssl.org>
    Reviewed-by: Tomas Mraz <tomas@openssl.org>
    MergeDate: Wed Feb 25 11:30:16 2026
    (Merged from https://github.com/openssl/openssl/pull/29982)

diff --git a/CHANGES.md b/CHANGES.md
index 7f0e019021..34b959f152 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -32,6 +32,27 @@ OpenSSL 4.0

 ### Changes between 3.6 and 4.0 [xx XXX xxxx]

+ * New `SSL_get0_sigalg()` and `SSL_get0_shared_sigalg()` functions report the
+   TLS signature algorithm name and codepoint for the peer advertised and shared
+   algorithms respectively.  These supersede the existing `SSL_get_sigalgs()` and
+   `SSL_get_shared_sigalgs()` functions which are only a good fit for TLS 1.2.
+   The names reported are the IANA names, and are expected to consistently match
+   the names expected in `SignatureAlgorithms` configuration settings, see
+   `SSL_CONF_cmd(3)` for details.  Previously reported names were not always directly
+   usable or configurations, and were mostly OpenSSL-specific aliases that
+   rarely matched the official IANA codepoint names.
+
+   There is an associated change in how signature algorithms are reported by the
+   `openssl-s_client(1)` and `openssl-s_server(1)` command-line tools.  They
+   now use the new functions and report the IANA registered names of each
+   signature scheme.  Example new output:
+
+    ```
+    Signature Algorithms: mldsa65:mldsa87:mldsa44:ecdsa_secp256r1_sha256:ecdsa_secp384r1_sha384:ecdsa_secp521r1_sha512:ed25519:ed448:ecdsa_brainpoolP256r1tls13_sha256:ecdsa_brainpoolP384r1tls13_sha384:ecdsa_brainpoolP512r1tls13_sha512:rsa_pss_pss_sha256:rsa_pss_pss_sha384:rsa_pss_pss_sha512:rsa_pss_rsae_sha256:rsa_pss_rsae_sha384:rsa_pss_rsae_sha512:rsa_pkcs1_sha256:rsa_pkcs1_sha384:rsa_pkcs1_sha512:ecdsa_sha224:rsa_pkcs1_sha224:dsa_sha224:dsa_sha256:dsa_sha384:dsa_sha512
+    ```
+
+   *Viktor Dukhovni*
+
  * Updated the default group list to append `SecP256r1MKEM768` and
    `curveSM2MLKEM768` to the first tuple in that order after `*X25519MLKEM768`.
    Also inserted a penultimate tuple with `curveSM2` (just before the `FFDHE`
diff --git a/apps/lib/s_cb.c b/apps/lib/s_cb.c
index 77a3762be8..adbfb5262c 100644
--- a/apps/lib/s_cb.c
+++ b/apps/lib/s_cb.c
@@ -274,9 +274,9 @@ static int do_print_sigalgs(BIO *out, SSL *s, int shared)

     client = SSL_is_server(s) ? 0 : 1;
     if (shared)
-        nsig = SSL_get_shared_sigalgs(s, 0, NULL, NULL, NULL, NULL, NULL);
+        nsig = SSL_get0_shared_sigalg(s, -1, NULL, NULL);
     else
-        nsig = SSL_get_sigalgs(s, -1, NULL, NULL, NULL, NULL, NULL);
+        nsig = SSL_get0_sigalg(s, -1, NULL, NULL);
     if (nsig == 0)
         return 1;

@@ -287,45 +287,19 @@ static int do_print_sigalgs(BIO *out, SSL *s, int shared)
         BIO_puts(out, "Requested ");
     BIO_puts(out, "Signature Algorithms: ");
     for (i = 0; i < nsig; i++) {
-        int hash_nid, sign_nid;
-        unsigned char rhash, rsign;
-        const char *sstr = NULL;
+        const char *name = NULL;
+        unsigned int codepoint;
+
         if (shared)
-            SSL_get_shared_sigalgs(s, i, &sign_nid, &hash_nid, NULL,
-                &rsign, &rhash);
+            SSL_get0_shared_sigalg(s, i, &codepoint, &name);
         else
-            SSL_get_sigalgs(s, i, &sign_nid, &hash_nid, NULL, &rsign, &rhash);
-        if (i)
+            SSL_get0_sigalg(s, i, &codepoint, &name);
+        if (i > 0)
             BIO_puts(out, ":");
-        switch (rsign | rhash << 8) {
-        case 0x0809:
-            BIO_puts(out, "rsa_pss_pss_sha256");
-            continue;
-        case 0x080a:
-            BIO_puts(out, "rsa_pss_pss_sha384");
-            continue;
-        case 0x080b:
-            BIO_puts(out, "rsa_pss_pss_sha512");
-            continue;
-        case 0x081a:
-            BIO_puts(out, "ecdsa_brainpoolP256r1_sha256");
-            continue;
-        case 0x081b:
-            BIO_puts(out, "ecdsa_brainpoolP384r1_sha384");
-            continue;
-        case 0x081c:
-            BIO_puts(out, "ecdsa_brainpoolP512r1_sha512");
-            continue;
-        }
-        sstr = get_sigtype(sign_nid);
-        if (sstr)
-            BIO_puts(out, sstr);
+        if (name != NULL)
+            BIO_puts(out, name);
         else
-            BIO_printf(out, "0x%02X", (int)rsign);
-        if (hash_nid != NID_undef)
-            BIO_printf(out, "+%s", OBJ_nid2sn(hash_nid));
-        else if (sstr == NULL)
-            BIO_printf(out, "+0x%02X", (int)rhash);
+            BIO_printf(out, "0x%04X", codepoint);
     }
     BIO_puts(out, "\n");
     return 1;
@@ -1319,6 +1293,7 @@ void print_ssl_summary(SSL *s)
     const SSL_CIPHER *c;
     X509 *peer = SSL_get0_peer_certificate(s);
     EVP_PKEY *peer_rpk = SSL_get0_peer_rpk(s);
+    const char *local_sigalg = NULL;
     int nid;

     BIO_printf(bio_err, "Protocol version: %s\n", SSL_get_version(s));
@@ -1326,6 +1301,9 @@ void print_ssl_summary(SSL *s)
     c = SSL_get_current_cipher(s);
     BIO_printf(bio_err, "Ciphersuite: %s\n", SSL_CIPHER_get_name(c));
     do_print_sigalgs(bio_err, s, 0);
+    if (SSL_get0_signature_name(s, &local_sigalg) > 0
+        && local_sigalg != NULL)
+        BIO_printf(bio_err, "Own signature type: %s\n", local_sigalg);
     if (peer != NULL) {
         BIO_puts(bio_err, "Peer certificate: ");
         X509_NAME_print_ex(bio_err, X509_get_subject_name(peer),
diff --git a/doc/man3/SSL_get_shared_sigalgs.pod b/doc/man3/SSL_get_shared_sigalgs.pod
index cb9ce02500..286472e218 100644
--- a/doc/man3/SSL_get_shared_sigalgs.pod
+++ b/doc/man3/SSL_get_shared_sigalgs.pod
@@ -2,12 +2,20 @@

 =head1 NAME

-SSL_get_shared_sigalgs, SSL_get_sigalgs - get supported signature algorithms
+SSL_get0_shared_sigalg, SSL_get0_sigalg,
+SSL_get_shared_sigalgs, SSL_get_sigalgs -
+get shared or peer signature algorithms

 =head1 SYNOPSIS

  #include <openssl/ssl.h>

+ int SSL_get0_sigalg(SSL *s, int idx, unsigned int *codepoint,
+     const char **name);
+
+ int SSL_get0_shared_sigalg(SSL *s, int idx, unsigned int *codepoint,
+     const char **name);
+
  int SSL_get_shared_sigalgs(SSL *s, int idx,
                             int *psign, int *phash, int *psignhash,
                             unsigned char *rsig, unsigned char *rhash);
@@ -18,21 +26,62 @@ SSL_get_shared_sigalgs, SSL_get_sigalgs - get supported signature algorithms

 =head1 DESCRIPTION

-SSL_get_shared_sigalgs() returns information about the shared signature
-algorithms supported by peer B<s>. The parameter B<idx> indicates the index
-of the shared signature algorithm to return starting from zero. The signature
-algorithm NID is written to B<*psign>, the hash NID to B<*phash> and the
-sign and hash NID to B<*psignhash>. The raw signature and hash values
-are written to B<*rsig> and B<*rhash>.
+SSL_get0_shared_sigalg() returns the names and codepoints of signature
+algorithms shared with the peer in the SSL connection I<s>.
+When I<idx> is negative, the number of algorithms is returned, and the output
+parameters remain unmodified.
+Otherwise, when I<idx> is nonnegative, it is the index of the shared
+signature algorithm to return starting from zero.
+The signature algorithm name is written to I<*name> (if I<name>) is not NULL).
+The name reported is the IANA registered name for that algorithm, and is
+expected to work verbatim if used in a B<SignatureAlgorithms> configuration
+setting (see L<SSL_CONF_cmd(3)>).
+The signature algorithm codepoint is written to I<*codepoint> (if I<codepoint>
+is not NULL).
+This function is better suited for inspecting TLS 1.3 signature algorithms than
+the older SSL_get_shared_sigalgs().
+
+SSL_get0_sigalg() returns the names and codepoints of signature
+algorithms advertised by the peer in the SSL connection I<s>.
+When I<idx> is negative, the number of algorithms is returned, and the output
+parameters remain unmodified.
+Otherwise, when I<idx> is nonnegative, it is the index of the peer
+signature algorithm to return starting from zero.
+For signature algorithms that are locally known, the name reported is the IANA
+registered name for that algorithm, and is expected to work verbatim if used in
+a B<SignatureAlgorithms> configuration setting (see L<SSL_CONF_cmd(3)>).
+The signature algorithm codepoint is written to I<*codepoint> (if I<codepoint>
+is not NULL).
+If the signature algorithm is known, its name is written to I<*name> (if
+I<name>) is not NULL).
+This function is better suited for inspecting TLS 1.3 signature algorithms than
+the older SSL_get_sigalgs().
+
+SSL_get_shared_sigalgs() returns information about the supported signature
+algorithms shared with the peer in the SSL connection I<s>.
+When I<idx> is negative, the number of shared algorithms is returned, and
+the output parameters remain unmodified.
+Otherwise, when I<idx> is nonnegative, it is the index of the shared
+signature algorithm to return starting from zero.
+The signature algorithm NID is written to I<*psign>, the hash NID to I<*phash>
+and the sign and hash NID to I<*psignhash>.
+As of TLS 1.3 signature algorithms are not always a pairing of of a separate
+public key algorithm and a digest algorithm, and so I<*phash> and I<*psignhash>
+may not always be meaningful.
+The raw B<hash> and B<signature> bytes of the signature algorithm codepoint are
+written to I<*rhash> and I<*rsig>.
+For example, the B<rsa_pkcs1_sha256> signature algorithm, with codepoint B<0x0401>,
+gives B<0x04> for I<*rhash> and B<0x01> for I<*rsig>.

 SSL_get_sigalgs() is similar to SSL_get_shared_sigalgs() except it returns
-information about all signature algorithms supported by B<s> in the order
+information about all signature algorithms advertised by the peer in the order
 they were sent by the peer.

 =head1 RETURN VALUES

-SSL_get_shared_sigalgs() and SSL_get_sigalgs() return the number of
-signature algorithms or B<0> if the B<idx> parameter is out of range.
+SSL_get0_sigalg(), SSL_get0_shared_sigalg(), SSL_get_shared_sigalgs() and
+SSL_get_sigalgs() return the number of signature algorithms or B<0> if the
+I<idx> parameter is out of range (too large).

 =head1 NOTES

@@ -45,8 +94,8 @@ If an application is only interested in the highest preference shared
 signature algorithm it can just set B<idx> to zero.

 Any or all of the parameters B<psign>, B<phash>, B<psignhash>, B<rsig> or
-B<rhash> can be set to B<NULL> if the value is not required. By setting
-them all to B<NULL> and setting B<idx> to zero the total number of
+B<rhash> can be set to NULL if the value is not required. By setting
+them all to NULL and setting B<idx> to zero the total number of
 signature algorithms can be determined: which can be zero.

 These functions must be called after the peer has sent a list of supported
@@ -76,6 +125,14 @@ signature algorithm does not use a hash (for example Ed25519).
 L<SSL_CTX_set_cert_cb(3)>,
 L<ssl(7)>

+=head1 HISTORY
+
+Prior to OpenSSL 4.0 SSL_get_shared_sigalgs() treated negative I<idx> values
+the same way as out-of-range (too large positive) values, and returned 0.
+
+SSL_get0_shared_sigalg() and SSL_get0_sigalg() were added in OpenSSL
+4.0.
+
 =head1 COPYRIGHT

 Copyright 2015-2018 The OpenSSL Project Authors. All Rights Reserved.
diff --git a/include/openssl/tls1.h b/include/openssl/tls1.h
index b468fae881..87ea007f05 100644
--- a/include/openssl/tls1.h
+++ b/include/openssl/tls1.h
@@ -285,6 +285,11 @@ __owur int SSL_export_keying_material_early(SSL *s, unsigned char *out,
 int SSL_get_peer_signature_type_nid(const SSL *s, int *pnid);
 int SSL_get_signature_type_nid(const SSL *s, int *pnid);

+int SSL_get0_sigalg(SSL *s, int idx, unsigned int *codepoint,
+    const char **name);
+int SSL_get0_shared_sigalg(SSL *s, int idx, unsigned int *codepoint,
+    const char **name);
+
 int SSL_get_sigalgs(SSL *s, int idx,
     int *psign, int *phash, int *psignandhash,
     unsigned char *rsig, unsigned char *rhash);
diff --git a/ssl/t1_lib.c b/ssl/t1_lib.c
index c077aa5722..ec7a7b643b 100644
--- a/ssl/t1_lib.c
+++ b/ssl/t1_lib.c
@@ -2505,23 +2505,27 @@ char *SSL_get1_builtin_sigalgs(OSSL_LIB_CTX *libctx)
     return retval;
 }

-/* Lookup TLS signature algorithm */
-static const SIGALG_LOOKUP *tls1_lookup_sigalg(const SSL_CTX *ctx,
+/* Find known TLS signature algorithm */
+static const SIGALG_LOOKUP *tls1_find_sigalg(const SSL_CTX *ctx,
     uint16_t sigalg)
 {
-    size_t i;
     const SIGALG_LOOKUP *lu = ctx->sigalg_lookup_cache;

-    for (i = 0; i < ctx->sigalg_lookup_cache_len; lu++, i++) {
-        if (lu->sigalg == sigalg) {
-            if (!lu->available)
-                return NULL;
+    for (size_t i = 0; i < ctx->sigalg_lookup_cache_len; lu++, i++)
+        if (lu->sigalg == sigalg)
             return lu;
-        }
-    }
     return NULL;
 }

+/* Look up available TLS signature algorithm */
+static const SIGALG_LOOKUP *tls1_lookup_sigalg(const SSL_CTX *ctx,
+    uint16_t sigalg)
+{
+    const SIGALG_LOOKUP *lu = tls1_find_sigalg(ctx, sigalg);
+
+    return (lu != NULL && lu->available) ? lu : NULL;
+}
+
 /* Lookup hash: return 0 if invalid or not enabled */
 int tls1_lookup_md(SSL_CTX *ctx, const SIGALG_LOOKUP *lu, const EVP_MD **pmd)
 {
@@ -3750,22 +3754,20 @@ int SSL_get_sigalgs(SSL *s, int idx,
     unsigned char *rsig, unsigned char *rhash)
 {
     uint16_t *psig;
-    size_t numsigalgs;
+    int numsigalgs;
     SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(s);

     if (sc == NULL)
         return 0;

-    psig = sc->s3.tmp.peer_sigalgs;
-    numsigalgs = sc->s3.tmp.peer_sigalgslen;
-
-    if (psig == NULL || numsigalgs > INT_MAX)
+    /* A TLS peer can't propose more sigalgs than would fit in an int. */
+    numsigalgs = (int)sc->s3.tmp.peer_sigalgslen;
+    if (idx >= numsigalgs || (psig = sc->s3.tmp.peer_sigalgs) == NULL)
         return 0;
+
     if (idx >= 0) {
         const SIGALG_LOOKUP *lu;

-        if (idx >= (int)numsigalgs)
-            return 0;
         psig += idx;
         if (rhash != NULL)
             *rhash = (unsigned char)((*psig >> 8) & 0xff);
@@ -3811,6 +3813,57 @@ int SSL_get_shared_sigalgs(SSL *s, int idx,
     return (int)sc->shared_sigalgslen;
 }

+int SSL_get0_sigalg(SSL *s, int idx, unsigned int *codepoint,
+    const char **name)
+{
+    SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(s);
+    const SIGALG_LOOKUP *lu;
+    uint16_t *psig;
+    int numsigalgs;
+
+    if (sc == NULL)
+        return 0;
+
+    /* A TLS peer can't propose more sigalgs than would fit in an int. */
+    numsigalgs = (int)sc->s3.tmp.peer_sigalgslen;
+    if (idx >= numsigalgs || (psig = sc->s3.tmp.peer_sigalgs) == NULL)
+        return 0;
+
+    if (idx >= 0) {
+        if (codepoint != NULL)
+            *codepoint = psig[idx];
+        lu = tls1_find_sigalg(SSL_CONNECTION_GET_CTX(sc), psig[idx]);
+        if (name != NULL)
+            *name = lu == NULL ? NULL : lu->name;
+    }
+    return numsigalgs;
+}
+
+int SSL_get0_shared_sigalg(SSL *s, int idx, unsigned int *codepoint,
+    const char **name)
+{
+    SSL_CONNECTION *sc = SSL_CONNECTION_FROM_SSL(s);
+    const SIGALG_LOOKUP *lu;
+    int numsigalgs;
+
+    if (sc == NULL)
+        return 0;
+
+    /* A TLS peer can't propose more sigalgs than would fit in an int. */
+    numsigalgs = (int)sc->shared_sigalgslen;
+    if (idx >= numsigalgs || sc->shared_sigalgs == NULL)
+        return 0;
+
+    if (idx >= 0) {
+        lu = sc->shared_sigalgs[idx];
+        if (codepoint != NULL)
+            *codepoint = lu->sigalg;
+        if (name != NULL)
+            *name = lu->name;
+    }
+    return numsigalgs;
+}
+
 /* Maximum possible number of unique entries in sigalgs array */
 #define TLS_MAX_SIGALGCNT (OSSL_NELEM(sigalg_lookup_tbl) * 2)

diff --git a/test/sslapitest.c b/test/sslapitest.c
index f9ef582394..fb228ea061 100644
--- a/test/sslapitest.c
+++ b/test/sslapitest.c
@@ -10583,7 +10583,9 @@ static int test_sigalgs_available(int idx)
     OSSL_LIB_CTX *clientctx = libctx, *serverctx = libctx;
     OSSL_PROVIDER *filterprov = NULL;
     int sig, hash, numshared, numshared_expected, hash_expected, sig_expected;
-    const char *sigalg_name, *signame_expected;
+    unsigned char rsig, rhash;
+    unsigned int sigalg, csigalg_expected;
+    const char *sigalg_name, *signame_expected, *csigname_expected;

     if (!TEST_ptr(tmpctx))
         goto end;
@@ -10703,20 +10705,29 @@ static int test_sigalgs_available(int idx)

     /* For tests 0 and 3 we expect 2 shared sigalgs, otherwise exactly 1 */
     numshared = SSL_get_shared_sigalgs(serverssl, 0, &sig, &hash,
-        NULL, NULL, NULL);
+        NULL, &rsig, &rhash);
     numshared_expected = 1;
     hash_expected = NID_sha256;
     sig_expected = NID_rsassaPss;
     signame_expected = "rsa_pss_rsae_sha256";
+    csigname_expected = "rsa_pss_rsae_sha256";
+    csigalg_expected = 0x0804;
     switch (idx) {
     case 0:
-        hash_expected = NID_sha384;
         signame_expected = "rsa_pss_rsae_sha384";
+        hash_expected = NID_sha384;
+        numshared_expected = 2;
         /* FALLTHROUGH */
+    case 2:
+        csigname_expected = "rsa_pss_rsae_sha384";
+        csigalg_expected = 0x0805;
+        break;
     case 3:
         numshared_expected = 2;
         break;
     case 4:
+        csigname_expected = "ecdsa_secp256r1_sha256";
+        csigalg_expected = 0x0403;
     case 5:
         sig_expected = EVP_PKEY_EC;
         signame_expected = "ecdsa_secp256r1_sha256";
@@ -10727,7 +10738,13 @@ static int test_sigalgs_available(int idx)
         || !TEST_int_eq(sig, sig_expected)
         || !TEST_true(SSL_get0_peer_signature_name(clientssl, &sigalg_name))
         || !TEST_ptr(sigalg_name)
-        || !TEST_str_eq(sigalg_name, signame_expected))
+        || !TEST_str_eq(sigalg_name, signame_expected)
+        || !TEST_int_gt(SSL_get0_shared_sigalg(serverssl, 0, &sigalg, &sigalg_name), 0)
+        || !TEST_int_eq(sigalg, (((int)rhash) << 8) | rsig)
+        || !TEST_str_eq(sigalg_name, signame_expected)
+        || !TEST_int_gt(SSL_get0_sigalg(serverssl, 0, &sigalg, &sigalg_name), 0)
+        || !TEST_int_eq(sigalg, csigalg_expected)
+        || !TEST_str_eq(sigalg_name, csigname_expected))
         goto end;

     testresult = filter_provider_check_clean_finish();
diff --git a/util/libssl.num b/util/libssl.num
index 2c50b8aba7..0b8b7f0577 100644
--- a/util/libssl.num
+++ b/util/libssl.num
@@ -624,3 +624,5 @@ SSL_ech_set1_grease_suite               ?	4_0_0	EXIST::FUNCTION:ECH
 SSL_ech_get1_retry_config               ?	4_0_0	EXIST::FUNCTION:ECH
 SSL_CTX_ech_set1_outer_alpn_protos      ?	4_0_0	EXIST::FUNCTION:ECH
 SSL_set1_ech_config_list                ?	4_0_0	EXIST::FUNCTION:ECH
+SSL_get0_sigalg                         ?	4_0_0	EXIST::FUNCTION:
+SSL_get0_shared_sigalg                  ?	4_0_0	EXIST::FUNCTION: