Thanks a lot Jacob for helping update the tests and sorry for the late reply.

Based on previous discussion, I remove the document patch, and start to focus on the v1 simple OCSP logic by checking the leaf/Postgres server certificate's status only (0001-v1-WIP-OCSP-support-certificate-status-check.patch). I also merge your changes and simplify the test by testing the Postgres server certificate's status only (0002-v1-WIP-OCSP-simplify-.res-generation-and-regress-tes.patch).

On 2024-07-18 10:18 a.m., Jacob Champion wrote:
A question from ignorance: how does the client decide that the
signature on the OCSP response is actually valid for the specific
chain in use?
If I understand correctly, the certificate used by the OCSP responder to
sign the OCSP response must be valid for the specific chain in use, or
the admins allow to load a new chain to validate the certificate used to
sign the OCSP response. I think it would be better to make this part to
be more open.
Okay. That part needs more design work IMO, and solid testing.
Based on the RFC here, https://datatracker.ietf.org/doc/html/rfc6960#section-4.2.2.2. My understanding is that the OCSP responder's certificate should be directly signed by the same CA which signed the Postgres Server's certificate. It looks the openssl 3.0.14 implements in this way. In other words, it the OCSP responder's certificate is signed by a different branch of the chain, then openssl will report some errors. Unless the end user explicitly provides the OCSP responder's certificate with trust_other option. In this case it will skip the some certificate verification. I think it should be simple enough for v1 by set `OCSP_basic_verify(basic_resp, NULL, trusted_store, 0)`.  The key function OCSP_basic_verify is documented at here, https://docs.openssl.org/3.0/man3/OCSP_resp_find_status/



Thank you,
David
From 368c77885d7925334e8dabcce655b6a82f0a9a8f Mon Sep 17 00:00:00 2001
From: David Zhang <idraw...@gmail.com>
Date: Tue, 6 Aug 2024 15:38:14 -0700
Subject: [PATCH 2/2] v1 WIP OCSP simplify .res generation and regress test

---
 src/test/ssl/conf/ocsp_ca.config      |  39 ++++++++++
 src/test/ssl/sslfiles.mk              |  59 +++++++++++++--
 src/test/ssl/t/001_ssltests.pl        | 100 ++++++++++++++++++++++++++
 src/test/ssl/t/SSL/Backend/OpenSSL.pm |   3 +
 src/test/ssl/t/SSL/Server.pm          |   4 ++
 5 files changed, 201 insertions(+), 4 deletions(-)
 create mode 100644 src/test/ssl/conf/ocsp_ca.config

diff --git a/src/test/ssl/conf/ocsp_ca.config b/src/test/ssl/conf/ocsp_ca.config
new file mode 100644
index 0000000000..04f78bacb8
--- /dev/null
+++ b/src/test/ssl/conf/ocsp_ca.config
@@ -0,0 +1,39 @@
+# An OpenSSL format CSR config file for creating the ocsp responder 
certificate.
+# This configuration file is also used when operating the CA.
+#
+# This certificate is used to sign OCSP resonpose.
+
+[ req ]
+distinguished_name     = req_distinguished_name
+prompt                 = no
+req_extensions         = v3_ocsp
+
+[ req_distinguished_name ]
+CN                     = Test CA for PostgreSQL SSL regression test ocsp 
response
+
+# Extensions for OCSP responder certs
+[ v3_ocsp ]
+basicConstraints = CA:false
+keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+extendedKeyUsage = OCSPSigning
+
+[ ca ]
+default_ca = ocsp
+
+# A shell of a CA, mostly duplicating the server CA, which is used only during
+# the OCSP index generation recipes.
+[ ocsp ]
+dir = ./ssl/
+
+# The database (or "index") is the main thing we want.
+database = ./ssl/ocsp-certindex
+
+# Everything else should all be unused, so we specify whatever's most
+# convenient. In particular there's no need to have a unique cert/key pair for
+# this.
+certificate = ./ssl/server_ca.crt
+private_key = ./ssl/server_ca.key
+serial = ./ssl/ocsp_ca.srl
+default_md = sha256
+policy = policy_match
+
diff --git a/src/test/ssl/sslfiles.mk b/src/test/ssl/sslfiles.mk
index 88c93ec18d..6f314b7153 100644
--- a/src/test/ssl/sslfiles.mk
+++ b/src/test/ssl/sslfiles.mk
@@ -35,6 +35,10 @@ SERVERS := server-cn-and-alt-names \
        server-revoked
 CLIENTS := client client-dn client-revoked client_ext client-long \
        client-revoked-utf8
+OCSPS := server-ocsp-good \
+       server-ocsp-revoked \
+       server-ocsp-expired \
+       server-ocsp-unknown
 
 #
 # To add a new non-standard certificate, add it to SPECIAL_CERTS and then add
@@ -64,12 +68,13 @@ COMBINATIONS := \
        ssl/client+client_ca.crt \
        ssl/server-cn-only+server_ca.crt
 
-CERTIFICATES := root_ca server_ca client_ca $(SERVERS) $(CLIENTS)
+CERTIFICATES := root_ca server_ca client_ca ocsp_ca $(SERVERS) $(CLIENTS)
 STANDARD_CERTS := $(CERTIFICATES:%=ssl/%.crt)
 STANDARD_KEYS := $(CERTIFICATES:%=ssl/%.key)
 CRLS := ssl/root.crl \
        ssl/client.crl \
        ssl/server.crl
+OCSPRES := $(OCSPS:%=ssl/%.res)
 
 SSLFILES := \
        $(STANDARD_CERTS) \
@@ -77,7 +82,8 @@ SSLFILES := \
        $(SPECIAL_CERTS) \
        $(SPECIAL_KEYS) \
        $(COMBINATIONS) \
-       $(CRLS)
+       $(CRLS) \
+       $(OCSPRES)
 SSLDIRS := ssl/client-crldir \
        ssl/server-crldir \
        ssl/root+client-crldir \
@@ -175,13 +181,14 @@ $(STANDARD_KEYS):
 #
 
 CA_CERTS     := ssl/server_ca.crt ssl/client_ca.crt
-SERVER_CERTS := $(SERVERS:%=ssl/%.crt)
+SERVER_CERTS := $(SERVERS:%=ssl/%.crt) ssl/ocsp_ca.crt
 CLIENT_CERTS := $(CLIENTS:%=ssl/%.crt)
 
 # See the "CA State" section below.
 root_ca_state_files := ssl/root_ca-certindex ssl/root_ca-certindex.attr 
ssl/root_ca.srl
 server_ca_state_files := ssl/server_ca-certindex ssl/server_ca-certindex.attr 
ssl/server_ca.srl
 client_ca_state_files := ssl/client_ca-certindex ssl/client_ca-certindex.attr 
ssl/client_ca.srl
+ocsp_ca_state_files := ssl/ocsp-certindex ssl/ocsp-certindex.attr 
ssl/ocsp_ca.srl
 
 # These are the workhorse recipes. `openssl ca` can't be safely run from
 # parallel processes, so we must mark the entire Makefile .NOTPARALLEL.
@@ -210,6 +217,7 @@ ssl/%.csr: ssl/%.key conf/%.config
 #
 
 .INTERMEDIATE: $(root_ca_state_files) $(server_ca_state_files) 
$(client_ca_state_files)
+.INTERMEDIATE: $(ocsp_ca_state_files)
 
 # OpenSSL requires a directory to put all generated certificates in. We don't
 # use this for anything, but we need a location.
@@ -227,6 +235,49 @@ ssl/%-certindex.attr:
 ssl/%.srl:
        date +%Y%m%d%H%M%S00 > $@
 
+#
+# OCSP
+#
+.INTERMEDIATE: $(OCSPS:%=ssl/%.idx)
+
+# to generate an ocsp response with status 'good'
+ssl/server-ocsp-good.idx: conf/ocsp_ca.config ssl/server-cn-only.crt | 
$(ocsp_ca_state_files)
+       : > ssl/ocsp-certindex
+       openssl ca -config conf/ocsp_ca.config -valid ssl/server-cn-only.crt
+       cp ssl/ocsp-certindex $@
+
+# to generate an ocsp response with status 'revoked'
+ssl/server-ocsp-revoked.idx: conf/ocsp_ca.config ssl/server-cn-only.crt | 
$(ocsp_ca_state_files)
+       : > ssl/ocsp-certindex
+       openssl ca -config conf/ocsp_ca.config -revoke ssl/server-cn-only.crt
+       cp ssl/ocsp-certindex $@
+
+# to generate an ocsp response with status 'unknown'
+ssl/server-ocsp-unknown.idx:
+       touch $@
+
+# to generate an ocsp response with status 'good' but nextUpdate 'expired' in 
1 minute
+ssl/server-ocsp-expired.idx: ssl/server-ocsp-good.idx
+       cp $< $@
+
+# All of the responses have the server cert in the chain.
+OCSPCHAIN = -issuer ssl/server_ca.crt -cert ssl/server-cn-only.crt
+$(OCSPRES): ssl/server_ca.crt ssl/server-cn-only.crt
+
+# Additionally, the server CA is part of the server-ca-* responses.
+ssl/server-ca-%.res: OCSPCHAIN += -issuer ssl/root_ca.crt -cert 
ssl/server_ca.crt
+ssl/server-ca-%.res: ssl/root_ca.crt
+
+# Most responses should "never" expire, except the ones being explicitly tested
+# for expiration.
+OCSPEXP = -ndays 10000
+ssl/%-ocsp-expired.res: OCSPEXP = -nmin 1
+
+$(OCSPRES): ssl/%.res: ssl/%.idx ssl/ocsp_ca.crt ssl/ocsp_ca.key 
ssl/root+server_ca.crt
+       $(OPENSSL) ocsp -index $< -respout $@ -rsigner ssl/ocsp_ca.crt \
+               -rkey ssl/ocsp_ca.key -CA ssl/root+server_ca.crt \
+               $(OCSPEXP) $(OCSPCHAIN)
+
 #
 # CRLs
 #
@@ -262,7 +313,7 @@ ssl/%-crldir:
 
 .PHONY: sslfiles-clean
 sslfiles-clean:
-       rm -f $(SSLFILES) ssl/*.old ssl/*.csr ssl/*.srl ssl/*-certindex*
+       rm -f $(SSLFILES) ssl/*.old ssl/*.csr ssl/*.srl ssl/*-certindex* 
ssl/*.idx ssl/*.res
        rm -rf $(SSLDIRS) ssl/new_certs_dir
 
 # The difference between the below clean targets and sslfiles-clean is that the
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index b877327023..39eace0c7f 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -905,4 +905,104 @@ $node->connect_fails(
        #       ]
 );
 
+# Test ocsp stapling.
+# Use a stapled ocsp response with different status for certificate 
server-cn-only
+# so that the client can verify the ocsp response when sslocspstapling is set.
+# server-cn-only certificates status is 'good'
+switch_server_cert(
+       $node,
+       certfile => 'server-cn-only',
+       keyfile => 'server-cn-only',
+       cafile => 'root_ca',
+       ocspfile => 'server-ocsp-good');
+
+# Reset the common_connstr
+$common_connstr =
+  "$default_ssl_connstr user=ssltestuser dbname=trustdb 
hostaddr=$SERVERHOSTADDR host=ocsp-good.pg-ssltest.test";
+
+# Continue the TLS connection when certificate status is 'good' in stapled 
ocsp response.
+$node->connect_ok(
+       "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca 
sslocspstapling=1",
+       "connect with valid stapled ocsp response when sslocspstapling=1");
+
+# Continue the TLS connection when no ocsp response is required.
+$node->connect_ok(
+       "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca 
sslocspstapling=0",
+       "connect without requesting ocsp response when sslocspstapling=0");
+
+# server-cn-only certificates status is 'revoked'
+switch_server_cert(
+       $node,
+       certfile => 'server-cn-only',
+       keyfile => 'server-cn-only',
+       cafile => 'root_ca',
+       ocspfile => 'server-ocsp-revoked');
+
+# Fail the TLS connection when certificate status is 'revoked' in stapled ocsp 
response.
+$node->connect_fails(
+       "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca 
sslocspstapling=1",
+       "failed with a revoked ocsp response when sslocspstapling=1");
+
+# Continue the TLS connection when no ocsp response is required.
+$node->connect_ok(
+       "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca 
sslocspstapling=0",
+       "connect without requesting ocsp response when sslocspstapling=0");
+
+# server-cn-only certificates status is 'unknown'
+switch_server_cert(
+       $node,
+       certfile => 'server-cn-only',
+       keyfile => 'server-cn-only',
+       cafile => 'root_ca',
+       ocspfile => 'server-ocsp-unknown');
+
+# Fail the TLS connection when certificate status is 'unknown' in stapled ocsp 
response.
+$node->connect_fails(
+       "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca 
sslocspstapling=1",
+       "failed with a revoked ocsp response when sslocspstapling=1");
+
+# Continue the TLS connection when no ocsp response is required.
+$node->connect_ok(
+       "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca 
sslocspstapling=0",
+       "connect without requesting ocsp response when sslocspstapling=0");
+
+# server-cn-only certificates status is 'expired'
+switch_server_cert(
+       $node,
+       certfile => 'server-cn-only',
+       keyfile => 'server-cn-only',
+       cafile => 'root_ca',
+       ocspfile => 'server-ocsp-expired');
+
+# Fail the TLS connection when stapled ocsp response is 'expired'.
+$node->connect_fails(
+       "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca 
sslocspstapling=1",
+       "failed with an expired ocsp response when sslocspstapling=1");
+
+# Continue the TLS connection when no ocsp response is required.
+$node->connect_ok(
+       "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca 
sslocspstapling=0",
+       "connect without requesting ocsp response when sslocspstapling=0");
+
+
+# Use a stapled ocsp response which doesn't match the certificate 
server-ip-cn-only
+# so that the client will fail the TLS connection when sslocspstapling is set.
+switch_server_cert(
+       $node,
+       certfile => 'server-ip-cn-only',
+       keyfile => 'server-ip-cn-only',
+       cafile => 'root_ca',
+       ocspfile => 'server-ocsp-good');
+
+# Fail the TLS connection when stapled ocsp response doesn't match certificate.
+$node->connect_fails(
+       "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca 
sslocspstapling=1",
+       "failed with an expired ocsp response when sslocspstapling=1");
+
+# Continue the TLS connection when no ocsp response is required.
+$node->connect_ok(
+       "$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca 
sslocspstapling=0",
+       "connect without requesting ocsp response when sslocspstapling=0");
+
+
 done_testing();
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm 
b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
index 410b4b1a3f..3f4c1a0fe4 100644
--- a/src/test/ssl/t/SSL/Backend/OpenSSL.pm
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -73,6 +73,7 @@ sub init
        _copy_files("ssl/root+client_ca.crt", $pgdata);
        _copy_files("ssl/root_ca.crt", $pgdata);
        _copy_files("ssl/root+client.crl", $pgdata);
+       _copy_files("ssl/server-*.res", $pgdata);
        mkdir("$pgdata/root+client-crldir")
          or die "unable to create server CRL dir $pgdata/root+client-crldir: 
$!";
        _copy_files("ssl/root+client-crldir/*", "$pgdata/root+client-crldir/");
@@ -186,6 +187,8 @@ sub set_server_cert
          . "ssl_crl_file='$params->{crlfile}'\n";
        $sslconf .= "ssl_crl_dir='$params->{crldir}'\n"
          if defined $params->{crldir};
+       $sslconf .= "ssl_ocsp_file='$params->{ocspfile}.res'\n"
+         if defined $params->{ocspfile};
 
        return $sslconf;
 }
diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm
index 021eec74ab..97d49236ec 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -261,6 +261,10 @@ The CA certificate to use. Implementation is SSL backend 
specific.
 
 The certificate file to use. Implementation is SSL backend specific.
 
+=item ocspfile => B<value>
+
+The ocsp stapling file to use. Implementation is SSL backend specific.
+
 =item keyfile => B<value>
 
 The private key file to use. Implementation is SSL backend specific.
-- 
2.34.1

From 0fa54f2517c5958f054eff99a30ebd9b41a28bac Mon Sep 17 00:00:00 2001
From: David Zhang <idraw...@gmail.com>
Date: Tue, 6 Aug 2024 15:35:45 -0700
Subject: [PATCH 1/2] v1 WIP OCSP support certificate status check

---
 src/backend/libpq/be-secure-openssl.c         |  87 ++++++++++
 src/backend/libpq/be-secure.c                 |   1 +
 src/backend/utils/misc/guc_tables.c           |  10 ++
 src/backend/utils/misc/postgresql.conf.sample |   1 +
 src/include/libpq/libpq.h                     |   1 +
 src/interfaces/libpq/fe-connect.c             |  37 +++++
 src/interfaces/libpq/fe-secure-openssl.c      | 153 ++++++++++++++++++
 src/interfaces/libpq/libpq-int.h              |   1 +
 8 files changed, 291 insertions(+)

diff --git a/src/backend/libpq/be-secure-openssl.c 
b/src/backend/libpq/be-secure-openssl.c
index 7e056abd5a..efd242591c 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -50,6 +50,7 @@
 #include <openssl/ec.h>
 #endif
 #include <openssl/x509v3.h>
+#include <openssl/ocsp.h>
 
 
 /* default init hook can be overridden by a shared library */
@@ -87,6 +88,8 @@ static bool ssl_is_server_start;
 static int     ssl_protocol_version_to_openssl(int v);
 static const char *ssl_protocol_version_to_string(int v);
 
+static int ocsp_stapling_cb(SSL *ssl);
+
 /* for passing data back from verify_cb() */
 static const char *cert_errdetail;
 
@@ -453,6 +456,9 @@ be_tls_open_server(Port *port)
                return -1;
        }
 
+       /* set up OCSP stapling callback */
+       SSL_CTX_set_tlsext_status_cb(SSL_context, ocsp_stapling_cb);
+
        /* set up debugging/info callback */
        SSL_CTX_set_info_callback(SSL_context, info_cb);
 
@@ -1768,3 +1774,84 @@ default_openssl_tls_init(SSL_CTX *context, bool 
isServerStart)
                        SSL_CTX_set_default_passwd_cb(context, 
dummy_ssl_passwd_cb);
        }
 }
+
+/*
+ * OCSP stapling callback function for the server side.
+ *
+ * This function is responsible for providing the OCSP stapling response to
+ * the client during the SSL/TLS handshake, based on the client's request.
+ *
+ * Parameters:
+ *   - ssl: SSL/TLS connection object.
+ *
+ * Returns:
+ *   - SSL_TLSEXT_ERR_OK: OCSP stapling response successfully provided.
+ *   - SSL_TLSEXT_ERR_NOACK: OCSP stapling response not provided due to errors.
+ *
+ * Steps:
+ *   1. Check if the server-side OCSP stapling feature is enabled.
+ *   2. Read OCSP response from file if client requested OCSP stapling.
+ *   3. Set the OCSP stapling response in the SSL/TLS connection.
+ */
+static int ocsp_stapling_cb(SSL *ssl)
+{
+       int                             resp_len = -1;
+       BIO                             *bio = NULL;
+       OCSP_RESPONSE   *resp = NULL;
+       unsigned char   *rspder = NULL;
+
+       /* return, if ssl_ocsp_file not enabled on server */
+       if (ssl_ocsp_file == NULL)
+       {
+               ereport(WARNING,
+                               (errcode(ERRCODE_CONFIG_FILE_ERROR),
+                                errmsg("could not find ssl_ocsp_file")));
+               return SSL_TLSEXT_ERR_NOACK;
+       }
+
+       /* whether the client requested OCSP stapling */
+       if (SSL_get_tlsext_status_type(ssl) == TLSEXT_STATUSTYPE_ocsp)
+       {
+               bio = BIO_new_file(ssl_ocsp_file, "r");
+               if (bio == NULL)
+               {
+                       ereport(WARNING,
+                                       (errcode(ERRCODE_CONFIG_FILE_ERROR),
+                                        errmsg("could not read 
ssl_ocsp_file")));
+                       return SSL_TLSEXT_ERR_NOACK;
+               }
+
+               resp = d2i_OCSP_RESPONSE_bio(bio, NULL);
+               BIO_free(bio);
+               if (resp == NULL)
+               {
+                       ereport(WARNING,
+                                       (errcode(ERRCODE_PROTOCOL_VIOLATION),
+                                        errmsg("could not convert OCSP 
response to intarnal format")));
+                       return SSL_TLSEXT_ERR_NOACK;
+               }
+
+               resp_len = i2d_OCSP_RESPONSE(resp, &rspder);
+               OCSP_RESPONSE_free(resp);
+               if (resp_len <= 0)
+               {
+                       ereport(WARNING,
+                                       (errcode(ERRCODE_PROTOCOL_VIOLATION),
+                                        errmsg("could not convert OCSP 
response to der format")));
+                       return SSL_TLSEXT_ERR_NOACK;
+               }
+
+               /* set up the OCSP stapling response */
+               if (SSL_set_tlsext_status_ocsp_resp(ssl, rspder, resp_len) != 1)
+               {
+                       ereport(WARNING,
+                                       (errcode(ERRCODE_PROTOCOL_VIOLATION),
+                                        errmsg("could not set up OCSP stapling 
response")));
+                       return SSL_TLSEXT_ERR_NOACK;
+               }
+
+               return SSL_TLSEXT_ERR_OK;
+       }
+
+       return SSL_TLSEXT_ERR_NOACK;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index ef20ea755b..d80e52f26d 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -35,6 +35,7 @@
 
 char      *ssl_library;
 char      *ssl_cert_file;
+char      *ssl_ocsp_file;
 char      *ssl_key_file;
 char      *ssl_ca_file;
 char      *ssl_crl_file;
diff --git a/src/backend/utils/misc/guc_tables.c 
b/src/backend/utils/misc/guc_tables.c
index c0a52cdcc3..4b499dd70b 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -4616,6 +4616,16 @@ struct config_string ConfigureNamesString[] =
                NULL, NULL, NULL
        },
 
+       {
+               {"ssl_ocsp_file", PGC_SIGHUP, CONN_AUTH_SSL,
+                       gettext_noop("Location of the SSL certificate OCSP 
stapling file."),
+                       NULL
+               },
+               &ssl_ocsp_file,
+               "",
+               NULL, NULL, NULL
+       },
+
        {
                {"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
                        gettext_noop("Number of synchronous standbys and list 
of names of potential synchronous ones."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample 
b/src/backend/utils/misc/postgresql.conf.sample
index 9ec9f97e92..7454564bd7 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -107,6 +107,7 @@
 #ssl = off
 #ssl_ca_file = ''
 #ssl_cert_file = 'server.crt'
+#ssl_ocsp_file = 'server.res'
 #ssl_crl_file = ''
 #ssl_crl_dir = ''
 #ssl_key_file = 'server.key'
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 142c98462e..67be66d711 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -88,6 +88,7 @@ extern bool pq_check_connection(void);
  */
 extern PGDLLIMPORT char *ssl_library;
 extern PGDLLIMPORT char *ssl_cert_file;
+extern PGDLLIMPORT char *ssl_ocsp_file;
 extern PGDLLIMPORT char *ssl_key_file;
 extern PGDLLIMPORT char *ssl_ca_file;
 extern PGDLLIMPORT char *ssl_crl_file;
diff --git a/src/interfaces/libpq/fe-connect.c 
b/src/interfaces/libpq/fe-connect.c
index 360d9a4547..35b9e49978 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -329,6 +329,10 @@ static const internalPQconninfoOption PQconninfoOptions[] 
= {
                "SSL-Maximum-Protocol-Version", "", 8,  /* sizeof("TLSv1.x") == 
8 */
        offsetof(struct pg_conn, ssl_max_protocol_version)},
 
+       {"sslocspstapling", "PGSSLOCSPSTAPLING", "0", NULL,
+               "SSL-OCSP-Stapling", "", 1,
+       offsetof(struct pg_conn, sslocspstapling)},
+
        /*
         * As with SSL, all GSS options are exposed even in builds that don't 
have
         * support.
@@ -449,6 +453,7 @@ static void pgpassfileWarning(PGconn *conn);
 static void default_threadlock(int acquire);
 static bool sslVerifyProtocolVersion(const char *version);
 static bool sslVerifyProtocolRange(const char *min, const char *max);
+static bool sslVerifyOcspStapling(const char *stapling);
 
 
 /* global variable because fe-auth.c needs to access it */
@@ -1686,6 +1691,18 @@ pqConnectOptions2(PGconn *conn)
                return false;
        }
 
+       /*
+        * Validate sslocspstapling settings
+        */
+       if (!sslVerifyOcspStapling(conn->sslocspstapling))
+       {
+               conn->status = CONNECTION_BAD;
+               libpq_append_conn_error(conn, "invalid %s value: \"%s\"",
+                                                               
"sslocspstapling",
+                                                               
conn->sslocspstapling);
+               return false;
+       }
+
        /*
         * validate sslcertmode option
         */
@@ -4704,6 +4721,7 @@ freePGconn(PGconn *conn)
        free(conn->require_auth);
        free(conn->ssl_min_protocol_version);
        free(conn->ssl_max_protocol_version);
+       free(conn->sslocspstapling);
        free(conn->gssencmode);
        free(conn->krbsrvname);
        free(conn->gsslib);
@@ -7656,6 +7674,25 @@ sslVerifyProtocolRange(const char *min, const char *max)
        return true;
 }
 
+/*
+ * Check sslocspstapling is set properly
+ */
+static bool
+sslVerifyOcspStapling(const char *stapling)
+{
+       /*
+        * An empty string or a NULL value is considered valid
+       */
+       if (!stapling || strlen(stapling) == 0)
+               return true;
+
+       if (pg_strcasecmp(stapling, "0") == 0 ||
+                       pg_strcasecmp(stapling, "1") == 0)
+               return true;
+
+       /* anything else is wrong */
+       return false;
+}
 
 /*
  * Obtain user's home directory, return in given buffer
diff --git a/src/interfaces/libpq/fe-secure-openssl.c 
b/src/interfaces/libpq/fe-secure-openssl.c
index b6fffd7b9b..5a6fa404b7 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -62,6 +62,7 @@
 #include <openssl/engine.h>
 #endif
 #include <openssl/x509v3.h>
+#include <openssl/ocsp.h>
 
 
 static int     verify_cb(int ok, X509_STORE_CTX *ctx);
@@ -95,6 +96,9 @@ static pthread_mutex_t ssl_config_mutex = 
PTHREAD_MUTEX_INITIALIZER;
 
 static PQsslKeyPassHook_OpenSSL_type PQsslKeyPassHook = NULL;
 static int     ssl_protocol_version_to_openssl(const char *protocol);
+static int ocsp_response_check_cb(SSL *ssl);
+#define OCSP_CERT_STATUS_OK    1
+#define OCSP_CERT_STATUS_NOK   (-1)
 
 /* ------------------------------------------------------------ */
 /*                      Procedures common to all secure sessions               
        */
@@ -1184,6 +1188,36 @@ initialize_SSL(PGconn *conn)
                have_cert = true;
        }
 
+       /* Enable OCSP stapling for certificate status check */
+       if (conn->sslocspstapling &&
+               strlen(conn->sslocspstapling) != 0 &&
+               (strcmp(conn->sslocspstapling, "1") == 0))
+       {
+               /* set up certificate status request */
+               if (SSL_CTX_set_tlsext_status_type(SSL_context,
+                               TLSEXT_STATUSTYPE_ocsp) != 1)
+               {
+                       char    *err = SSLerrmessage(ERR_get_error());
+                       libpq_append_conn_error(conn,
+                                       "could not set up certificate status 
request: %s", err);
+                       SSLerrfree(err);
+                       SSL_CTX_free(SSL_context);
+                       return -1;
+               }
+
+               /* set up OCSP response callback */
+               if (SSL_CTX_set_tlsext_status_cb(SSL_context,
+                               ocsp_response_check_cb) != 1)
+               {
+                       char    *err = SSLerrmessage(ERR_get_error());
+                       libpq_append_conn_error(conn,
+                                       "could not set up OCSP response 
callback: %s", err);
+                       SSLerrfree(err);
+                       SSL_CTX_free(SSL_context);
+                       return -1;
+               }
+       }
+
        /*
         * The SSL context is now loaded with the correct root and client
         * certificates. Create a connection-specific SSL object. The private 
key
@@ -2146,3 +2180,122 @@ ssl_protocol_version_to_openssl(const char *protocol)
 
        return -1;
 }
+
+/*
+ * Verify OCSP stapling response in the context of an SSL/TLS connection.
+ *
+ * This function checks whether the server provided an OCSP response
+ * as part of the TLS handshake, verifies its integrity, and checks the
+ * revocation status of the presented certificates.
+ *
+ * Parameters:
+ *   - ssl: SSL/TLS connection object.
+ *
+ * Returns:
+ *   - OCSP_CERT_STATUS_OK: OCSP stapling was not requested or status is OK.
+ *   - OCSP_CERT_STATUS_NOK: OCSP verification failed or status is not OK.
+ *
+ * Steps:
+ *   1. Retrieve OCSP response during handshake.
+ *   2. Perform basic OCSP response verification.
+ *   3. Verify the signature and issuer of OCSP response.
+ *   4. Valid the certificate status in OCSP response.
+ *
+ * Cleanup:
+ *   - Free allocated memory for the OCSP response and basic response.
+ */
+static int ocsp_response_check_cb(SSL *ssl)
+{
+       long resp_len = 0;
+       const unsigned char *resp_data;
+       OCSP_RESPONSE *ocsp_resp = NULL;
+       OCSP_BASICRESP *basic_resp = NULL;
+       X509_STORE *trusted_store = NULL;
+       int ocsp_status = OCSP_CERT_STATUS_NOK;
+
+       OCSP_CERTID *cert_id;
+       X509 *peer_cert = NULL;
+       X509 *peer_cert_issuer = NULL;
+
+       int cert_status;
+       int rev_reason;
+       ASN1_GENERALIZEDTIME *rev_time;
+       ASN1_GENERALIZEDTIME *this_update;
+       ASN1_GENERALIZEDTIME *next_update;
+
+       /*
+        * step-1: retrieve OCSP response
+        */
+       /* check if requested a certificate status */
+       if (SSL_get_tlsext_status_type(ssl) != TLSEXT_STATUSTYPE_ocsp)
+               return OCSP_CERT_STATUS_OK; /* didn't request this OCSP status 
*/
+
+       /* check if got a correct OCSP response */
+       resp_len = SSL_get_tlsext_status_ocsp_resp(ssl, &resp_data);
+       if (resp_data == NULL)
+               goto cleanup; /* no proper OCSP response found */
+
+       /* convert the OCSP response to internal format */
+       ocsp_resp = d2i_OCSP_RESPONSE(NULL, &resp_data, resp_len);
+       if (ocsp_resp == NULL)
+               goto cleanup; /* failed to convert this OCSP response */
+
+       /*
+        * step-2: verify the basic of OCSP response
+        */
+       if (OCSP_response_status(ocsp_resp) != OCSP_RESPONSE_STATUS_SUCCESSFUL)
+               goto cleanup; /* OCSP response not successful */
+
+       /* get OCSP basic response structure */
+       basic_resp = OCSP_response_get1_basic(ocsp_resp);
+       if (basic_resp == NULL)
+               goto cleanup; /* failed to get basic OCSP response */
+
+       /*
+        * step-3: verify OCSP response is proper signed by a trusted signer
+        */
+       /* get local trusted certificate store */
+       trusted_store = SSL_CTX_get_cert_store(SSL_get_SSL_CTX(ssl));
+       if (trusted_store == NULL)
+               goto cleanup;
+
+       /* verify ocsp response signature and the issuer */
+       if (OCSP_basic_verify(basic_resp, NULL, trusted_store, 0) != 1)
+               goto cleanup; /* basic verification failed */
+
+       /*
+        * step-4: valid the certificate status inside OCSP response
+        */
+       /* get certificate and issuer to construct OCSP status lookup id */
+       peer_cert = SSL_get0_peer_certificate(ssl);
+       peer_cert_issuer = sk_X509_value(SSL_get0_verified_chain(ssl), 1);
+
+       cert_id = OCSP_cert_to_id(NULL, peer_cert, peer_cert_issuer);
+       if (cert_id == NULL)
+               goto cleanup;
+
+       /* get certificate status information */
+       if (OCSP_resp_find_status(basic_resp, cert_id,
+                       &cert_status, &rev_reason, &rev_time, &this_update, 
&next_update) != 1)
+               goto cleanup;
+
+       /* check whether or not the certificate has been revoked */
+       if (cert_status == V_OCSP_CERTSTATUS_GOOD)
+       {
+               if (OCSP_check_validity(this_update, next_update, 0, -1) == 1)
+                       ocsp_status = OCSP_CERT_STATUS_OK;
+               else
+                       goto cleanup;
+       }
+       else
+               goto cleanup;
+
+cleanup:
+       if (ocsp_resp != NULL)
+               OCSP_RESPONSE_free(ocsp_resp);
+
+       if (basic_resp != NULL)
+               OCSP_BASICRESP_free(basic_resp);
+
+       return ocsp_status;
+}
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index f36d76bf3f..b56433fed0 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -412,6 +412,7 @@ struct pg_conn
        char       *gssdelegation;      /* Try to delegate GSS credentials? (0 
or 1) */
        char       *ssl_min_protocol_version;   /* minimum TLS protocol version 
*/
        char       *ssl_max_protocol_version;   /* maximum TLS protocol version 
*/
+       char       *sslocspstapling;    /* request ocsp stapling from server */
        char       *target_session_attrs;       /* desired session properties */
        char       *require_auth;       /* name of the expected auth method */
        char       *load_balance_hosts; /* load balance over hosts */
-- 
2.34.1

Reply via email to