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