Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 75ec5e7

Browse files
Add notBefore and notAfter to SSL cert info display
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. Author: Cary Huang <cary.huang@highgo.ca> Discussion: https://postgr.es/m/182b8565486.10af1a86f158715.2387262617218380588@highgo.ca
1 parent 40fad96 commit 75ec5e7

File tree

18 files changed

+246
-33
lines changed

18 files changed

+246
-33
lines changed

contrib/sslinfo/Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ OBJS = \
66
sslinfo.o
77

88
EXTENSION = sslinfo
9-
DATA = sslinfo--1.2.sql sslinfo--1.1--1.2.sql sslinfo--1.0--1.1.sql
9+
DATA = sslinfo--1.2--1.3.sql sslinfo--1.2.sql sslinfo--1.1--1.2.sql sslinfo--1.0--1.1.sql
1010
PGFILEDESC = "sslinfo - information about client SSL certificate"
1111

1212
ifdef USE_PGXS

contrib/sslinfo/meson.build

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ install_data(
2626
'sslinfo--1.0--1.1.sql',
2727
'sslinfo--1.1--1.2.sql',
2828
'sslinfo--1.2.sql',
29+
'sslinfo--1.2--1.3.sql',
2930
'sslinfo.control',
3031
kwargs: contrib_data_args,
3132
)

contrib/sslinfo/sslinfo--1.2--1.3.sql

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/* contrib/sslinfo/sslinfo--1.2--1.3.sql */
2+
3+
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
4+
\echo Use "CREATE EXTENSION sslinfo" to load this file. \quit
5+
6+
CREATE FUNCTION ssl_client_get_notbefore() RETURNS timestamp
7+
AS 'MODULE_PATHNAME', 'ssl_client_get_notbefore'
8+
LANGUAGE C STRICT PARALLEL RESTRICTED;
9+
10+
CREATE FUNCTION ssl_client_get_notafter() RETURNS timestamp
11+
AS 'MODULE_PATHNAME', 'ssl_client_get_notafter'
12+
LANGUAGE C STRICT PARALLEL RESTRICTED;

contrib/sslinfo/sslinfo.c

+67
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "libpq/libpq-be.h"
1919
#include "miscadmin.h"
2020
#include "utils/builtins.h"
21+
#include "utils/timestamp.h"
2122

2223
/*
2324
* On Windows, <wincrypt.h> includes a #define for X509_NAME, which breaks our
@@ -34,6 +35,7 @@ PG_MODULE_MAGIC;
3435

3536
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
3637
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
38+
static Datum ASN1_TIME_to_timestamp(ASN1_TIME *time);
3739

3840
/*
3941
* Function context for data persisting over repeated calls.
@@ -225,6 +227,39 @@ X509_NAME_field_to_text(X509_NAME *name, text *fieldName)
225227
}
226228

227229

230+
/*
231+
* Converts OpenSSL ASN1_TIME structure into timestamp
232+
*
233+
* Parameter: time - OpenSSL ASN1_TIME structure.
234+
*
235+
* Returns Datum, which can be directly returned from a C language SQL
236+
* function.
237+
*/
238+
static Datum
239+
ASN1_TIME_to_timestamp(ASN1_TIME * time)
240+
{
241+
struct tm tm_time;
242+
struct pg_tm pgtm_time;
243+
Timestamp ts;
244+
245+
ASN1_TIME_to_tm(time, &tm_time);
246+
247+
pgtm_time.tm_sec = tm_time.tm_sec;
248+
pgtm_time.tm_min = tm_time.tm_min;
249+
pgtm_time.tm_hour = tm_time.tm_hour;
250+
pgtm_time.tm_mday = tm_time.tm_mday;
251+
pgtm_time.tm_mon = tm_time.tm_mon + 1;
252+
pgtm_time.tm_year = tm_time.tm_year + 1900;
253+
254+
if (tm2timestamp(&pgtm_time, 0, NULL, &ts))
255+
ereport(ERROR,
256+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
257+
errmsg("failed to convert tm to timestamp")));
258+
259+
PG_RETURN_TIMESTAMP(ts);
260+
}
261+
262+
228263
/*
229264
* Returns specified field of client certificate distinguished name
230265
*
@@ -482,3 +517,35 @@ ssl_extension_info(PG_FUNCTION_ARGS)
482517
/* All done */
483518
SRF_RETURN_DONE(funcctx);
484519
}
520+
521+
/*
522+
* Returns current client certificate notBefore timestamp in
523+
* timestamp data type
524+
*/
525+
PG_FUNCTION_INFO_V1(ssl_client_get_notbefore);
526+
Datum
527+
ssl_client_get_notbefore(PG_FUNCTION_ARGS)
528+
{
529+
X509 *cert = MyProcPort->peer;
530+
531+
if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
532+
PG_RETURN_NULL();
533+
534+
return ASN1_TIME_to_timestamp(X509_get_notBefore(cert));
535+
}
536+
537+
/*
538+
* Returns current client certificate notAfter timestamp in
539+
* timestamp data type
540+
*/
541+
PG_FUNCTION_INFO_V1(ssl_client_get_notafter);
542+
Datum
543+
ssl_client_get_notafter(PG_FUNCTION_ARGS)
544+
{
545+
X509 *cert = MyProcPort->peer;
546+
547+
if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
548+
PG_RETURN_NULL();
549+
550+
return ASN1_TIME_to_timestamp(X509_get_notAfter(cert));
551+
}

contrib/sslinfo/sslinfo.control

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# sslinfo extension
22
comment = 'information about SSL certificates'
3-
default_version = '1.2'
3+
default_version = '1.3'
44
module_pathname = '$libdir/sslinfo'
55
relocatable = true

doc/src/sgml/monitoring.sgml

+20
Original file line numberDiff line numberDiff line change
@@ -2257,6 +2257,26 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i
22572257
This field is truncated like <structfield>client_dn</structfield>.
22582258
</para></entry>
22592259
</row>
2260+
2261+
<row>
2262+
<entry role="catalog_table_entry"><para role="column_definition">
2263+
<structfield>not_before</structfield> <type>text</type>
2264+
</para>
2265+
<para>
2266+
Not before UTC timestamp of the client certificate, or NULL if no client
2267+
certificate was supplied.
2268+
</para></entry>
2269+
</row>
2270+
2271+
<row>
2272+
<entry role="catalog_table_entry"><para role="column_definition">
2273+
<structfield>not_after</structfield> <type>text</type>
2274+
</para>
2275+
<para>
2276+
Not after UTC timestamp of the client certificate, or NULL if no client
2277+
certificate was supplied.
2278+
</para></entry>
2279+
</row>
22602280
</tbody>
22612281
</tgroup>
22622282
</table>

doc/src/sgml/sslinfo.sgml

+30
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,36 @@ emailAddress
240240
</para>
241241
</listitem>
242242
</varlistentry>
243+
244+
<varlistentry>
245+
<term>
246+
<function>ssl_client_get_notbefore() returns text</function>
247+
<indexterm>
248+
<primary>ssl_client_get_notbefore</primary>
249+
</indexterm>
250+
</term>
251+
<listitem>
252+
<para>
253+
Return the <structfield>not before</structfield> UTC timestamp of the client
254+
certificate.
255+
</para>
256+
</listitem>
257+
</varlistentry>
258+
259+
<varlistentry>
260+
<term>
261+
<function>ssl_client_get_notafter() returns text</function>
262+
<indexterm>
263+
<primary>ssl_client_get_notafter</primary>
264+
</indexterm>
265+
</term>
266+
<listitem>
267+
<para>
268+
Return the <structfield>not after</structfield> UTC timestamp of the client
269+
certificate.
270+
</para>
271+
</listitem>
272+
</varlistentry>
243273
</variablelist>
244274
</sect2>
245275

src/backend/catalog/system_views.sql

+3-1
Original file line numberDiff line numberDiff line change
@@ -970,7 +970,9 @@ CREATE VIEW pg_stat_ssl AS
970970
S.sslbits AS bits,
971971
S.ssl_client_dn AS client_dn,
972972
S.ssl_client_serial AS client_serial,
973-
S.ssl_issuer_dn AS issuer_dn
973+
S.ssl_issuer_dn AS issuer_dn,
974+
S.ssl_not_before AS not_before,
975+
S.ssl_not_after AS not_after
974976
FROM pg_stat_get_activity(NULL) AS S
975977
WHERE S.client_port IS NOT NULL;
976978

src/backend/libpq/be-secure-openssl.c

+47
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include "tcop/tcopprot.h"
3737
#include "utils/builtins.h"
3838
#include "utils/memutils.h"
39+
#include "utils/timestamp.h"
3940

4041
/*
4142
* These SSL-related #includes must come after all system-provided headers.
@@ -72,6 +73,7 @@ static bool initialize_ecdh(SSL_CTX *context, bool isServerStart);
7273
static const char *SSLerrmessage(unsigned long ecode);
7374

7475
static char *X509_NAME_to_cstring(X509_NAME *name);
76+
static Timestamp ASN1_TIME_to_timestamp(ASN1_TIME *time);
7577

7678
static SSL_CTX *SSL_context = NULL;
7779
static bool SSL_initialized = false;
@@ -1406,6 +1408,24 @@ be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
14061408
ptr[0] = '\0';
14071409
}
14081410

1411+
void
1412+
be_tls_get_peer_not_before(Port *port, Timestamp *ptr)
1413+
{
1414+
if (port->peer)
1415+
*ptr = ASN1_TIME_to_timestamp(X509_get_notBefore(port->peer));
1416+
else
1417+
*ptr = 0;
1418+
}
1419+
1420+
void
1421+
be_tls_get_peer_not_after(Port *port, Timestamp *ptr)
1422+
{
1423+
if (port->peer)
1424+
*ptr = ASN1_TIME_to_timestamp(X509_get_notAfter(port->peer));
1425+
else
1426+
*ptr = 0;
1427+
}
1428+
14091429
void
14101430
be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
14111431
{
@@ -1549,6 +1569,33 @@ X509_NAME_to_cstring(X509_NAME *name)
15491569
return result;
15501570
}
15511571

1572+
/*
1573+
* Convert an ASN1_TIME to a Timestamp
1574+
*/
1575+
static Timestamp
1576+
ASN1_TIME_to_timestamp(ASN1_TIME * time)
1577+
{
1578+
struct tm tm_time;
1579+
struct pg_tm pgtm_time;
1580+
Timestamp ts;
1581+
1582+
ASN1_TIME_to_tm(time, &tm_time);
1583+
1584+
pgtm_time.tm_sec = tm_time.tm_sec;
1585+
pgtm_time.tm_min = tm_time.tm_min;
1586+
pgtm_time.tm_hour = tm_time.tm_hour;
1587+
pgtm_time.tm_mday = tm_time.tm_mday;
1588+
pgtm_time.tm_mon = tm_time.tm_mon + 1;
1589+
pgtm_time.tm_year = tm_time.tm_year + 1900;
1590+
1591+
if (tm2timestamp(&pgtm_time, 0, NULL, &ts))
1592+
ereport(ERROR,
1593+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1594+
errmsg("timestamp out of range")));
1595+
1596+
return ts;
1597+
}
1598+
15521599
/*
15531600
* Convert TLS protocol version GUC enum to OpenSSL values
15541601
*

src/backend/utils/activity/backend_status.c

+2
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,8 @@ pgstat_bestart(void)
367367
be_tls_get_peer_subject_name(MyProcPort, lsslstatus.ssl_client_dn, NAMEDATALEN);
368368
be_tls_get_peer_serial(MyProcPort, lsslstatus.ssl_client_serial, NAMEDATALEN);
369369
be_tls_get_peer_issuer_name(MyProcPort, lsslstatus.ssl_issuer_dn, NAMEDATALEN);
370+
be_tls_get_peer_not_before(MyProcPort, &lsslstatus.ssl_not_before);
371+
be_tls_get_peer_not_after(MyProcPort, &lsslstatus.ssl_not_after);
370372
}
371373
else
372374
{

src/backend/utils/adt/pgstatfuncs.c

+29-17
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ pg_stat_get_progress_info(PG_FUNCTION_ARGS)
303303
Datum
304304
pg_stat_get_activity(PG_FUNCTION_ARGS)
305305
{
306-
#define PG_STAT_GET_ACTIVITY_COLS 31
306+
#define PG_STAT_GET_ACTIVITY_COLS 33
307307
int num_backends = pgstat_fetch_stat_numbackends();
308308
int curr_backend;
309309
int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
@@ -395,7 +395,7 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
395395
pfree(clipped_activity);
396396

397397
/* leader_pid */
398-
nulls[29] = true;
398+
nulls[31] = true;
399399

400400
proc = BackendPidGetProc(beentry->st_procpid);
401401

@@ -432,17 +432,17 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
432432
*/
433433
if (leader && leader->pid != beentry->st_procpid)
434434
{
435-
values[29] = Int32GetDatum(leader->pid);
436-
nulls[29] = false;
435+
values[31] = Int32GetDatum(leader->pid);
436+
nulls[31] = false;
437437
}
438438
else if (beentry->st_backendType == B_BG_WORKER)
439439
{
440440
int leader_pid = GetLeaderApplyWorkerPid(beentry->st_procpid);
441441

442442
if (leader_pid != InvalidPid)
443443
{
444-
values[29] = Int32GetDatum(leader_pid);
445-
nulls[29] = false;
444+
values[31] = Int32GetDatum(leader_pid);
445+
nulls[31] = false;
446446
}
447447
}
448448
}
@@ -587,35 +587,45 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
587587
values[24] = CStringGetTextDatum(beentry->st_sslstatus->ssl_issuer_dn);
588588
else
589589
nulls[24] = true;
590+
591+
if (beentry->st_sslstatus->ssl_not_before != 0)
592+
values[25] = TimestampGetDatum(beentry->st_sslstatus->ssl_not_before);
593+
else
594+
nulls[25] = true;
595+
596+
if (beentry->st_sslstatus->ssl_not_after != 0)
597+
values[26] = TimestampGetDatum(beentry->st_sslstatus->ssl_not_after);
598+
else
599+
nulls[26] = true;
590600
}
591601
else
592602
{
593603
values[18] = BoolGetDatum(false); /* ssl */
594-
nulls[19] = nulls[20] = nulls[21] = nulls[22] = nulls[23] = nulls[24] = true;
604+
nulls[19] = nulls[20] = nulls[21] = nulls[22] = nulls[23] = nulls[24] = nulls[25] = nulls[26] = true;
595605
}
596606

597607
/* GSSAPI information */
598608
if (beentry->st_gss)
599609
{
600-
values[25] = BoolGetDatum(beentry->st_gssstatus->gss_auth); /* gss_auth */
601-
values[26] = CStringGetTextDatum(beentry->st_gssstatus->gss_princ);
602-
values[27] = BoolGetDatum(beentry->st_gssstatus->gss_enc); /* GSS Encryption in use */
603-
values[28] = BoolGetDatum(beentry->st_gssstatus->gss_delegation); /* GSS credentials
610+
values[27] = BoolGetDatum(beentry->st_gssstatus->gss_auth); /* gss_auth */
611+
values[28] = CStringGetTextDatum(beentry->st_gssstatus->gss_princ);
612+
values[29] = BoolGetDatum(beentry->st_gssstatus->gss_enc); /* GSS Encryption in use */
613+
values[30] = BoolGetDatum(beentry->st_gssstatus->gss_delegation); /* GSS credentials
604614
* delegated */
605615
}
606616
else
607617
{
608-
values[25] = BoolGetDatum(false); /* gss_auth */
609-
nulls[26] = true; /* No GSS principal */
610-
values[27] = BoolGetDatum(false); /* GSS Encryption not in
618+
values[27] = BoolGetDatum(false); /* gss_auth */
619+
nulls[28] = true; /* No GSS principal */
620+
values[29] = BoolGetDatum(false); /* GSS Encryption not in
611621
* use */
612-
values[28] = BoolGetDatum(false); /* GSS credentials not
622+
values[30] = BoolGetDatum(false); /* GSS credentials not
613623
* delegated */
614624
}
615625
if (beentry->st_query_id == 0)
616-
nulls[30] = true;
626+
nulls[32] = true;
617627
else
618-
values[30] = UInt64GetDatum(beentry->st_query_id);
628+
values[32] = UInt64GetDatum(beentry->st_query_id);
619629
}
620630
else
621631
{
@@ -645,6 +655,8 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
645655
nulls[28] = true;
646656
nulls[29] = true;
647657
nulls[30] = true;
658+
nulls[31] = true;
659+
nulls[32] = true;
648660
}
649661

650662
tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);

0 commit comments

Comments
 (0)