Commit b8e5bb958e for openssl.org

commit b8e5bb958ee4501a95b424c6f9f457dd013b4d83
Author: Viktor Dukhovni <viktor@openssl.org>
Date:   Sat Jun 27 01:02:53 2026 +1000

    Handle signature_algorithms_cert extension in key-only context

    Servers or clients that configure only a private key in
    expectation of always negotiating use of RFC7250 raw public keys
    failed to handle the "signature_algorithms_cert" extension.

    The issue is now resolved and the RPK tests now check that
    key-only configurations are robust also when the extension
    is sent by the peer.

    Key-only configurations are quite uncommon.  As a best practice,
    RPK-capable servers and clients pair their private key with a
    (possibly self-signed) certificate, enabling fallback to X.509
    handshakes with non-RPK peers.

    Fixes CVE-2026-14457

    Reviewed-by: Eugene Syromiatnikov <esyr@openssl.org>
    Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
    Reviewed-by: Norbert Pocs <norbertp@openssl.org>
    Merge-date: Mon Aug 24 12:48:45 2026

diff --git a/ssl/t1_lib.c b/ssl/t1_lib.c
index 15a4e09591..f4b9b144b0 100644
--- a/ssl/t1_lib.c
+++ b/ssl/t1_lib.c
@@ -4641,6 +4641,20 @@ static int check_cert_usable(SSL_CONNECTION *s, const SIGALG_LOOKUP *sig,
     if (supported <= 0)
         return 0;

+    /*
+     * When RPK is negotiated there are no certificate signatures to
+     * constrain, and there may not even be a certificate configured.
+     */
+    if (TLSEXT_cert_type_rpk == (s->server ? s->ext.server_cert_type : s->ext.client_cert_type))
+        return 1;
+
+    /*
+     * RPK was enabled, adding candidate private-key-only slots, but was not
+     * negotiated, so the key-only slot is not usable.
+     */
+    if (x == NULL)
+        return 0;
+
     /*
      * The TLS 1.3 signature_algorithms_cert extension places restrictions
      * on the sigalg with which the certificate was signed (by its issuer).
diff --git a/test/rpktest.c b/test/rpktest.c
index 98be18b3a6..06a4ee3909 100644
--- a/test/rpktest.c
+++ b/test/rpktest.c
@@ -38,6 +38,37 @@ static OSSL_PROVIDER *defctxnull = NULL;
 static const unsigned char cert_type_rpk[] = { TLSEXT_cert_type_rpk, TLSEXT_cert_type_x509 };
 static const unsigned char SID_CTX[] = { 'r', 'p', 'k' };

+/*
+ * Wire form of a SignatureSchemeList that lists rsa_pkcs1_sha256
+ * and ed448 -- between them they cover the issuer signature on
+ * every cert this file loads from test/certs
+ * (sha256WithRSAEncryption for the RSA/ECDSA/Ed25519 leaves and
+ * ED448 for the Ed448 leaf), so the extension is harmless when
+ * the handshake is non-RPK and the server's check_cert_usable()
+ * has to walk the list against a real cert.  When RPK is
+ * negotiated check_cert_usable() returns early without inspecting
+ * the list, and when the slot is an RPK-listed key-only slot but
+ * X509 was negotiated check_cert_usable() returns 0 on the x ==
+ * NULL path -- the inevitable outcome, now discovered earlier.
+ *
+ * Payload: length, rsa_pkcs1_sha256, ed448
+ */
+static const unsigned char sigalgs_cert_payload[] = {
+    0x00, 0x04,
+    0x04, 0x01,
+    0x08, 0x08
+};
+
+static int sigalgs_cert_add_cb(SSL *s, unsigned int ext_type,
+    unsigned int context,
+    const unsigned char **out, size_t *outlen,
+    X509 *x, size_t chainidx, int *al, void *add_arg)
+{
+    *out = sigalgs_cert_payload;
+    *outlen = sizeof(sigalgs_cert_payload);
+    return 1;
+}
+
 static int rpk_verify_client_cb(int ok, X509_STORE_CTX *ctx)
 {
     int err = X509_STORE_CTX_get_error(ctx);
@@ -255,18 +286,43 @@ static int test_rpk(int idx)
     /* NEW */
     SSL_CTX_set_verify(cctx, SSL_VERIFY_PEER, rpk_verify_client_cb);

-    if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl,
-            NULL, NULL)))
+    /*
+     * Send signature_algorithms_cert in every ClientHello, and in
+     * every TLS 1.3 CertificateRequest.  The OpenSSL stack doesn't
+     * construct this extension by default in either direction, so
+     * register a custom add hook on both ends.  This exercises the
+     * three distinct paths through check_cert_usable() on whichever
+     * side receives the extension:
+     *   - RPK was negotiated for this side's cert -- early return 1,
+     *     list contents ignored.
+     *   - RPK was offered but X509 was negotiated and this side's
+     *     slot holds only a private key -- x == NULL, return 0
+     *     (any peer-sent signature_algorithms_cert against a key-only
+     *     slot would otherwise trigger a crash).
+     *   - X509 negotiated with a real cert -- walk the list, find
+     *     a match against the issuer's signature algorithm.
+     * The server's registration only fires on TLS 1.3 connections
+     * where the server requests a client certificate (case 2, 9,
+     * 10 etc.); on TLS 1.2 the sigalgs travel inside the
+     * CertificateRequest body, not as a separate extension.
+     */
+    if (!TEST_true(SSL_CTX_add_custom_ext(cctx,
+            TLSEXT_TYPE_signature_algorithms_cert,
+            SSL_EXT_CLIENT_HELLO,
+            sigalgs_cert_add_cb, NULL, NULL,
+            NULL, NULL))
+        || !TEST_true(SSL_CTX_add_custom_ext(sctx,
+            TLSEXT_TYPE_signature_algorithms_cert,
+            SSL_EXT_TLS1_3_CERTIFICATE_REQUEST,
+            sigalgs_cert_add_cb, NULL, NULL,
+            NULL, NULL))
+        || !TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl,
+            NULL, NULL))
+        || !TEST_int_gt(SSL_dane_enable(serverssl, NULL), 0)
+        || !TEST_int_gt(SSL_dane_enable(clientssl, "example.com"), 0)
+        || !TEST_int_eq(SSL_use_PrivateKey_file(serverssl, privkey_file, SSL_FILETYPE_PEM), 1))
         goto end;

-    if (!TEST_int_gt(SSL_dane_enable(serverssl, NULL), 0))
-        goto end;
-    if (!TEST_int_gt(SSL_dane_enable(clientssl, "example.com"), 0))
-        goto end;
-
-    /* Set private key and certificate */
-    if (!TEST_int_eq(SSL_use_PrivateKey_file(serverssl, privkey_file, SSL_FILETYPE_PEM), 1))
-        goto end;
     /* Only a private key */
     if (idx == 1) {
         if (idx_server_server_rpk == 0 || idx_client_server_rpk == 0) {