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: