contrib/sslinfo: add ssl_extension_info SRF
authorAlvaro Herrera <alvherre@alvh.no-ip.org>
Tue, 8 Sep 2015 00:24:17 +0000 (21:24 -0300)
committerAlvaro Herrera <alvherre@alvh.no-ip.org>
Tue, 8 Sep 2015 00:24:17 +0000 (21:24 -0300)
This new function provides information about SSL extensions present in
the X509 certificate used for the current connection.

Extension version updated to version 1.1.

Author: Дмитрий Воронин (Dmitry Voronin)
Reviewed by: Michael Paquier, Heikki Linnakangas, Álvaro Herrera

contrib/sslinfo/Makefile
contrib/sslinfo/sslinfo--1.0--1.1.sql [new file with mode: 0644]
contrib/sslinfo/sslinfo--1.1.sql [moved from contrib/sslinfo/sslinfo--1.0.sql with 83% similarity]
contrib/sslinfo/sslinfo.c
contrib/sslinfo/sslinfo.control
doc/src/sgml/sslinfo.sgml

index 86cbf053e6b6a5105076479dd1fb5de4c018ec8e..f6c147293cb0b9fcf2aa8840b1d89b987973985c 100644 (file)
@@ -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 (file)
index 0000000..4c26c6e
--- /dev/null
@@ -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;
similarity index 83%
rename from contrib/sslinfo/sslinfo--1.0.sql
rename to contrib/sslinfo/sslinfo--1.1.sql
index 79ce656786f9a762b57ae2924b5152f584fc6fd7..92855e3144989efcfd430d0ca5407db2f2dfd8cf 100644 (file)
@@ -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;
index 6e230052c9ddb0e07a754a39c076ef24fa6c561d..e00f8a2663c22be0c701d32391be2fea1194acec 100644 (file)
@@ -8,22 +8,30 @@
  */
 
 #include "postgres.h"
-#include "fmgr.h"
-#include "utils/numeric.h"
-#include "libpq/libpq-be.h"
-#include "miscadmin.h"
-#include "utils/builtins.h"
-#include "mb/pg_wchar.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"
+
 PG_MODULE_MAGIC;
 
 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);
+}
index 1d2f058f6e245811557466ef70b0ca223ab07517..dfcf17efcfafa7bb15951f790323e745f2203e7a 100644 (file)
@@ -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
index 22ef4396abc98fea3db88cabb3a8059c70b1b884..4203d8fb8a25a86e9a316d142d0dd0a7e2ccd4c0 100644 (file)
@@ -219,6 +219,21 @@ emailAddress
     </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+    <term>
+     <function>ssl_extension_info() returns setof record</function>
+     <indexterm>
+      <primary>ssl_extension_info</primary>
+     </indexterm>
+    </term>
+    <listitem>
+    <para>
+     Provide information about extensions of client certificate: extension name,
+     extension value, and if it is a critical extension.
+    </para>
+    </listitem>
+   </varlistentry>
   </variablelist>
  </sect2>
 
@@ -229,6 +244,10 @@ emailAddress
    Victor Wagner <email>vitus@cryptocom.ru</email>, Cryptocom LTD
   </para>
 
+  <para>
+   Dmitry Voronin <email>carriingfate92@yandex.ru</email>
+  </para>
+
   <para>
    E-Mail of Cryptocom OpenSSL development group:
    <email>openssl@cryptocom.ru</email>