Hi Hackers,

This is the 2nd version patch with following updates:

1) Changed the frontend SSL parameter from `ssl_ocsp_stapling` to `sslocspstapling` to align with other SSL parameters.

2) Documented both the backend parameter `ssl_ocsp_file` and the frontend parameter `sslocspstapling`.

3) Implemented a check for the `nextUpdate` field in the OCSP response. If it is present but expired, the TLS connection will fail."

To add the test cases for OCSP Stapling, I think I should 1) add one section to conf/cas.config to generate `ocsp_ca.crt`; 2) use this `ocsp_ca` to sign some OCSP responses for server side certificates with `good`, `revoked` and `unknown`, and then 3) add some test cases to t/001_ssltests.pl.

Any comments or feedback would be greatly appreciated!

Thank you,

David


On 2024-02-05 3:51 p.m., David Zhang wrote:
Hello PostgreSQL Hackers,

This proposal suggests implementing OCSP Stapling in PostgreSQL as an alternative and more efficient method for checking certificate revocation, aligning with the trend shift from Certificate Revocation Lists (CRL).


1. benefits
OCSP Stapling offers several advantages over traditional CRL checks, including:

*) enhances user trust and real-time certificate verification without relying on potentially outdated CRLs. *) helps address privacy concerns associated with traditional OCSP checks, where the client contacts the OCSP responder directly. *) reduces latency by eliminating the need for the client to perform an additional round-trip to the OCSP responder. *) efficient resource utilization by allowing the server to cache and reuse OCSP responses.



2. a POC patch with below changes:
*) a new configuration option 'ssl_ocsp_file' to enable/disable OCSP Stapling and specify OCSP responses for PostgreSQL servers. For instance, ssl_ocsp_file = '_server.resp'

*) a server-side callback function responsible for generating OCSP stapling responses. This function comes into play only when a client requests the server's certificate status during the SSL/TLS handshake.

*) a new connection parameter 'ssl_ocsp_stapling' on the client side. For example, when 'ssl_ocsp_stapling=1', the psql client will send a certificate status request to the PostgreSQL server.

*) a client-side callback function within the libpq interface to validate and check the stapled OCSP response received from the server. If the server's certificate status is valid, the TLS handshake continues; otherwise, the connection is rejected.



3.  test cases for 'make check' are not yet ready as they could be complicated, but basic tests can be performed as demonstrated below: To run the tests, OpenSSL tools are required to simulate the OCSP responder for generating OCSP responses. Additionally, initial certificate generation, including a self-signed root CA, OCSP response signing certificate, and PostgreSQL server certificate, is needed.

*) add ocsp atrributes to openssl.cnf
$ openssl version
OpenSSL 3.0.2 15 Mar 2022 (Library: OpenSSL 3.0.2 15 Mar 2022)

$ diff openssl-ocsp.cnf /etc/ssl/openssl.cnf
204d203
< authorityInfoAccess = OCSP;URI:http://127.0.0.1:6655
232,235d230
< [ v3_ocsp ]
< basicConstraints = CA:FALSE
< keyUsage = nonRepudiation, digitalSignature, keyEncipherment
< extendedKeyUsage = OCSPSigning
255c250
< keyUsage = critical, cRLSign, digitalSignature, keyCertSign
---
>


*) prepare OCSP responder for generating OCSP response
$ mkdir -p demoCA/newcerts
$ touch demoCA/index.txt
$ echo '01' > demoCA/serial

# create a self-signed root CA
$ openssl req -new -nodes -out rootCA.csr -keyout rootCA.key -subj "/C=CA/ST=BC/L=VAN/O=IDO/OU=DEV/CN=rootCA" $ openssl x509 -req -in rootCA.csr -days 3650 -extfile openssl-ocsp.cnf -extensions v3_ca -signkey rootCA.key -out rootCA.crt

# create a certificate for OCSP responder
$ openssl req -new -nodes -out ocspSigning.csr -keyout ocspSigning.key -subj "/C=CA/ST=BC/L=VAN/O=IDO/OU=DEV/CN=ocspSigner" $ openssl ca -keyfile rootCA.key -cert rootCA.crt -in ocspSigning.csr -out ocspSigning.crt -config openssl-ocsp.cnf -extensions v3_ocsp
    Sign the certificate? [y/n]:y
    1 out of 1 certificate requests certified, commit? [y/n]y

# create a certificate for PostgreSQL server
$ openssl req -new -nodes -out server.csr -keyout server.key -subj "/C=CA/ST=BC/L=VAN/O=IDO/OU=DEV/CN=server" $ openssl ca -batch -days 365 -keyfile rootCA.key -cert rootCA.crt -config openssl-ocsp.cnf -out server.crt -infiles server.csr


# start OCSP responder
$ openssl ocsp -index demoCA/index.txt -port 6655 -rsigner ocspSigning.crt -rkey ocspSigning.key -CA rootCA.crt -text

# make sure PostgreSQL server's certificate is 'good'
$ openssl ocsp -issuer rootCA.crt -url http://127.0.0.1:6655 -resp_text -noverify -cert server.crt

# generate OCSP response when certificate status is 'good' and save as _server.resp-good: $ openssl ocsp -issuer rootCA.crt -cert server.crt -url http://127.0.0.1:6655 -respout _server.resp-good


# revoke PostgreSQL server's certificate
$ openssl ca -keyfile rootCA.key -cert rootCA.crt -revoke server.crt

# make sure PostgreSQL server's certificate is 'revoked'
$ openssl ocsp -issuer rootCA.crt -url http://127.0.0.1:6655 -resp_text -noverify -cert server.crt

### generate OCSP response when certificate status is 'revoked' and save as _server.resp-revoked: $ openssl ocsp -issuer rootCA.crt -cert server.crt -url http://127.0.0.1:6655 -respout _server.resp-revoked


*) setup OCSP stapling on PostgreSQL server side
copy 'rootCA.crt, server.key, server.crt, _server.resp-good, and _server.resp-revoked' to pgdata folder and update PostgreSQL server configuration by specifying ssl_ocsp_file = '_server.resp', where '_server.resp' is either a copy of '_server.resp-good' or '_server.resp-revoked' depending on the test case, for example:

listen_addresses = '*'
ssl = on
ssl_ca_file = 'rootCA.crt'
ssl_cert_file = 'server.crt'
ssl_key_file = 'server.key'
ssl_ocsp_file = '_server.resp'



*) test with psql client
3.1) PostgreSQL server's certificate status is 'good'
$ cp -pr _server.resp-good _server.resp
$ psql -d "sslmode=verify-ca sslrootcert=rootCA.crt user=david dbname=postgres ssl_ocsp_stapling=1" -h 127.0.0.1 -p 5432
psql (17devel)
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, compression: off)
Type "help" for help.

postgres=#


3.2) PostgreSQL server's certificate status is 'revoked'
$ cp -pr _server.resp-revoked _server.resp
$ psql -d "sslmode=verify-ca sslrootcert=rootCA.crt user=david dbname=postgres ssl_ocsp_stapling=1" -h 127.0.0.1 -p 5432 psql: error: connection to server at "127.0.0.1", port 5432 failed: SSL error: ocsp callback failure


3.3) PostgreSQL server's certificate status is 'revoked' but OCSP stapling is not required by psql client: $ psql -d "sslmode=verify-ca sslrootcert=rootCA.crt user=david dbname=postgres ssl_ocsp_stapling=0" -h 127.0.0.1 -p 5432
psql (17devel)
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, compression: off)
Type "help" for help.

postgres=#


This is a highly experimental proof of concept, and any comments or feedback would be greatly appreciated!


Best regards,
David Zhang

===============
Highgo Software Canada
www.highgo.ca
From 5a7b9216869837d7de73dd3edd4827611b8cfe8b Mon Sep 17 00:00:00 2001
From: David Zhang <idraw...@gmail.com>
Date: Tue, 20 Feb 2024 16:01:35 -0800
Subject: [PATCH] support certificate status check using OCSP stapling

---
 doc/src/sgml/config.sgml                      |  17 +++
 doc/src/sgml/libpq.sgml                       |  30 ++++
 doc/src/sgml/runtime.sgml                     |   6 +
 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      | 142 ++++++++++++++++++
 src/interfaces/libpq/libpq-int.h              |   1 +
 11 files changed, 333 insertions(+)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index ffd711b7f2..af502d5c0f 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1262,6 +1262,23 @@ include_dir 'conf.d'
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-ssl-ocsp-file" xreflabel="ssl_ocsp_file">
+      <term><varname>ssl_ocsp_file</varname> (<type>string</type>)
+      <indexterm>
+       <primary><varname>ssl_ocsp_file</varname> configuration 
parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        Specifies the name of the file containing the SSL server stapled
+        certificate status (OCSP Stapling). Relative paths are relative to the
+        data directory. This parameter can only be set in the
+        <filename>postgresql.conf</filename> file or on the server command 
line.
+        The default is empty, meaning no OCSP Stapling file is loaded.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-ssl-crl-file" xreflabel="ssl_crl_file">
       <term><varname>ssl_crl_file</varname> (<type>string</type>)
       <indexterm>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 1d8998efb2..a6457453f5 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1761,6 +1761,21 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
       </listitem>
      </varlistentry>
 
+     <varlistentry id="libpq-connect-sslocspstapling" 
xreflabel="sslocspstapling">
+      <term><literal>sslocspstapling</literal></term>
+      <listitem>
+       <para>
+        If set to 1, an <acronym>SSL</acronym> connection to the server
+        will send a certificate status request.  
<application>libpq</application>
+        will then refuse to connect if the server does not provide a valid OCSP
+         Stapling response. If set to 0 (default),
+        <application>libpq</application> will neither request the certificate
+        status nor check OCSP Stapling response.  This option is only 
available if
+        <productname>PostgreSQL</productname> is compiled with SSL support.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="libpq-connect-sslkey" xreflabel="sslkey">
       <term><literal>sslkey</literal></term>
       <listitem>
@@ -8323,6 +8338,16 @@ myEventProc(PGEventId evtId, void *evtInfo, void 
*passThrough)
      </para>
     </listitem>
 
+    <listitem>
+     <para>
+      <indexterm>
+       <primary><envar>PGSSLOCSPSTAPLING</envar></primary>
+      </indexterm>
+      <envar>PGSSLOCSPSTAPLING</envar> behaves the same as the <xref
+      linkend="libpq-connect-sslocspstapling"/> connection parameter.
+     </para>
+    </listitem>
+
     <listitem>
      <para>
       <indexterm>
@@ -8899,6 +8924,11 @@ 
ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*)
    Windows).
   </para>
 
+  <para>
+   Stapled server certificate status (OCSP Stapling) will be checked,
+   if the parameter <literal>sslocspstapling</literal> is set to 1.
+  </para>
+
   <para>
    The location of the root certificate file and the CRL can be changed by
    setting
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 64753d9c01..8704f1d3e5 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2428,6 +2428,12 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
       <entry>sent to client to indicate server's identity</entry>
      </row>
 
+     <row>
+      <entry><xref linkend="guc-ssl-ocsp-file"/></entry>
+      <entry>server certificate status stapled by ocsp responder</entry>
+      <entry>sent to client to indicate server certificate status</entry>
+     </row>
+
      <row>
       <entry><xref linkend="guc-ssl-key-file"/> 
(<filename>$PGDATA/server.key</filename>)</entry>
       <entry>server private key</entry>
diff --git a/src/backend/libpq/be-secure-openssl.c 
b/src/backend/libpq/be-secure-openssl.c
index e12b1cc9e3..c727634dfa 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 */
@@ -81,6 +82,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;
 
@@ -429,6 +432,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);
 
@@ -1653,3 +1659,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 6923c241b9..c57ea4ff33 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -37,6 +37,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 70652f0a3f..eae2676e3b 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -4490,6 +4490,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 e10755972a..a022a0a543 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.resp'
 #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 6171a0d17a..edef65fd39 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -89,6 +89,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 d4e10a0c4f..335da4df54 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -324,6 +324,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.
@@ -438,6 +442,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 */
@@ -1576,6 +1581,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
         */
@@ -4388,6 +4405,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);
@@ -7326,6 +7344,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 8110882262..1b0f92aade 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               
        */
@@ -1182,6 +1186,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) <= 0)
+               {
+                       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
@@ -2043,3 +2077,111 @@ 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 each revocation status in the OCSP response.
+ *
+ * Cleanup:
+ *   - Free allocated memory for the OCSP response and basic response.
+ */
+static int ocsp_response_check_cb(SSL *ssl)
+{
+       const unsigned char *resp;
+       long resp_len = 0;
+       int cert_index = 0;
+       int num_of_resp = 0;
+       OCSP_RESPONSE *ocsp_resp = NULL;
+       OCSP_BASICRESP *basic_resp = NULL;
+       OCSP_SINGLERESP *single_resp = NULL;
+       int status = OCSP_CERT_STATUS_NOK;
+
+       /*
+        * step-1: retrieve OCSP response
+        * refer to 'ocsp_resp_cb' in openssl/apps/s_client.c
+        */
+       /* check if requested certificate status */
+       if (SSL_get_tlsext_status_type(ssl) != TLSEXT_STATUSTYPE_ocsp)
+               return OCSP_CERT_STATUS_OK;
+
+       /* check if got OCSP response */
+       resp_len = SSL_get_tlsext_status_ocsp_resp(ssl, &resp);
+       if (resp == NULL)
+               goto cleanup; /* no OCSP response */
+
+       /* convert OCSP response to internal format */
+       ocsp_resp = d2i_OCSP_RESPONSE(NULL, &resp, resp_len);
+       if (ocsp_resp == NULL)
+               goto cleanup; /* failed to convert OCSP response */
+
+       /*
+        * step-2: verify the basic of OCSP response
+        * refer to 'ocsp_main' in openssl/apps/ocsp.c
+        */
+       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 */
+
+       /* perform basic OCSP response verify */
+       if (OCSP_basic_verify(basic_resp, SSL_get_peer_cert_chain(ssl),
+                       SSL_CTX_get_cert_store(SSL_get_SSL_CTX(ssl)), 0) != 1)
+               goto cleanup; /* basic verification failed */
+
+       /*
+        * step-3: verify each revocation status
+        * ref to 'ct_extract_ocsp_response_scts' in openssl/ssl/ssl_lib.c
+        */
+       num_of_resp = OCSP_resp_count(basic_resp);
+       for (cert_index = 0; cert_index < num_of_resp; cert_index ++)
+       {
+               ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd;
+
+               single_resp = OCSP_resp_get0(basic_resp, cert_index);
+               if (single_resp == NULL)
+                       goto cleanup; /* failed to get single response */
+
+               if (OCSP_single_get0_status(single_resp, NULL, &rev, &thisupd, 
&nextupd)
+                               == V_OCSP_CERTSTATUS_GOOD)
+               {
+                       if (!OCSP_check_validity(thisupd, nextupd, 0, -1))
+                               break; /* check validity of thisUpdate and 
nextUpdate failed */
+
+                       continue; /* status is good */
+               }
+               else
+               {
+                       break; /* status is revoked or unknown */
+               }
+       }
+       if (cert_index == num_of_resp)
+               status = OCSP_CERT_STATUS_OK;
+
+cleanup:
+       if (ocsp_resp != NULL)
+               OCSP_RESPONSE_free(ocsp_resp);
+
+       if (basic_resp != NULL)
+               OCSP_BASICRESP_free(basic_resp);
+
+       return status;
+}
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 82c18f870d..e65885cd39 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -405,6 +405,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