]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Add notBefore and notAfter to SSL cert info display
authorDaniel Gustafsson <dgustafsson@postgresql.org>
Fri, 22 Mar 2024 20:25:25 +0000 (21:25 +0100)
committerDaniel Gustafsson <dgustafsson@postgresql.org>
Fri, 22 Mar 2024 20:25:25 +0000 (21:25 +0100)
This adds the X509 attributes notBefore and notAfter to sslinfo
as well as pg_stat_ssl to allow verifying and identifying the
validity period of the current client certificate. OpenSSL has
APIs for extracting notAfter and notBefore, but they are only
supported in recent versions so we have to calculate the dates
by hand in order to make this work for the older versions of
OpenSSL that we still support.

Original patch by Cary Huang with additional hacking by Jacob
and myself.

Author: Cary Huang <cary.huang@highgo.ca>
Co-author: Jacob Champion <jacob.champion@enterprisedb.com>
Co-author: Daniel Gustafsson <daniel@yesql.se>
Discussion: https://postgr.es/m/182b8565486.10af1a86f158715.2387262617218380588@highgo.ca

19 files changed:
contrib/sslinfo/Makefile
contrib/sslinfo/meson.build
contrib/sslinfo/sslinfo--1.2--1.3.sql [new file with mode: 0644]
contrib/sslinfo/sslinfo.c
contrib/sslinfo/sslinfo.control
doc/src/sgml/monitoring.sgml
doc/src/sgml/sslinfo.sgml
src/backend/catalog/system_views.sql
src/backend/libpq/be-secure-openssl.c
src/backend/utils/activity/backend_status.c
src/backend/utils/adt/pgstatfuncs.c
src/include/catalog/catversion.h
src/include/catalog/pg_proc.dat
src/include/libpq/libpq-be.h
src/include/utils/backend_status.h
src/test/regress/expected/rules.out
src/test/ssl/t/001_ssltests.pl
src/test/ssl/t/003_sslinfo.pl
src/tools/pgindent/typedefs.list

index dd1ff83b16d19adddd42806bec68b6ada3eabbd1..78a5a83d5c45e44e6132388b46dd81b5f4d09655 100644 (file)
@@ -6,7 +6,7 @@ OBJS = \
        sslinfo.o
 
 EXTENSION = sslinfo
-DATA = sslinfo--1.2.sql sslinfo--1.1--1.2.sql sslinfo--1.0--1.1.sql
+DATA = sslinfo--1.2--1.3.sql sslinfo--1.2.sql sslinfo--1.1--1.2.sql sslinfo--1.0--1.1.sql
 PGFILEDESC = "sslinfo - information about client SSL certificate"
 
 ifdef USE_PGXS
index 39d49a1736c152d78987e7832915640b8ff58209..a4bcd21ea6914f8a40b316c5cdbd49ba0c32a712 100644 (file)
@@ -26,6 +26,7 @@ install_data(
   'sslinfo--1.0--1.1.sql',
   'sslinfo--1.1--1.2.sql',
   'sslinfo--1.2.sql',
+  'sslinfo--1.2--1.3.sql',
   'sslinfo.control',
   kwargs: contrib_data_args,
 )
diff --git a/contrib/sslinfo/sslinfo--1.2--1.3.sql b/contrib/sslinfo/sslinfo--1.2--1.3.sql
new file mode 100644 (file)
index 0000000..424a11a
--- /dev/null
@@ -0,0 +1,12 @@
+/* contrib/sslinfo/sslinfo--1.2--1.3.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION sslinfo" to load this file. \quit
+
+CREATE FUNCTION ssl_client_get_notbefore() RETURNS timestamptz
+AS 'MODULE_PATHNAME', 'ssl_client_get_notbefore'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION ssl_client_get_notafter() RETURNS timestamptz
+AS 'MODULE_PATHNAME', 'ssl_client_get_notafter'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
index 5fd46b9874138d407bc97cbff627153b4a336162..904b203a172ad7dc1283c8d79edd09dcfe2c8263 100644 (file)
 #include <openssl/asn1.h>
 
 #include "access/htup_details.h"
+#include "common/int.h"
 #include "funcapi.h"
 #include "libpq/libpq-be.h"
 #include "miscadmin.h"
 #include "utils/builtins.h"
+#include "utils/timestamp.h"
 
 /*
  * On Windows, <wincrypt.h> includes a #define for X509_NAME, which breaks our
@@ -34,6 +36,7 @@ PG_MODULE_MAGIC;
 
 static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
 static Datum ASN1_STRING_to_text(ASN1_STRING *str);
+static Datum ASN1_TIME_to_timestamptz(ASN1_TIME *time);
 
 /*
  * Function context for data persisting over repeated calls.
@@ -225,6 +228,66 @@ X509_NAME_field_to_text(X509_NAME *name, text *fieldName)
 }
 
 
+/*
+ * Converts OpenSSL ASN1_TIME structure into timestamptz
+ *
+ * OpenSSL 1.0.2 doesn't expose a function to convert an ASN1_TIME to a tm
+ * struct, it's only available in 1.1.1 and onwards. Instead we can ask for the
+ * difference between the ASN1_TIME and a known timestamp and get the actual
+ * timestamp that way. Until support for OpenSSL 1.0.2 is retired we have to do
+ * it this way.
+ *
+ * Parameter: time - OpenSSL ASN1_TIME structure.
+ * Returns Datum, which can be directly returned from a C language SQL
+ * function.
+ */
+static Datum
+ASN1_TIME_to_timestamptz(ASN1_TIME *ASN1_cert_ts)
+{
+       int                     days;
+       int                     seconds;
+       const char      postgres_epoch[] = "20000101000000Z";
+       ASN1_TIME  *ASN1_epoch;
+       int64           result_days;
+       int64           result_secs;
+       int64           result;
+
+       /* Create an epoch to compare against */
+       ASN1_epoch = ASN1_TIME_new();
+       if (!ASN1_epoch)
+               ereport(ERROR,
+                               (errcode(ERRCODE_OUT_OF_MEMORY),
+                                errmsg("could not allocate memory for ASN1 TIME structure")));
+
+       /* Calculate the diff from the epoch to the certificate timestamp */
+       if (!ASN1_TIME_set_string(ASN1_epoch, postgres_epoch) ||
+               !ASN1_TIME_diff(&days, &seconds, ASN1_epoch, ASN1_cert_ts))
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                errmsg("failed to read certificate validity")));
+
+       /*
+        * Unlike when freeing other OpenSSL memory structures, there is no error
+        * return on freeing ASN1 strings.
+        */
+       ASN1_TIME_free(ASN1_epoch);
+
+       /*
+        * Convert the reported date into usecs to be used as a TimestampTz. The
+        * date should really not overflow an int64 but rather than trusting the
+        * certificate we take overflow into consideration.
+        */
+       if (pg_mul_s64_overflow(days, USECS_PER_DAY, &result_days) ||
+               pg_mul_s64_overflow(seconds, USECS_PER_SEC, &result_secs) ||
+               pg_add_s64_overflow(result_days, result_secs, &result))
+       {
+               return TimestampTzGetDatum(0);
+       }
+
+       return TimestampTzGetDatum(result);
+}
+
+
 /*
  * Returns specified field of client certificate distinguished name
  *
@@ -482,3 +545,35 @@ ssl_extension_info(PG_FUNCTION_ARGS)
        /* All done */
        SRF_RETURN_DONE(funcctx);
 }
+
+/*
+ * Returns current client certificate notBefore timestamp in
+ * timestamptz data type
+ */
+PG_FUNCTION_INFO_V1(ssl_client_get_notbefore);
+Datum
+ssl_client_get_notbefore(PG_FUNCTION_ARGS)
+{
+       X509       *cert = MyProcPort->peer;
+
+       if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+               PG_RETURN_NULL();
+
+       return ASN1_TIME_to_timestamptz(X509_get_notBefore(cert));
+}
+
+/*
+ * Returns current client certificate notAfter timestamp in
+ * timestamptz data type
+ */
+PG_FUNCTION_INFO_V1(ssl_client_get_notafter);
+Datum
+ssl_client_get_notafter(PG_FUNCTION_ARGS)
+{
+       X509       *cert = MyProcPort->peer;
+
+       if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+               PG_RETURN_NULL();
+
+       return ASN1_TIME_to_timestamptz(X509_get_notAfter(cert));
+}
index c7754f924cf440d9111c5b376ea88c2df85434bc..b53e95b7da8f4804fc78afc9a1e8ac0bbf3e14a8 100644 (file)
@@ -1,5 +1,5 @@
 # sslinfo extension
 comment = 'information about SSL certificates'
-default_version = '1.2'
+default_version = '1.3'
 module_pathname = '$libdir/sslinfo'
 relocatable = true
index 8736eac284199c45eb80f555541b983c5ef658a2..ca6b5631d7b9cc93028d0368ee1ea6b09f70c6b6 100644 (file)
@@ -2292,6 +2292,26 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        This field is truncated like <structfield>client_dn</structfield>.
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>not_before</structfield> <type>text</type>
+      </para>
+      <para>
+       Not before timestamp of the client certificate, or NULL if no client
+       certificate was supplied.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>not_after</structfield> <type>text</type>
+      </para>
+      <para>
+       Not after timestamp of the client certificate, or NULL if no client
+       certificate was supplied.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
index 85d49f66537cf03dec6801f8ff16500bfc4bd9b0..2a6725cc1cba38ec706a3808708d9420e63d8e43 100644 (file)
@@ -240,6 +240,36 @@ emailAddress
     </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+    <term>
+     <function>ssl_client_get_notbefore() returns timestamptz</function>
+     <indexterm>
+      <primary>ssl_client_get_notbefore</primary>
+     </indexterm>
+    </term>
+    <listitem>
+    <para>
+     Return the <structfield>not before</structfield> timestamp of the client
+     certificate.
+    </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term>
+     <function>ssl_client_get_notafter() returns timestamptz</function>
+     <indexterm>
+      <primary>ssl_client_get_notafter</primary>
+     </indexterm>
+    </term>
+    <listitem>
+    <para>
+     Return the <structfield>not after</structfield> timestamp of the client
+     certificate.
+    </para>
+    </listitem>
+   </varlistentry>
   </variablelist>
  </sect2>
 
index f69b7f55801595d153d76c0747b4619d4c971967..72d5caa5357aff51e2bdd55ec9920ea3952ad249 100644 (file)
@@ -992,7 +992,9 @@ CREATE VIEW pg_stat_ssl AS
             S.sslbits AS bits,
             S.ssl_client_dn AS client_dn,
             S.ssl_client_serial AS client_serial,
-            S.ssl_issuer_dn AS issuer_dn
+            S.ssl_issuer_dn AS issuer_dn,
+            S.ssl_not_before AS not_before,
+            S.ssl_not_after AS not_after
     FROM pg_stat_get_activity(NULL) AS S
     WHERE S.client_port IS NOT NULL;
 
index 72e43af353780130521ff5272bbe8dbaa2b127ab..c6cc681b2e7285a208418bf88966f7cfbcbeec2c 100644 (file)
@@ -27,6 +27,7 @@
 #include <netinet/tcp.h>
 #include <arpa/inet.h>
 
+#include "common/int.h"
 #include "common/string.h"
 #include "libpq/libpq.h"
 #include "miscadmin.h"
@@ -36,6 +37,7 @@
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
 #include "utils/memutils.h"
+#include "utils/timestamp.h"
 
 /*
  * These SSL-related #includes must come after all system-provided headers.
@@ -72,6 +74,7 @@ static bool initialize_ecdh(SSL_CTX *context, bool isServerStart);
 static const char *SSLerrmessage(unsigned long ecode);
 
 static char *X509_NAME_to_cstring(X509_NAME *name);
+static TimestampTz ASN1_TIME_to_timestamptz(ASN1_TIME *time);
 
 static SSL_CTX *SSL_context = NULL;
 static bool SSL_initialized = false;
@@ -1430,6 +1433,24 @@ be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
                ptr[0] = '\0';
 }
 
+void
+be_tls_get_peer_not_before(Port *port, TimestampTz *ptr)
+{
+       if (port->peer)
+               *ptr = ASN1_TIME_to_timestamptz(X509_get_notBefore(port->peer));
+       else
+               *ptr = 0;
+}
+
+void
+be_tls_get_peer_not_after(Port *port, TimestampTz *ptr)
+{
+       if (port->peer)
+               *ptr = ASN1_TIME_to_timestamptz(X509_get_notAfter(port->peer));
+       else
+               *ptr = 0;
+}
+
 void
 be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
 {
@@ -1573,6 +1594,63 @@ X509_NAME_to_cstring(X509_NAME *name)
        return result;
 }
 
+/*
+ * Convert an ASN1_TIME to a Timestamptz. OpenSSL 1.0.2 doesn't expose a function
+ * to convert an ASN1_TIME to a tm struct, it's only available in 1.1.1 and
+ * onwards. Instead we can ask for the difference between the ASN1_TIME and a
+ * known timestamp and get the actual timestamp that way. Until support for
+ * OpenSSL 1.0.2 is retired we have to do it this way.
+ */
+static TimestampTz
+ASN1_TIME_to_timestamptz(ASN1_TIME *ASN1_cert_ts)
+{
+       int                     days;
+       int                     seconds;
+       const char      postgres_epoch[] = "20000101000000Z";
+       ASN1_TIME  *ASN1_epoch;
+       int64           result_days;
+       int64           result_seconds;
+       int64           result;
+
+       /* Create an epoch to compare against */
+       ASN1_epoch = ASN1_TIME_new();
+       if (!ASN1_epoch)
+               ereport(ERROR,
+                               (errcode(ERRCODE_OUT_OF_MEMORY),
+                                errmsg("could not allocate memory for ASN1 TIME structure")));
+
+       /*
+        * Calculate the diff from the epoch to the certificate timestamp.
+        * POSTGRES_EPOCH_JDATE cannot be used here since OpenSSL needs an epoch
+        * in the ASN.1 format.
+        */
+       if (!ASN1_TIME_set_string(ASN1_epoch, postgres_epoch) ||
+               !ASN1_TIME_diff(&days, &seconds, ASN1_epoch, ASN1_cert_ts))
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                errmsg("failed to read certificate validity")));
+
+       /*
+        * Unlike when freeing other OpenSSL memory structures, there is no error
+        * return on freeing ASN1 strings.
+        */
+       ASN1_TIME_free(ASN1_epoch);
+
+       /*
+        * Convert the reported date into usecs to be used as a TimestampTz. The
+        * date should really not overflow an int64 but rather than trusting the
+        * certificate we take overflow into consideration.
+        */
+       if (pg_mul_s64_overflow(days, USECS_PER_DAY, &result_days) ||
+               pg_mul_s64_overflow(seconds, USECS_PER_SEC, &result_seconds) ||
+               pg_add_s64_overflow(result_seconds, result_days, &result))
+       {
+               return 0;
+       }
+
+       return result;
+}
+
 /*
  * Convert TLS protocol version GUC enum to OpenSSL values
  *
index 1ccf4c6d839e1503c9ba8fef6ba09521a685fd13..121ec8956f15d4bec6ae29eb2250f4ad08648afa 100644 (file)
@@ -348,6 +348,8 @@ pgstat_bestart(void)
                be_tls_get_peer_subject_name(MyProcPort, lsslstatus.ssl_client_dn, NAMEDATALEN);
                be_tls_get_peer_serial(MyProcPort, lsslstatus.ssl_client_serial, NAMEDATALEN);
                be_tls_get_peer_issuer_name(MyProcPort, lsslstatus.ssl_issuer_dn, NAMEDATALEN);
+               be_tls_get_peer_not_before(MyProcPort, &lsslstatus.ssl_not_before);
+               be_tls_get_peer_not_after(MyProcPort, &lsslstatus.ssl_not_after);
        }
        else
        {
index 3876339ee1b4b1b7227ecf0cc37af8b160843b38..61522642cba63960ec3f786a891f4c9796cba58c 100644 (file)
@@ -302,7 +302,7 @@ pg_stat_get_progress_info(PG_FUNCTION_ARGS)
 Datum
 pg_stat_get_activity(PG_FUNCTION_ARGS)
 {
-#define PG_STAT_GET_ACTIVITY_COLS      31
+#define PG_STAT_GET_ACTIVITY_COLS      33
        int                     num_backends = pgstat_fetch_stat_numbackends();
        int                     curr_backend;
        int                     pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
@@ -394,7 +394,7 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
                        pfree(clipped_activity);
 
                        /* leader_pid */
-                       nulls[29] = true;
+                       nulls[31] = true;
 
                        proc = BackendPidGetProc(beentry->st_procpid);
 
@@ -431,8 +431,8 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
                                 */
                                if (leader && leader->pid != beentry->st_procpid)
                                {
-                                       values[29] = Int32GetDatum(leader->pid);
-                                       nulls[29] = false;
+                                       values[31] = Int32GetDatum(leader->pid);
+                                       nulls[31] = false;
                                }
                                else if (beentry->st_backendType == B_BG_WORKER)
                                {
@@ -440,8 +440,8 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
 
                                        if (leader_pid != InvalidPid)
                                        {
-                                               values[29] = Int32GetDatum(leader_pid);
-                                               nulls[29] = false;
+                                               values[31] = Int32GetDatum(leader_pid);
+                                               nulls[31] = false;
                                        }
                                }
                        }
@@ -586,35 +586,45 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
                                        values[24] = CStringGetTextDatum(beentry->st_sslstatus->ssl_issuer_dn);
                                else
                                        nulls[24] = true;
+
+                               if (beentry->st_sslstatus->ssl_not_before != 0)
+                                       values[25] = TimestampTzGetDatum(beentry->st_sslstatus->ssl_not_before);
+                               else
+                                       nulls[25] = true;
+
+                               if (beentry->st_sslstatus->ssl_not_after != 0)
+                                       values[26] = TimestampTzGetDatum(beentry->st_sslstatus->ssl_not_after);
+                               else
+                                       nulls[26] = true;
                        }
                        else
                        {
                                values[18] = BoolGetDatum(false);       /* ssl */
-                               nulls[19] = nulls[20] = nulls[21] = nulls[22] = nulls[23] = nulls[24] = true;
+                               nulls[19] = nulls[20] = nulls[21] = nulls[22] = nulls[23] = nulls[24] = nulls[25] = nulls[26] = true;
                        }
 
                        /* GSSAPI information */
                        if (beentry->st_gss)
                        {
-                               values[25] = BoolGetDatum(beentry->st_gssstatus->gss_auth); /* gss_auth */
-                               values[26] = CStringGetTextDatum(beentry->st_gssstatus->gss_princ);
-                               values[27] = BoolGetDatum(beentry->st_gssstatus->gss_enc);      /* GSS Encryption in use */
-                               values[28] = BoolGetDatum(beentry->st_gssstatus->gss_delegation);       /* GSS credentials
+                               values[27] = BoolGetDatum(beentry->st_gssstatus->gss_auth); /* gss_auth */
+                               values[28] = CStringGetTextDatum(beentry->st_gssstatus->gss_princ);
+                               values[29] = BoolGetDatum(beentry->st_gssstatus->gss_enc);      /* GSS Encryption in use */
+                               values[30] = BoolGetDatum(beentry->st_gssstatus->gss_delegation);       /* GSS credentials
                                                                                                                                                                         * delegated */
                        }
                        else
                        {
-                               values[25] = BoolGetDatum(false);       /* gss_auth */
-                               nulls[26] = true;       /* No GSS principal */
-                               values[27] = BoolGetDatum(false);       /* GSS Encryption not in
+                               values[27] = BoolGetDatum(false);       /* gss_auth */
+                               nulls[28] = true;       /* No GSS principal */
+                               values[29] = BoolGetDatum(false);       /* GSS Encryption not in
                                                                                                         * use */
-                               values[28] = BoolGetDatum(false);       /* GSS credentials not
+                               values[30] = BoolGetDatum(false);       /* GSS credentials not
                                                                                                         * delegated */
                        }
                        if (beentry->st_query_id == 0)
-                               nulls[30] = true;
+                               nulls[32] = true;
                        else
-                               values[30] = UInt64GetDatum(beentry->st_query_id);
+                               values[32] = UInt64GetDatum(beentry->st_query_id);
                }
                else
                {
@@ -644,6 +654,8 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
                        nulls[28] = true;
                        nulls[29] = true;
                        nulls[30] = true;
+                       nulls[31] = true;
+                       nulls[32] = true;
                }
 
                tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
index f042d16832054ab7ad407edcfd8a3370c944c364..0fc0d19468b08ac8f9265640e01026b0d2d591a5 100644 (file)
@@ -57,6 +57,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     202403222
+#define CATALOG_VERSION_NO     202403223
 
 #endif
index 71c74350a0d0d7ecea5449339073e9c720ac1211..ea45b300b8cf2b4febe614aad6cc36616f83cc90 100644 (file)
   proname => 'pg_stat_get_activity', prorows => '100', proisstrict => 'f',
   proretset => 't', provolatile => 's', proparallel => 'r',
   prorettype => 'record', proargtypes => 'int4',
-  proallargtypes => '{int4,oid,int4,oid,text,text,text,text,text,timestamptz,timestamptz,timestamptz,timestamptz,inet,text,int4,xid,xid,text,bool,text,text,int4,text,numeric,text,bool,text,bool,bool,int4,int8}',
-  proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
-  proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
+  proallargtypes => '{int4,oid,int4,oid,text,text,text,text,text,timestamptz,timestamptz,timestamptz,timestamptz,inet,text,int4,xid,xid,text,bool,text,text,int4,text,numeric,text,timestamptz,timestamptz,bool,text,bool,bool,int4,int8}',
+  proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,ssl_not_before,ssl_not_after,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
   prosrc => 'pg_stat_get_activity' },
 { oid => '8403', descr => 'describe wait events',
   proname => 'pg_get_wait_events', procost => '10', prorows => '250',
index 4dce7677510b3ddb0c276c200a21587b18504fa5..3414899ebf9aa1634a9f1e0d49e20cc1277f69da 100644 (file)
@@ -294,6 +294,8 @@ extern const char *be_tls_get_cipher(Port *port);
 extern void be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len);
 extern void be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len);
 extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
+extern void be_tls_get_peer_not_before(Port *port, TimestampTz *ptr);
+extern void be_tls_get_peer_not_after(Port *port, TimestampTz *ptr);
 
 /*
  * Get the server certificate hash for SCRAM channel binding type
index 7b7f6f59d07a815e65777e3ab386d82caa500f52..d5bd4eceb624474e244516967ed2a6f1863fa30b 100644 (file)
@@ -61,6 +61,9 @@ typedef struct PgBackendSSLStatus
        char            ssl_client_serial[NAMEDATALEN];
 
        char            ssl_issuer_dn[NAMEDATALEN];
+       /* Certificate validity in postgres epoch format */
+       TimestampTz     ssl_not_before;
+       TimestampTz     ssl_not_after;
 } PgBackendSSLStatus;
 
 /*
index 18829ea5860352fc456365cd657a8063eb89af79..037e83b2ad73d9f060df3466381b57e9d8fa7881 100644 (file)
@@ -1763,7 +1763,7 @@ pg_stat_activity| SELECT s.datid,
     s.query_id,
     s.query,
     s.backend_type
-   FROM ((pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, gss_delegation, leader_pid, query_id)
+   FROM ((pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, ssl_not_before, ssl_not_after, gss_auth, gss_princ, gss_enc, gss_delegation, leader_pid, query_id)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)))
      LEFT JOIN pg_authid u ON ((s.usesysid = u.oid)));
 pg_stat_all_indexes| SELECT c.oid AS relid,
@@ -1883,7 +1883,7 @@ pg_stat_gssapi| SELECT pid,
     gss_princ AS principal,
     gss_enc AS encrypted,
     gss_delegation AS credentials_delegated
-   FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, gss_delegation, leader_pid, query_id)
+   FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, ssl_not_before, ssl_not_after, gss_auth, gss_princ, gss_enc, gss_delegation, leader_pid, query_id)
   WHERE (client_port IS NOT NULL);
 pg_stat_io| SELECT backend_type,
     object,
@@ -2086,7 +2086,7 @@ pg_stat_replication| SELECT s.pid,
     w.sync_priority,
     w.sync_state,
     w.reply_time
-   FROM ((pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, gss_delegation, leader_pid, query_id)
+   FROM ((pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, ssl_not_before, ssl_not_after, gss_auth, gss_princ, gss_enc, gss_delegation, leader_pid, query_id)
      JOIN pg_stat_get_wal_senders() w(pid, state, sent_lsn, write_lsn, flush_lsn, replay_lsn, write_lag, flush_lag, replay_lag, sync_priority, sync_state, reply_time) ON ((s.pid = w.pid)))
      LEFT JOIN pg_authid u ON ((s.usesysid = u.oid)));
 pg_stat_replication_slots| SELECT s.slot_name,
@@ -2119,8 +2119,10 @@ pg_stat_ssl| SELECT pid,
     sslbits AS bits,
     ssl_client_dn AS client_dn,
     ssl_client_serial AS client_serial,
-    ssl_issuer_dn AS issuer_dn
-   FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, gss_delegation, leader_pid, query_id)
+    ssl_issuer_dn AS issuer_dn,
+    ssl_not_before AS not_before,
+    ssl_not_after AS not_after
+   FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, ssl_not_before, ssl_not_after, gss_auth, gss_princ, gss_enc, gss_delegation, leader_pid, query_id)
   WHERE (client_port IS NOT NULL);
 pg_stat_subscription| SELECT su.oid AS subid,
     su.subname,
index 94ff043c8ec5751b7cb85d71256b69c5619c7880..90a423041339c2600a3921e0fc4029ce41e2539e 100644 (file)
@@ -538,8 +538,8 @@ command_like(
                "$common_connstr sslrootcert=invalid", '-c',
                "SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
        ],
-       qr{^pid,ssl,version,cipher,bits,client_dn,client_serial,issuer_dn\r?\n
-                               ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,_null_,_null_,_null_\r?$}mx,
+       qr{^pid,ssl,version,cipher,bits,client_dn,client_serial,issuer_dn,not_before,not_after\r?\n
+                               ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,_null_,_null_,_null_,_null_,_null_\r?$}mx,
        'pg_stat_ssl view without client certificate');
 
 # Test min/max SSL protocol versions.
@@ -740,10 +740,10 @@ command_like(
                "$common_connstr user=ssltestuser sslcert=ssl/client.crt "
                  . sslkey('client.key'),
                '-c',
-               "SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
+               "SELECT ssl,version,cipher,bits,client_dn,client_serial,issuer_dn,not_before AT TIME ZONE 'UTC' AS not_before,not_after AT TIME ZONE 'UTC' AS not_after FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
        ],
-       qr{^pid,ssl,version,cipher,bits,client_dn,client_serial,issuer_dn\r?\n
-                               ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,/?CN=ssltestuser,$serialno,/?\QCN=Test CA for PostgreSQL SSL regression test client certs\E\r?$}mx,
+       qr{^ssl,version,cipher,bits,client_dn,client_serial,issuer_dn,not_before,not_after\r?\n
+                               ^t,TLSv[\d.]+,[\w-]+,\d+,/?CN=ssltestuser,$serialno,/?\QCN=Test CA for PostgreSQL SSL regression test client certs,2023-06-29 01:01:01,2050-01-01 01:01:01\E\r?$}mx,
        'pg_stat_ssl with client certificate');
 
 # client key with wrong permissions
index 2ae5724846b1d913c8946de38dd06c71a3eb4fc4..4df3a941b5ca8c9d37858ccd1d6f1cbeb79d17c5 100644 (file)
@@ -165,6 +165,20 @@ $result = $node->safe_psql(
        connstr => $common_connstr);
 is($result, 't', "ssl_issuer_field() for commonName");
 
+$result = $node->safe_psql(
+       "certdb",
+       "SELECT ssl_client_get_notbefore() = not_before, "
+         . "not_before AT TIME ZONE 'UTC' = '2023-06-29 01:01:01' FROM pg_stat_ssl WHERE pid = pg_backend_pid();",
+       connstr => $common_connstr);
+is($result, 't|t', "ssl_client_get_notbefore() for not_before timestamp");
+
+$result = $node->safe_psql(
+       "certdb",
+       "SELECT ssl_client_get_notafter() = not_after, "
+         . "not_after AT TIME ZONE 'UTC' = '2050-01-01 01:01:01' FROM pg_stat_ssl WHERE pid = pg_backend_pid();",
+       connstr => $common_connstr);
+is($result, 't|t', "ssl_client_get_notafter() for not_after timestamp");
+
 $result = $node->safe_psql(
        "certdb",
        "SELECT value, critical FROM ssl_extension_info() WHERE name = 'basicConstraints';",
index e2a0525dd4a5027750af8c0f2d14e4d5d3e1a893..de54e9d8699f747ba42f8e8d2cfd771eff635283 100644 (file)
@@ -6,6 +6,7 @@ ASN1_INTEGER
 ASN1_OBJECT
 ASN1_OCTET_STRING
 ASN1_STRING
+ASN1_TIME
 AV
 A_ArrayExpr
 A_Const