summaryrefslogtreecommitdiffstats
path: root/contrib/sslinfo
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:17:33 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:17:33 +0000
commit5e45211a64149b3c659b90ff2de6fa982a5a93ed (patch)
tree739caf8c461053357daa9f162bef34516c7bf452 /contrib/sslinfo
parentInitial commit. (diff)
downloadpostgresql-15-5e45211a64149b3c659b90ff2de6fa982a5a93ed.tar.xz
postgresql-15-5e45211a64149b3c659b90ff2de6fa982a5a93ed.zip
Adding upstream version 15.5.upstream/15.5
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'contrib/sslinfo')
-rw-r--r--contrib/sslinfo/Makefile23
-rw-r--r--contrib/sslinfo/sslinfo--1.0--1.1.sql12
-rw-r--r--contrib/sslinfo/sslinfo--1.1--1.2.sql15
-rw-r--r--contrib/sslinfo/sslinfo--1.2.sql48
-rw-r--r--contrib/sslinfo/sslinfo.c484
-rw-r--r--contrib/sslinfo/sslinfo.control5
6 files changed, 587 insertions, 0 deletions
diff --git a/contrib/sslinfo/Makefile b/contrib/sslinfo/Makefile
new file mode 100644
index 0000000..dd1ff83
--- /dev/null
+++ b/contrib/sslinfo/Makefile
@@ -0,0 +1,23 @@
+# contrib/sslinfo/Makefile
+
+MODULE_big = sslinfo
+OBJS = \
+ $(WIN32RES) \
+ sslinfo.o
+
+EXTENSION = sslinfo
+DATA = 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
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/sslinfo
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
+
+SHLIB_LINK += $(filter -lssl -lcrypto -lssleay32 -leay32, $(LIBS))
diff --git a/contrib/sslinfo/sslinfo--1.0--1.1.sql b/contrib/sslinfo/sslinfo--1.0--1.1.sql
new file mode 100644
index 0000000..12d341f
--- /dev/null
+++ b/contrib/sslinfo/sslinfo--1.0--1.1.sql
@@ -0,0 +1,12 @@
+/* contrib/sslinfo/sslinfo--1.0--1.1.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "ALTER EXTENSION sslinfo UPDATE TO '1.1'" to load this file. \quit
+
+CREATE FUNCTION
+ssl_extension_info(OUT name text,
+ OUT value text,
+ OUT critical boolean
+) RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'ssl_extension_info'
+LANGUAGE C STRICT;
diff --git a/contrib/sslinfo/sslinfo--1.1--1.2.sql b/contrib/sslinfo/sslinfo--1.1--1.2.sql
new file mode 100644
index 0000000..f4f9014
--- /dev/null
+++ b/contrib/sslinfo/sslinfo--1.1--1.2.sql
@@ -0,0 +1,15 @@
+/* contrib/sslinfo/sslinfo--1.1--1.2.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION sslinfo UPDATE TO '1.2'" to load this file. \quit
+
+ALTER FUNCTION ssl_client_serial() PARALLEL RESTRICTED;
+ALTER FUNCTION ssl_is_used() PARALLEL RESTRICTED;
+ALTER FUNCTION ssl_version() PARALLEL RESTRICTED;
+ALTER FUNCTION ssl_cipher() PARALLEL RESTRICTED;
+ALTER FUNCTION ssl_client_cert_present() PARALLEL RESTRICTED;
+ALTER FUNCTION ssl_client_dn_field(text) PARALLEL RESTRICTED;
+ALTER FUNCTION ssl_issuer_field(text) PARALLEL RESTRICTED;
+ALTER FUNCTION ssl_client_dn() PARALLEL RESTRICTED;
+ALTER FUNCTION ssl_issuer_dn() PARALLEL RESTRICTED;
+ALTER FUNCTION ssl_extension_info() PARALLEL RESTRICTED;
diff --git a/contrib/sslinfo/sslinfo--1.2.sql b/contrib/sslinfo/sslinfo--1.2.sql
new file mode 100644
index 0000000..a555cfb
--- /dev/null
+++ b/contrib/sslinfo/sslinfo--1.2.sql
@@ -0,0 +1,48 @@
+/* contrib/sslinfo/sslinfo--1.2.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_serial() RETURNS numeric
+AS 'MODULE_PATHNAME', 'ssl_client_serial'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION ssl_is_used() RETURNS boolean
+AS 'MODULE_PATHNAME', 'ssl_is_used'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION ssl_version() RETURNS text
+AS 'MODULE_PATHNAME', 'ssl_version'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION ssl_cipher() RETURNS text
+AS 'MODULE_PATHNAME', 'ssl_cipher'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION ssl_client_cert_present() RETURNS boolean
+AS 'MODULE_PATHNAME', 'ssl_client_cert_present'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION ssl_client_dn_field(text) RETURNS text
+AS 'MODULE_PATHNAME', 'ssl_client_dn_field'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION ssl_issuer_field(text) RETURNS text
+AS 'MODULE_PATHNAME', 'ssl_issuer_field'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION ssl_client_dn() RETURNS text
+AS 'MODULE_PATHNAME', 'ssl_client_dn'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION ssl_issuer_dn() RETURNS text
+AS 'MODULE_PATHNAME', 'ssl_issuer_dn'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
+
+CREATE FUNCTION
+ssl_extension_info(OUT name text,
+ OUT value text,
+ OUT critical boolean
+) RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'ssl_extension_info'
+LANGUAGE C STRICT PARALLEL RESTRICTED;
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
new file mode 100644
index 0000000..5fd46b9
--- /dev/null
+++ b/contrib/sslinfo/sslinfo.c
@@ -0,0 +1,484 @@
+/*
+ * module for PostgreSQL to access client SSL certificate information
+ *
+ * Written by Victor B. Wagner <vitus@cryptocom.ru>, Cryptocom LTD
+ * This file is distributed under BSD-style license.
+ *
+ * contrib/sslinfo/sslinfo.c
+ */
+
+#include "postgres.h"
+
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+#include <openssl/asn1.h>
+
+#include "access/htup_details.h"
+#include "funcapi.h"
+#include "libpq/libpq-be.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+
+/*
+ * On Windows, <wincrypt.h> includes a #define for X509_NAME, which breaks our
+ * ability to use OpenSSL's version of that symbol if <wincrypt.h> is pulled
+ * in after <openssl/ssl.h> ... and, at least on some builds, it is. We
+ * can't reliably fix that by re-ordering #includes, because libpq/libpq-be.h
+ * #includes <openssl/ssl.h>. Instead, just zap the #define again here.
+ */
+#ifdef X509_NAME
+#undef X509_NAME
+#endif
+
+PG_MODULE_MAGIC;
+
+static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
+static Datum ASN1_STRING_to_text(ASN1_STRING *str);
+
+/*
+ * Function context for data persisting over repeated calls.
+ */
+typedef struct
+{
+ TupleDesc tupdesc;
+} SSLExtensionInfoContext;
+
+/*
+ * Indicates whether current session uses SSL
+ *
+ * Function has no arguments. Returns bool. True if current session
+ * is SSL session and false if it is local or non-ssl session.
+ */
+PG_FUNCTION_INFO_V1(ssl_is_used);
+Datum
+ssl_is_used(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_BOOL(MyProcPort->ssl_in_use);
+}
+
+
+/*
+ * Returns SSL version currently in use.
+ */
+PG_FUNCTION_INFO_V1(ssl_version);
+Datum
+ssl_version(PG_FUNCTION_ARGS)
+{
+ const char *version;
+
+ if (!MyProcPort->ssl_in_use)
+ PG_RETURN_NULL();
+
+ version = be_tls_get_version(MyProcPort);
+ if (version == NULL)
+ PG_RETURN_NULL();
+
+ PG_RETURN_TEXT_P(cstring_to_text(version));
+}
+
+
+/*
+ * Returns SSL cipher currently in use.
+ */
+PG_FUNCTION_INFO_V1(ssl_cipher);
+Datum
+ssl_cipher(PG_FUNCTION_ARGS)
+{
+ const char *cipher;
+
+ if (!MyProcPort->ssl_in_use)
+ PG_RETURN_NULL();
+
+ cipher = be_tls_get_cipher(MyProcPort);
+ if (cipher == NULL)
+ PG_RETURN_NULL();
+
+ PG_RETURN_TEXT_P(cstring_to_text(cipher));
+}
+
+
+/*
+ * Indicates whether current client provided a certificate
+ *
+ * Function has no arguments. Returns bool. True if current session
+ * is SSL session and client certificate is verified, otherwise false.
+ */
+PG_FUNCTION_INFO_V1(ssl_client_cert_present);
+Datum
+ssl_client_cert_present(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_BOOL(MyProcPort->peer_cert_valid);
+}
+
+
+/*
+ * Returns serial number of certificate used to establish current
+ * session
+ *
+ * Function has no arguments. It returns the certificate serial
+ * number as numeric or null if current session doesn't use SSL or if
+ * SSL connection is established without sending client certificate.
+ */
+PG_FUNCTION_INFO_V1(ssl_client_serial);
+Datum
+ssl_client_serial(PG_FUNCTION_ARGS)
+{
+ char decimal[NAMEDATALEN];
+ Datum result;
+
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_serial(MyProcPort, decimal, NAMEDATALEN);
+
+ if (!*decimal)
+ PG_RETURN_NULL();
+
+ result = DirectFunctionCall3(numeric_in,
+ CStringGetDatum(decimal),
+ ObjectIdGetDatum(0),
+ Int32GetDatum(-1));
+ return result;
+}
+
+
+/*
+ * Converts OpenSSL ASN1_STRING structure into text
+ *
+ * Converts ASN1_STRING into text, converting all the characters into
+ * current database encoding if possible. Any invalid characters are
+ * replaced by question marks.
+ *
+ * Parameter: str - OpenSSL ASN1_STRING structure. Memory management
+ * of this structure is responsibility of caller.
+ *
+ * Returns Datum, which can be directly returned from a C language SQL
+ * function.
+ */
+static Datum
+ASN1_STRING_to_text(ASN1_STRING *str)
+{
+ BIO *membuf;
+ size_t size;
+ char nullterm;
+ char *sp;
+ char *dp;
+ text *result;
+
+ membuf = BIO_new(BIO_s_mem());
+ if (membuf == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("could not create OpenSSL BIO structure")));
+ (void) BIO_set_close(membuf, BIO_CLOSE);
+ ASN1_STRING_print_ex(membuf, str,
+ ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
+ | ASN1_STRFLGS_UTF8_CONVERT));
+ /* ensure null termination of the BIO's content */
+ nullterm = '\0';
+ BIO_write(membuf, &nullterm, 1);
+ size = BIO_get_mem_data(membuf, &sp);
+ dp = pg_any_to_server(sp, size - 1, PG_UTF8);
+ result = cstring_to_text(dp);
+ if (dp != sp)
+ pfree(dp);
+ if (BIO_free(membuf) != 1)
+ elog(ERROR, "could not free OpenSSL BIO structure");
+
+ PG_RETURN_TEXT_P(result);
+}
+
+
+/*
+ * Returns specified field of specified X509_NAME structure
+ *
+ * Common part of ssl_client_dn and ssl_issuer_dn functions.
+ *
+ * Parameter: X509_NAME *name - either subject or issuer of certificate
+ * Parameter: text fieldName - field name string like 'CN' or commonName
+ * to be looked up in the OpenSSL ASN1 OID database
+ *
+ * Returns result of ASN1_STRING_to_text applied to appropriate
+ * part of name
+ */
+static Datum
+X509_NAME_field_to_text(X509_NAME *name, text *fieldName)
+{
+ char *string_fieldname;
+ int nid,
+ index;
+ ASN1_STRING *data;
+
+ string_fieldname = text_to_cstring(fieldName);
+ nid = OBJ_txt2nid(string_fieldname);
+ if (nid == NID_undef)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid X.509 field name: \"%s\"",
+ string_fieldname)));
+ pfree(string_fieldname);
+ index = X509_NAME_get_index_by_NID(name, nid, -1);
+ if (index < 0)
+ return (Datum) 0;
+ data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, index));
+ return ASN1_STRING_to_text(data);
+}
+
+
+/*
+ * Returns specified field of client certificate distinguished name
+ *
+ * Receives field name (like 'commonName' and 'emailAddress') and
+ * returns appropriate part of certificate subject converted into
+ * database encoding.
+ *
+ * Parameter: fieldname text - will be looked up in OpenSSL object
+ * identifier database
+ *
+ * Returns text string with appropriate value.
+ *
+ * Throws an error if argument cannot be converted into ASN1 OID by
+ * OpenSSL. Returns null if no client certificate is present, or if
+ * there is no field with such name in the certificate.
+ */
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
+{
+ text *fieldname = PG_GETARG_TEXT_PP(0);
+ Datum result;
+
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ result = X509_NAME_field_to_text(X509_get_subject_name(MyProcPort->peer), fieldname);
+
+ if (!result)
+ PG_RETURN_NULL();
+ else
+ return result;
+}
+
+
+/*
+ * Returns specified field of client certificate issuer name
+ *
+ * Receives field name (like 'commonName' and 'emailAddress') and
+ * returns appropriate part of certificate subject converted into
+ * database encoding.
+ *
+ * Parameter: fieldname text - would be looked up in OpenSSL object
+ * identifier database
+ *
+ * Returns text string with appropriate value.
+ *
+ * Throws an error if argument cannot be converted into ASN1 OID by
+ * OpenSSL. Returns null if no client certificate is present, or if
+ * there is no field with such name in the certificate.
+ */
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ text *fieldname = PG_GETARG_TEXT_PP(0);
+ Datum result;
+
+ if (!(MyProcPort->peer))
+ PG_RETURN_NULL();
+
+ result = X509_NAME_field_to_text(X509_get_issuer_name(MyProcPort->peer), fieldname);
+
+ if (!result)
+ PG_RETURN_NULL();
+ else
+ return result;
+}
+
+
+/*
+ * Returns current client certificate subject as one string
+ *
+ * This function returns distinguished name (subject) of the client
+ * certificate used in the current SSL connection, converting it into
+ * the current database encoding.
+ *
+ * Returns text datum.
+ */
+PG_FUNCTION_INFO_V1(ssl_client_dn);
+Datum
+ssl_client_dn(PG_FUNCTION_ARGS)
+{
+ char subject[NAMEDATALEN];
+
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_subject_name(MyProcPort, subject, NAMEDATALEN);
+
+ if (!*subject)
+ PG_RETURN_NULL();
+
+ PG_RETURN_TEXT_P(cstring_to_text(subject));
+}
+
+
+/*
+ * Returns current client certificate issuer as one string
+ *
+ * This function returns issuer's distinguished name of the client
+ * certificate used in the current SSL connection, converting it into
+ * the current database encoding.
+ *
+ * Returns text datum.
+ */
+PG_FUNCTION_INFO_V1(ssl_issuer_dn);
+Datum
+ssl_issuer_dn(PG_FUNCTION_ARGS)
+{
+ char issuer[NAMEDATALEN];
+
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_issuer_name(MyProcPort, issuer, NAMEDATALEN);
+
+ if (!*issuer)
+ PG_RETURN_NULL();
+
+ PG_RETURN_TEXT_P(cstring_to_text(issuer));
+}
+
+
+/*
+ * Returns information about available SSL extensions.
+ *
+ * Returns setof record made of the following values:
+ * - name of the extension.
+ * - value of the extension.
+ * - critical status of the extension.
+ */
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ X509 *cert = MyProcPort->peer;
+ FuncCallContext *funcctx;
+ int call_cntr;
+ int max_calls;
+ MemoryContext oldcontext;
+ SSLExtensionInfoContext *fctx;
+
+ if (SRF_IS_FIRSTCALL())
+ {
+
+ TupleDesc tupdesc;
+
+ /* create a function context for cross-call persistence */
+ funcctx = SRF_FIRSTCALL_INIT();
+
+ /*
+ * Switch to memory context appropriate for multiple function calls
+ */
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+ /* Create a user function context for cross-call persistence */
+ fctx = (SSLExtensionInfoContext *) palloc(sizeof(SSLExtensionInfoContext));
+
+ /* Construct tuple descriptor */
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("function returning record called in context that cannot accept type record")));
+ fctx->tupdesc = BlessTupleDesc(tupdesc);
+
+ /* Set max_calls as a count of extensions in certificate */
+ max_calls = cert != NULL ? X509_get_ext_count(cert) : 0;
+
+ if (max_calls > 0)
+ {
+ /* got results, keep track of them */
+ funcctx->max_calls = max_calls;
+ funcctx->user_fctx = fctx;
+ }
+ else
+ {
+ /* fast track when no results */
+ MemoryContextSwitchTo(oldcontext);
+ SRF_RETURN_DONE(funcctx);
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+ }
+
+ /* stuff done on every call of the function */
+ funcctx = SRF_PERCALL_SETUP();
+
+ /*
+ * Initialize per-call variables.
+ */
+ call_cntr = funcctx->call_cntr;
+ max_calls = funcctx->max_calls;
+ fctx = funcctx->user_fctx;
+
+ /* do while there are more left to send */
+ if (call_cntr < max_calls)
+ {
+ Datum values[3];
+ bool nulls[3];
+ char *buf;
+ HeapTuple tuple;
+ Datum result;
+ BIO *membuf;
+ X509_EXTENSION *ext;
+ ASN1_OBJECT *obj;
+ int nid;
+ int len;
+
+ /* need a BIO for this */
+ membuf = BIO_new(BIO_s_mem());
+ if (membuf == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("could not create OpenSSL BIO structure")));
+
+ /* Get the extension from the certificate */
+ ext = X509_get_ext(cert, call_cntr);
+ obj = X509_EXTENSION_get_object(ext);
+
+ /* Get the extension name */
+ nid = OBJ_obj2nid(obj);
+ if (nid == NID_undef)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unknown OpenSSL extension in certificate at position %d",
+ call_cntr)));
+ values[0] = CStringGetTextDatum(OBJ_nid2sn(nid));
+ nulls[0] = false;
+
+ /* Get the extension value */
+ if (X509V3_EXT_print(membuf, ext, 0, 0) <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("could not print extension value in certificate at position %d",
+ call_cntr)));
+ len = BIO_get_mem_data(membuf, &buf);
+ values[1] = PointerGetDatum(cstring_to_text_with_len(buf, len));
+ nulls[1] = false;
+
+ /* Get critical status */
+ values[2] = BoolGetDatum(X509_EXTENSION_get_critical(ext));
+ nulls[2] = false;
+
+ /* Build tuple */
+ tuple = heap_form_tuple(fctx->tupdesc, values, nulls);
+ result = HeapTupleGetDatum(tuple);
+
+ if (BIO_free(membuf) != 1)
+ elog(ERROR, "could not free OpenSSL BIO structure");
+
+ SRF_RETURN_NEXT(funcctx, result);
+ }
+
+ /* All done */
+ SRF_RETURN_DONE(funcctx);
+}
diff --git a/contrib/sslinfo/sslinfo.control b/contrib/sslinfo/sslinfo.control
new file mode 100644
index 0000000..c7754f9
--- /dev/null
+++ b/contrib/sslinfo/sslinfo.control
@@ -0,0 +1,5 @@
+# sslinfo extension
+comment = 'information about SSL certificates'
+default_version = '1.2'
+module_pathname = '$libdir/sslinfo'
+relocatable = true