diff --git a/contrib/sslinfo/Makefile b/contrib/sslinfo/Makefile index 86cbf053e6..f6c147293c 100644 --- a/contrib/sslinfo/Makefile +++ b/contrib/sslinfo/Makefile @@ -4,7 +4,8 @@ MODULE_big = sslinfo OBJS = sslinfo.o $(WIN32RES) EXTENSION = sslinfo -DATA = sslinfo--1.0.sql sslinfo--unpackaged--1.0.sql +DATA = sslinfo--1.0--1.1.sql sslinfo--1.1.sql \ + sslinfo--unpackaged--1.0.sql PGFILEDESC = "sslinfo - information about client SSL certificate" ifdef USE_PGXS 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 0000000000..4c26c6e1cf --- /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 OR REPLACE 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.0.sql b/contrib/sslinfo/sslinfo--1.1.sql similarity index 83% rename from contrib/sslinfo/sslinfo--1.0.sql rename to contrib/sslinfo/sslinfo--1.1.sql index 79ce656786..92855e3144 100644 --- a/contrib/sslinfo/sslinfo--1.0.sql +++ b/contrib/sslinfo/sslinfo--1.1.sql @@ -1,4 +1,4 @@ -/* contrib/sslinfo/sslinfo--1.0.sql */ +/* contrib/sslinfo/sslinfo--1.1.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION sslinfo" to load this file. \quit @@ -38,3 +38,11 @@ LANGUAGE C STRICT; CREATE FUNCTION ssl_issuer_dn() RETURNS text AS 'MODULE_PATHNAME', 'ssl_issuer_dn' LANGUAGE C STRICT; + +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.c b/contrib/sslinfo/sslinfo.c index 6e230052c9..e00f8a2663 100644 --- a/contrib/sslinfo/sslinfo.c +++ b/contrib/sslinfo/sslinfo.c @@ -8,15 +8,16 @@ */ #include "postgres.h" -#include "fmgr.h" -#include "utils/numeric.h" + +#include +#include +#include + +#include "access/htup_details.h" +#include "funcapi.h" #include "libpq/libpq-be.h" #include "miscadmin.h" #include "utils/builtins.h" -#include "mb/pg_wchar.h" - -#include -#include PG_MODULE_MAGIC; @@ -24,6 +25,13 @@ static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName); static Datum X509_NAME_to_text(X509_NAME *name); 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 @@ -373,3 +381,148 @@ ssl_issuer_dn(PG_FUNCTION_ARGS) PG_RETURN_NULL(); return X509_NAME_to_text(X509_get_issuer_name(MyProcPort->peer)); } + + +/* + * 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; + + STACK_OF(X509_EXTENSION) *ext_stack = NULL; + + 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); + + /* Get all extensions of certificate */ + if (cert && cert->cert_info) + ext_stack = cert->cert_info->extensions; + + /* Set max_calls as a count of extensions in certificate */ + max_calls = cert != NULL ? X509_get_ext_count(cert) : 0; + + if (cert != NULL && + ext_stack != NULL && + 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; + + ext_stack = cert->cert_info->extensions; + + /* 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 = sk_X509_EXTENSION_value(ext_stack, 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); + } + + /* Do when there is no more left */ + SRF_RETURN_DONE(funcctx); +} diff --git a/contrib/sslinfo/sslinfo.control b/contrib/sslinfo/sslinfo.control index 1d2f058f6e..dfcf17efcf 100644 --- a/contrib/sslinfo/sslinfo.control +++ b/contrib/sslinfo/sslinfo.control @@ -1,5 +1,5 @@ # sslinfo extension comment = 'information about SSL certificates' -default_version = '1.0' +default_version = '1.1' module_pathname = '$libdir/sslinfo' relocatable = true diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml index 22ef4396ab..4203d8fb8a 100644 --- a/doc/src/sgml/sslinfo.sgml +++ b/doc/src/sgml/sslinfo.sgml @@ -219,6 +219,21 @@ emailAddress + + + + ssl_extension_info() returns setof record + + ssl_extension_info + + + + + Provide information about extensions of client certificate: extension name, + extension value, and if it is a critical extension. + + + @@ -229,6 +244,10 @@ emailAddress Victor Wagner vitus@cryptocom.ru, Cryptocom LTD + + Dmitry Voronin carriingfate92@yandex.ru + + E-Mail of Cryptocom OpenSSL development group: openssl@cryptocom.ru