Commit c5c8b44f0f for openssl.org

commit c5c8b44f0f66f8edf5d6035705f15f3e55265798
Author: martin <rauch.martin@gmail.com>
Date:   Sun Oct 19 18:37:06 2025 +0200

    Fixed non-compliant handling of missing stapled OCSP responses

    If the OCSP response was not present for a certificate the server
    created a non-conforming empty CertificateStatus extension
    instead of not sending the extension at all.

    Fixes #28902

    Fixes b1b4b154

    Reviewed-by: Matt Caswell <matt@openssl.org>
    Reviewed-by: Tomas Mraz <tomas@openssl.org>
    (Merged from https://github.com/openssl/openssl/pull/28955)

diff --git a/apps/s_server.c b/apps/s_server.c
index 82590f9adb..52b4a60794 100644
--- a/apps/s_server.c
+++ b/apps/s_server.c
@@ -601,7 +601,7 @@ static int bring_ocsp_resp_in_correct_order(SSL *s, tlsextstatusctx *srctx,
                                             STACK_OF(OCSP_RESPONSE) *sk_resp_unordered,
                                             STACK_OF(OCSP_RESPONSE) **sk_resp)
 {
-    STACK_OF(X509) *server_certs = NULL;
+    STACK_OF(X509) *server_chain = NULL;
     X509 *ssl_cert = NULL;
     X509 *issuer = NULL;
     OCSP_RESPONSE *resp = NULL;
@@ -613,14 +613,14 @@ static int bring_ocsp_resp_in_correct_order(SSL *s, tlsextstatusctx *srctx,
     if (*sk_resp != NULL)
         sk_OCSP_RESPONSE_pop_free(*sk_resp, OCSP_RESPONSE_free);

-    SSL_get0_chain_certs(s, &server_certs);
+    SSL_get0_chain_certs(s, &server_chain);
     /*
      * TODO(DTLS-1.3): in future DTLS should also be considered
      */
-    if (server_certs != NULL && srctx->status_all &&
+    if (server_chain != NULL && srctx->status_all &&
         !SSL_is_dtls(s) && SSL_version(s) >= TLS1_3_VERSION) {
         /* certificate chain is available */
-        num = sk_X509_num(server_certs) + 1;
+        num = sk_X509_num(server_chain) + 1;
     }

     /* get OCSP response for server certificate first */
@@ -640,10 +640,21 @@ static int bring_ocsp_resp_in_correct_order(SSL *s, tlsextstatusctx *srctx,

     for (i = 0; i < num; i++) {
         if (i != 0) /* for each certificate in chain (except root) get the OCSP response */
-            ssl_cert = sk_X509_value(server_certs, i - 1);
+            ssl_cert = sk_X509_value(server_chain, i - 1);

         /* issuer certificate is next in chain */
-        issuer = sk_X509_value(server_certs, i);
+        issuer = sk_X509_value(server_chain, i);
+
+        /*
+         * in the case the root CA certificate is not included in the chain
+         * we assume that the last remaining response is issued by it
+         */
+        if (issuer == NULL && i == (num - 1) && sk_OCSP_RESPONSE_num(sk_resp_unordered) == 1) {
+            resp = sk_OCSP_RESPONSE_value(sk_resp_unordered, 0);
+            (void)sk_OCSP_RESPONSE_push(*sk_resp, resp);
+            sk_OCSP_RESPONSE_delete(sk_resp_unordered, 0);
+            continue;
+        }

         if (issuer == NULL
             || (cert_id = OCSP_cert_to_id(NULL, ssl_cert, issuer)) == NULL) {
@@ -752,7 +763,7 @@ static int get_ocsp_resp_from_responder(SSL *s, tlsextstatusctx *srctx,
 {
     X509 *ssl_cert = NULL;
     int i, num = 0;
-    STACK_OF(X509) *server_certs = NULL;
+    STACK_OF(X509) *server_chain = NULL;
     OCSP_RESPONSE *resp = NULL;

     if (*sk_resp != NULL) {
@@ -760,14 +771,15 @@ static int get_ocsp_resp_from_responder(SSL *s, tlsextstatusctx *srctx,
         *sk_resp = NULL;
     }

-    SSL_get0_chain_certs(s, &server_certs);
+    SSL_get0_chain_certs(s, &server_chain);
+
     /*
      * TODO(DTLS-1.3): in future DTLS should also be considered
      */
-    if (server_certs != NULL && srctx->status_all &&
+    if (server_chain != NULL && srctx->status_all &&
         !SSL_is_dtls(s) && SSL_version(s) >= TLS1_3_VERSION) {
         /* certificate chain is available */
-        num = sk_X509_num(server_certs) + 1;
+        num = sk_X509_num(server_chain) + 1;
     } else {
         /*
          * certificate chain is not available,
@@ -792,7 +804,7 @@ static int get_ocsp_resp_from_responder(SSL *s, tlsextstatusctx *srctx,
     /* for each certificate in chain (except root) get the OCSP response */
     for (i = 0; i < num; i++) {
         if (i != 0) /* get OCSP response for server certificate first */
-            ssl_cert = sk_X509_value(server_certs, i - 1);
+            ssl_cert = sk_X509_value(server_chain, i - 1);

         resp = NULL;
         if (get_ocsp_resp_from_responder_single(s, ssl_cert, srctx, &resp) != SSL_TLSEXT_ERR_OK)
diff --git a/ssl/ssl_local.h b/ssl/ssl_local.h
index 5cf7368a82..3af36274c9 100644
--- a/ssl/ssl_local.h
+++ b/ssl/ssl_local.h
@@ -2980,6 +2980,8 @@ __owur int srp_verify_server_param(SSL_CONNECTION *s);

 __owur int send_certificate_request(SSL_CONNECTION *s);

+OCSP_RESPONSE *ossl_get_ocsp_response(SSL_CONNECTION *s, int chainidx);
+
 /* statem/extensions_cust.c */

 custom_ext_method *custom_ext_find(const custom_ext_methods *exts,
diff --git a/ssl/statem/extensions_srvr.c b/ssl/statem/extensions_srvr.c
index ac2bddde3b..52508a4ce0 100644
--- a/ssl/statem/extensions_srvr.c
+++ b/ssl/statem/extensions_srvr.c
@@ -1753,6 +1753,8 @@ EXT_RETURN tls_construct_stoc_status_request(SSL_CONNECTION *s, WPACKET *pkt,
                                              unsigned int context, X509 *x,
                                              size_t chainidx)
 {
+    OCSP_RESPONSE *resp;
+
     /* We don't currently support this extension inside a CertificateRequest */
     if (context == SSL_EXT_TLS1_3_CERTIFICATE_REQUEST)
         return EXT_RETURN_NOT_SENT;
@@ -1760,6 +1762,13 @@ EXT_RETURN tls_construct_stoc_status_request(SSL_CONNECTION *s, WPACKET *pkt,
     if (!s->ext.status_expected)
         return EXT_RETURN_NOT_SENT;

+    /* Try to retrieve OCSP response for the actual certificate */
+    resp = ossl_get_ocsp_response(s, (int)chainidx);
+
+    /* If no OCSP response was found the extension is not sent */
+    if (resp == NULL)
+        return EXT_RETURN_NOT_SENT;
+
     if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_status_request)
             || !WPACKET_start_sub_packet_u16(pkt)) {
         SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
@@ -1772,7 +1781,7 @@ EXT_RETURN tls_construct_stoc_status_request(SSL_CONNECTION *s, WPACKET *pkt,
      * separate message
      */
     if (SSL_CONNECTION_IS_TLS13(s)
-        && !tls_construct_cert_status_body(s, chainidx, pkt)) {
+        && !tls_construct_cert_status_body(s, resp, pkt)) {
         /* SSLfatal() already called */
         return EXT_RETURN_FAIL;
     }
diff --git a/ssl/statem/statem_local.h b/ssl/statem/statem_local.h
index 48870683c3..1b7bab4521 100644
--- a/ssl/statem/statem_local.h
+++ b/ssl/statem/statem_local.h
@@ -168,7 +168,7 @@ __owur int ssl_do_client_cert_cb(SSL_CONNECTION *s, X509 **px509,
 __owur CON_FUNC_RETURN tls_construct_client_key_exchange(SSL_CONNECTION *s,
                                                          WPACKET *pkt);
 __owur int tls_client_key_exchange_post_work(SSL_CONNECTION *s);
-__owur int tls_construct_cert_status_body(SSL_CONNECTION *s, size_t chainidx, WPACKET *pkt);
+__owur int tls_construct_cert_status_body(SSL_CONNECTION *s, OCSP_RESPONSE *resp, WPACKET *pkt);
 __owur CON_FUNC_RETURN tls_construct_cert_status(SSL_CONNECTION *s,
                                                  WPACKET *pkt);
 __owur MSG_PROCESS_RETURN tls_process_key_exchange(SSL_CONNECTION *s,
diff --git a/ssl/statem/statem_srvr.c b/ssl/statem/statem_srvr.c
index 5b6465bc23..8658e78bba 100644
--- a/ssl/statem/statem_srvr.c
+++ b/ssl/statem/statem_srvr.c
@@ -17,6 +17,7 @@
 #include "internal/constant_time.h"
 #include "internal/cryptlib.h"
 #include "internal/ssl_unwrap.h"
+#include "internal/sizes.h"
 #include <openssl/buffer.h>
 #include <openssl/rand.h>
 #include <openssl/objects.h>
@@ -455,6 +456,148 @@ int send_certificate_request(SSL_CONNECTION *s)
     return 0;
 }

+/*
+ * Get the OCSP response for the certificate from the chain identified
+ * chainidx.
+ * If no OCSP response could be found NULL is returned.
+ */
+OCSP_RESPONSE *ossl_get_ocsp_response(SSL_CONNECTION *s, int chainidx)
+{
+    OCSP_RESPONSE *resp = NULL;
+#ifndef OPENSSL_NO_OCSP
+    int i = 0, num = 0;
+    unsigned int len;
+    X509 *x = NULL;
+    STACK_OF(X509) *chain_certs = NULL;
+    SSL *ssl = SSL_CONNECTION_GET_SSL(s);
+    OCSP_BASICRESP *bs = NULL;
+    OCSP_SINGLERESP *sr = NULL;
+    OCSP_CERTID *cid = NULL;
+    OCSP_CERTID *sr_cert_id = NULL;
+    ASN1_OBJECT *cert_id_md_oid;
+    char cert_id_md_txt[OSSL_MAX_NAME_SIZE];
+    EVP_MD *cert_id_md;
+    ASN1_INTEGER *respSerial;
+    ASN1_OCTET_STRING *respIssuerNameHash = NULL;
+    ASN1_OCTET_STRING *certIssuerNameHash = NULL;
+    const X509_NAME *certIssuerName;
+    unsigned char md[EVP_MAX_MD_SIZE];
+    const ASN1_INTEGER *certSerial;
+    SSL_CTX *sctx = SSL_CONNECTION_GET_CTX(s);
+
+    /*
+     * In TLSv1.3 the caller gives the index of the certificate for which the
+     * status message should be created.
+     * Prior to TLSv1.3 the chain index is 0 and the body should contain only
+     * the status of the server certificate itself.
+     */
+    SSL_get0_chain_certs(ssl, &chain_certs);
+
+    /*
+     * If the certificate chain was built, get the status message for the
+     * requested certificate specified by chainidx.
+     * SSL_get0_chain_certs provides certificate chain except the server cert.
+     *
+     * if chainidx = 0 the server certificate is requested
+     * if chainidx > 0 an intermediate certificate is requested
+     */
+    if (chainidx == 0)
+        x = SSL_get_certificate(ssl);
+    else
+        x = sk_X509_value(chain_certs, chainidx - 1);
+    if (x == NULL)
+        return NULL;
+
+    /* for a selfsigned certificate there will be no OCSP response */
+    if (X509_self_signed(x, 0))
+        return NULL;
+
+    if ((resp = sk_OCSP_RESPONSE_value(s->ext.ocsp.resp_ex, chainidx)) != NULL) {
+        /*
+         * Find the corresponding single OCSP response by comparing the current
+         * certificate's serial number, and the hash of the current certificate's
+         * issuer name, to the serial number and issuer name hash in each OCSP
+         * response received.
+         */
+        if (OCSP_response_status(resp) == OCSP_RESPONSE_STATUS_SUCCESSFUL) {
+            /*
+             * Set a mark for the error queue here to be able to ignore errors
+             * happening because of test cases.
+             */
+            ERR_set_mark();
+            if (((bs = OCSP_response_get1_basic(resp)) != NULL)
+                && ((sr = OCSP_resp_get0(bs, 0)) != NULL)) {
+                /* use the first single response to get the algorithm used */
+                cid = (OCSP_CERTID *)OCSP_SINGLERESP_get0_id(sr);
+
+                /* determine the md algorithm which was used to create cert id */
+                OCSP_id_get0_info(&respIssuerNameHash, &cert_id_md_oid, NULL, &respSerial, cid);
+                if (cert_id_md_oid != NULL) {
+                    OBJ_obj2txt(cert_id_md_txt, sizeof(cert_id_md_txt), cert_id_md_oid, 0);
+                    cert_id_md = EVP_MD_fetch(sctx->libctx, cert_id_md_txt, sctx->propq);
+                } else {
+                    cert_id_md = EVP_MD_fetch(sctx->libctx, SN_sha1, sctx->propq);
+                }
+
+                if (cert_id_md == NULL) {
+                    OCSP_BASICRESP_free(bs);
+                    ERR_clear_last_mark();
+                    return NULL;
+                }
+
+                /* get serial number and issuer name hash of the certificate from the chain */
+                certSerial = X509_get0_serialNumber(x);
+                certIssuerName = X509_get_issuer_name(x);
+                certIssuerNameHash = ASN1_OCTET_STRING_new();
+                if (!X509_NAME_digest(certIssuerName, cert_id_md, md, &len) ||
+                    !(ASN1_OCTET_STRING_set(certIssuerNameHash, md, len))) {
+                    ASN1_OCTET_STRING_free(certIssuerNameHash);
+                    OCSP_BASICRESP_free(bs);
+                    EVP_MD_free(cert_id_md);
+                    ERR_clear_last_mark();
+                    return NULL;
+                }
+
+                num = OCSP_resp_count(bs);
+                for (i = 0; i < num; i++) {
+                    sr = OCSP_resp_get0(bs, i);
+
+                    /*
+                     * get the CertID from the OCSP response to compare it with the information
+                     * from the certificate
+                     */
+                    sr_cert_id = (OCSP_CERTID *)OCSP_SINGLERESP_get0_id(sr);
+
+                    OCSP_id_get0_info(&respIssuerNameHash, NULL, NULL, &respSerial, sr_cert_id);
+
+                    if (!ASN1_INTEGER_cmp(certSerial, respSerial) &&
+                        !ASN1_OCTET_STRING_cmp(certIssuerNameHash, respIssuerNameHash))
+                        break;
+                }
+
+                ASN1_OCTET_STRING_free(certIssuerNameHash);
+                OCSP_BASICRESP_free(bs);
+                EVP_MD_free(cert_id_md);
+
+                /*
+                 * if we did not find the right single response we return NULL here
+                 */
+                if (i == num)
+                    resp = NULL;
+            }
+
+            /*
+             * in a test case a response without a basic response is used the error set
+             * could be ignored here
+             */
+            ERR_pop_to_mark();
+        }
+    }
+#endif
+
+    return resp;
+}
+
 static int do_compressed_cert(SSL_CONNECTION *sc)
 {
     /* If we negotiated RPK, we won't attempt to compress it */
@@ -4333,30 +4476,10 @@ CON_FUNC_RETURN tls_construct_new_session_ticket(SSL_CONNECTION *s, WPACKET *pkt
  * In TLSv1.3 this is called from the extensions code, otherwise it is used to
  * create a separate message. Returns 1 on success or 0 on failure.
  */
-int tls_construct_cert_status_body(SSL_CONNECTION *s, size_t chainidx, WPACKET *pkt)
+int tls_construct_cert_status_body(SSL_CONNECTION *s, OCSP_RESPONSE *resp, WPACKET *pkt)
 {
     unsigned char *respder = NULL;
     int resplen = 0;
-#ifndef OPENSSL_NO_OCSP
-    int i = 0, num = 0;
-    unsigned int len;
-    X509 *x = NULL;
-    STACK_OF(X509) *chain_certs = NULL;
-    SSL *ssl = SSL_CONNECTION_GET_SSL(s);
-    OCSP_RESPONSE *resp = NULL;
-    OCSP_BASICRESP *bs = NULL;
-    OCSP_SINGLERESP *sr = NULL;
-    OCSP_CERTID *cid = NULL;
-    OCSP_CERTID *sr_cert_id = NULL;
-    ASN1_OBJECT *cert_id_md_oid;
-    const EVP_MD *cert_id_md;
-    ASN1_INTEGER *respSerial;
-    ASN1_OCTET_STRING *respIssuerNameHash;
-    ASN1_OCTET_STRING *certIssuerNameHash;
-    const X509_NAME *certIssuerName;
-    unsigned char md[EVP_MAX_MD_SIZE];
-    const ASN1_INTEGER *certSerial;
-#endif

     if (!WPACKET_put_bytes_u8(pkt, s->ext.status_type)) {
         SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
@@ -4364,103 +4487,7 @@ int tls_construct_cert_status_body(SSL_CONNECTION *s, size_t chainidx, WPACKET *
     }

 #ifndef OPENSSL_NO_OCSP
-    /*
-     * In TLSv1.3 the caller gives the index of the certificate for which the
-     * status message should be created.
-     * Prior to TLSv1.3 the chain index is 0 and the body should contain only
-     * the status of the server certificate itself.
-     */
-    SSL_get0_chain_certs(ssl, &chain_certs);
-
-    /*
-     * if the certificate chain was built, get the status message for the
-     * requested certificate specified by chainidx  SSL_get0_chain_certs
-     * contains certificate chain except the server cert
-     *
-     * if chainidx = 0 the server certificate is requested
-     * if chainidx > 0 an intermediate certificate is requested
-     */
-    if (chain_certs != NULL && (int)chainidx <= sk_X509_num(chain_certs) && chainidx > 0)
-        x = sk_X509_value(chain_certs, (int)chainidx - 1);
-    else
-        x = SSL_get_certificate(ssl);
-    if (x == NULL)
-        return 0;
-
-    /* for a selfsigned certificate there will be no OCSP response */
-    if (X509_self_signed(x, 0))
-        return 1;
-
-    if ((resp = sk_OCSP_RESPONSE_value(s->ext.ocsp.resp_ex, (int)chainidx)) != NULL) {
-        /*
-         * check if its the right response in the case it is a successful response
-         * as not every time the issuer certificate is available the check just
-         * uses the issuer name and the serial number from the current certificate
-         */
-        if (OCSP_response_status(resp) == OCSP_RESPONSE_STATUS_SUCCESSFUL) {
-            /*
-             * set a mark for the error queue her to be able to ignore errors
-             * happening because of test cases
-             */
-            ERR_set_mark();
-            if (((bs = OCSP_response_get1_basic(resp)) != NULL)
-                && ((sr = OCSP_resp_get0(bs, 0)) != NULL)) {
-                /* use the first single response to get the algorithm used */
-                cid = (OCSP_CERTID *)OCSP_SINGLERESP_get0_id(sr);
-
-                OCSP_id_get0_info(&respIssuerNameHash, &cert_id_md_oid, NULL, &respSerial, cid);
-                if (cert_id_md_oid != NULL)
-                    cert_id_md = EVP_get_digestbyobj(cert_id_md_oid);
-                else
-                    cert_id_md = EVP_sha1();
-
-                /* get serial number and issuer name hash of the certificate from the chain */
-                certSerial = X509_get0_serialNumber(x);
-                certIssuerName = X509_get_issuer_name(x);
-                certIssuerNameHash = ASN1_OCTET_STRING_new();
-                if (!X509_NAME_digest(certIssuerName, cert_id_md, md, &len) ||
-                    !(ASN1_OCTET_STRING_set(certIssuerNameHash, md, len))) {
-                    ASN1_OCTET_STRING_free(certIssuerNameHash);
-                    OCSP_BASICRESP_free(bs);
-                    ERR_clear_last_mark();
-                    return 0;
-                }
-
-                num = OCSP_resp_count(bs);
-                for (i = 0; i < num; i++) {
-                    sr = OCSP_resp_get0(bs, i);
-
-                    /* determine the md algorithm which was used to create cert id */
-                    sr_cert_id = (OCSP_CERTID *)OCSP_SINGLERESP_get0_id(sr);
-
-                    OCSP_id_get0_info(&respIssuerNameHash, NULL, NULL, &respSerial, sr_cert_id);
-
-                    if (!ASN1_INTEGER_cmp(certSerial, respSerial) &&
-                        !ASN1_OCTET_STRING_cmp(certIssuerNameHash, respIssuerNameHash))
-                        break;
-                }
-
-                ASN1_OCTET_STRING_free(certIssuerNameHash);
-                OCSP_BASICRESP_free(bs);
-
-                /*
-                 * if we did not find the right single response in the OCSP response we
-                 * construct an empty message
-                 */
-                if (i == num)
-                    resp = NULL;
-            }
-
-            /*
-             * in a test case a response without a basic response is used the error set
-             * could be ignored here
-             */
-            ERR_pop_to_mark();
-        }
-    }
-
-    if (resp != NULL)
-        resplen = i2d_OCSP_RESPONSE(resp, &respder);
+    resplen = i2d_OCSP_RESPONSE(resp, &respder);
 #endif

     if (!WPACKET_sub_memcpy_u24(pkt, respder, resplen)) {
@@ -4475,7 +4502,14 @@ int tls_construct_cert_status_body(SSL_CONNECTION *s, size_t chainidx, WPACKET *

 CON_FUNC_RETURN tls_construct_cert_status(SSL_CONNECTION *s, WPACKET *pkt)
 {
-    if (!tls_construct_cert_status_body(s, 0, pkt)) {
+    OCSP_RESPONSE *resp;
+
+    resp = ossl_get_ocsp_response(s, 0);
+
+    if (resp == NULL)
+        return CON_FUNC_DONT_SEND;
+
+    if (!tls_construct_cert_status_body(s, resp, pkt)) {
         /* SSLfatal() already called */
         return CON_FUNC_ERROR;
     }
diff --git a/test/recipes/ocsp-response.der b/test/recipes/ocsp-response.der
index 31351a0e3c..c3e1955756 100644
Binary files a/test/recipes/ocsp-response.der and b/test/recipes/ocsp-response.der differ