diff options
Diffstat (limited to 'contrib/sslinfo')
-rw-r--r-- | contrib/sslinfo/Makefile | 23 | ||||
-rw-r--r-- | contrib/sslinfo/sslinfo--1.0--1.1.sql | 12 | ||||
-rw-r--r-- | contrib/sslinfo/sslinfo--1.1--1.2.sql | 15 | ||||
-rw-r--r-- | contrib/sslinfo/sslinfo--1.2.sql | 48 | ||||
-rw-r--r-- | contrib/sslinfo/sslinfo.c | 484 | ||||
-rw-r--r-- | contrib/sslinfo/sslinfo.control | 5 |
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 |