LCOV - code coverage report
Current view: top level - contrib/sslinfo - sslinfo.c (source / functions) Hit Total Coverage
Test: PostgreSQL 12beta1 Lines: 0 160 0.0 %
Date: 2019-05-22 03:21:55 Functions: 0 24 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :  * module for PostgreSQL to access client SSL certificate information
       3             :  *
       4             :  * Written by Victor B. Wagner <vitus@cryptocom.ru>, Cryptocom LTD
       5             :  * This file is distributed under BSD-style license.
       6             :  *
       7             :  * contrib/sslinfo/sslinfo.c
       8             :  */
       9             : 
      10             : #include "postgres.h"
      11             : 
      12             : #include <openssl/x509.h>
      13             : #include <openssl/x509v3.h>
      14             : #include <openssl/asn1.h>
      15             : 
      16             : #include "access/htup_details.h"
      17             : #include "funcapi.h"
      18             : #include "libpq/libpq-be.h"
      19             : #include "miscadmin.h"
      20             : #include "utils/builtins.h"
      21             : 
      22           0 : PG_MODULE_MAGIC;
      23             : 
      24             : static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
      25             : static Datum X509_NAME_to_text(X509_NAME *name);
      26             : static Datum ASN1_STRING_to_text(ASN1_STRING *str);
      27             : 
      28             : /*
      29             :  * Function context for data persisting over repeated calls.
      30             :  */
      31             : typedef struct
      32             : {
      33             :     TupleDesc   tupdesc;
      34             : } SSLExtensionInfoContext;
      35             : 
      36             : /*
      37             :  * Indicates whether current session uses SSL
      38             :  *
      39             :  * Function has no arguments.  Returns bool.  True if current session
      40             :  * is SSL session and false if it is local or non-ssl session.
      41             :  */
      42           0 : PG_FUNCTION_INFO_V1(ssl_is_used);
      43             : Datum
      44           0 : ssl_is_used(PG_FUNCTION_ARGS)
      45             : {
      46           0 :     PG_RETURN_BOOL(MyProcPort->ssl_in_use);
      47             : }
      48             : 
      49             : 
      50             : /*
      51             :  * Returns SSL version currently in use.
      52             :  */
      53           0 : PG_FUNCTION_INFO_V1(ssl_version);
      54             : Datum
      55           0 : ssl_version(PG_FUNCTION_ARGS)
      56             : {
      57           0 :     if (MyProcPort->ssl == NULL)
      58           0 :         PG_RETURN_NULL();
      59           0 :     PG_RETURN_TEXT_P(cstring_to_text(SSL_get_version(MyProcPort->ssl)));
      60             : }
      61             : 
      62             : 
      63             : /*
      64             :  * Returns SSL cipher currently in use.
      65             :  */
      66           0 : PG_FUNCTION_INFO_V1(ssl_cipher);
      67             : Datum
      68           0 : ssl_cipher(PG_FUNCTION_ARGS)
      69             : {
      70           0 :     if (MyProcPort->ssl == NULL)
      71           0 :         PG_RETURN_NULL();
      72           0 :     PG_RETURN_TEXT_P(cstring_to_text(SSL_get_cipher(MyProcPort->ssl)));
      73             : }
      74             : 
      75             : 
      76             : /*
      77             :  * Indicates whether current client provided a certificate
      78             :  *
      79             :  * Function has no arguments.  Returns bool.  True if current session
      80             :  * is SSL session and client certificate is verified, otherwise false.
      81             :  */
      82           0 : PG_FUNCTION_INFO_V1(ssl_client_cert_present);
      83             : Datum
      84           0 : ssl_client_cert_present(PG_FUNCTION_ARGS)
      85             : {
      86           0 :     PG_RETURN_BOOL(MyProcPort->peer != NULL);
      87             : }
      88             : 
      89             : 
      90             : /*
      91             :  * Returns serial number of certificate used to establish current
      92             :  * session
      93             :  *
      94             :  * Function has no arguments.  It returns the certificate serial
      95             :  * number as numeric or null if current session doesn't use SSL or if
      96             :  * SSL connection is established without sending client certificate.
      97             :  */
      98           0 : PG_FUNCTION_INFO_V1(ssl_client_serial);
      99             : Datum
     100           0 : ssl_client_serial(PG_FUNCTION_ARGS)
     101             : {
     102             :     Datum       result;
     103           0 :     Port       *port = MyProcPort;
     104           0 :     X509       *peer = port->peer;
     105           0 :     ASN1_INTEGER *serial = NULL;
     106             :     BIGNUM     *b;
     107             :     char       *decimal;
     108             : 
     109           0 :     if (!peer)
     110           0 :         PG_RETURN_NULL();
     111           0 :     serial = X509_get_serialNumber(peer);
     112           0 :     b = ASN1_INTEGER_to_BN(serial, NULL);
     113           0 :     decimal = BN_bn2dec(b);
     114             : 
     115           0 :     BN_free(b);
     116           0 :     result = DirectFunctionCall3(numeric_in,
     117             :                                  CStringGetDatum(decimal),
     118             :                                  ObjectIdGetDatum(0),
     119             :                                  Int32GetDatum(-1));
     120           0 :     OPENSSL_free(decimal);
     121           0 :     return result;
     122             : }
     123             : 
     124             : 
     125             : /*
     126             :  * Converts OpenSSL ASN1_STRING structure into text
     127             :  *
     128             :  * Converts ASN1_STRING into text, converting all the characters into
     129             :  * current database encoding if possible.  Any invalid characters are
     130             :  * replaced by question marks.
     131             :  *
     132             :  * Parameter: str - OpenSSL ASN1_STRING structure.  Memory management
     133             :  * of this structure is responsibility of caller.
     134             :  *
     135             :  * Returns Datum, which can be directly returned from a C language SQL
     136             :  * function.
     137             :  */
     138             : static Datum
     139           0 : ASN1_STRING_to_text(ASN1_STRING *str)
     140             : {
     141             :     BIO        *membuf;
     142             :     size_t      size;
     143             :     char        nullterm;
     144             :     char       *sp;
     145             :     char       *dp;
     146             :     text       *result;
     147             : 
     148           0 :     membuf = BIO_new(BIO_s_mem());
     149           0 :     if (membuf == NULL)
     150           0 :         ereport(ERROR,
     151             :                 (errcode(ERRCODE_OUT_OF_MEMORY),
     152             :                  errmsg("could not create OpenSSL BIO structure")));
     153           0 :     (void) BIO_set_close(membuf, BIO_CLOSE);
     154           0 :     ASN1_STRING_print_ex(membuf, str,
     155             :                          ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
     156             :                           | ASN1_STRFLGS_UTF8_CONVERT));
     157             :     /* ensure null termination of the BIO's content */
     158           0 :     nullterm = '\0';
     159           0 :     BIO_write(membuf, &nullterm, 1);
     160           0 :     size = BIO_get_mem_data(membuf, &sp);
     161           0 :     dp = pg_any_to_server(sp, size - 1, PG_UTF8);
     162           0 :     result = cstring_to_text(dp);
     163           0 :     if (dp != sp)
     164           0 :         pfree(dp);
     165           0 :     if (BIO_free(membuf) != 1)
     166           0 :         elog(ERROR, "could not free OpenSSL BIO structure");
     167             : 
     168           0 :     PG_RETURN_TEXT_P(result);
     169             : }
     170             : 
     171             : 
     172             : /*
     173             :  * Returns specified field of specified X509_NAME structure
     174             :  *
     175             :  * Common part of ssl_client_dn and ssl_issuer_dn functions.
     176             :  *
     177             :  * Parameter: X509_NAME *name - either subject or issuer of certificate
     178             :  * Parameter: text fieldName  - field name string like 'CN' or commonName
     179             :  *            to be looked up in the OpenSSL ASN1 OID database
     180             :  *
     181             :  * Returns result of ASN1_STRING_to_text applied to appropriate
     182             :  * part of name
     183             :  */
     184             : static Datum
     185           0 : X509_NAME_field_to_text(X509_NAME *name, text *fieldName)
     186             : {
     187             :     char       *string_fieldname;
     188             :     int         nid,
     189             :                 index;
     190             :     ASN1_STRING *data;
     191             : 
     192           0 :     string_fieldname = text_to_cstring(fieldName);
     193           0 :     nid = OBJ_txt2nid(string_fieldname);
     194           0 :     if (nid == NID_undef)
     195           0 :         ereport(ERROR,
     196             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     197             :                  errmsg("invalid X.509 field name: \"%s\"",
     198             :                         string_fieldname)));
     199           0 :     pfree(string_fieldname);
     200           0 :     index = X509_NAME_get_index_by_NID(name, nid, -1);
     201           0 :     if (index < 0)
     202           0 :         return (Datum) 0;
     203           0 :     data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, index));
     204           0 :     return ASN1_STRING_to_text(data);
     205             : }
     206             : 
     207             : 
     208             : /*
     209             :  * Returns specified field of client certificate distinguished name
     210             :  *
     211             :  * Receives field name (like 'commonName' and 'emailAddress') and
     212             :  * returns appropriate part of certificate subject converted into
     213             :  * database encoding.
     214             :  *
     215             :  * Parameter: fieldname text - will be looked up in OpenSSL object
     216             :  * identifier database
     217             :  *
     218             :  * Returns text string with appropriate value.
     219             :  *
     220             :  * Throws an error if argument cannot be converted into ASN1 OID by
     221             :  * OpenSSL.  Returns null if no client certificate is present, or if
     222             :  * there is no field with such name in the certificate.
     223             :  */
     224           0 : PG_FUNCTION_INFO_V1(ssl_client_dn_field);
     225             : Datum
     226           0 : ssl_client_dn_field(PG_FUNCTION_ARGS)
     227             : {
     228           0 :     text       *fieldname = PG_GETARG_TEXT_PP(0);
     229             :     Datum       result;
     230             : 
     231           0 :     if (!(MyProcPort->peer))
     232           0 :         PG_RETURN_NULL();
     233             : 
     234           0 :     result = X509_NAME_field_to_text(X509_get_subject_name(MyProcPort->peer), fieldname);
     235             : 
     236           0 :     if (!result)
     237           0 :         PG_RETURN_NULL();
     238             :     else
     239           0 :         return result;
     240             : }
     241             : 
     242             : 
     243             : /*
     244             :  * Returns specified field of client certificate issuer name
     245             :  *
     246             :  * Receives field name (like 'commonName' and 'emailAddress') and
     247             :  * returns appropriate part of certificate subject converted into
     248             :  * database encoding.
     249             :  *
     250             :  * Parameter: fieldname text - would be looked up in OpenSSL object
     251             :  * identifier database
     252             :  *
     253             :  * Returns text string with appropriate value.
     254             :  *
     255             :  * Throws an error if argument cannot be converted into ASN1 OID by
     256             :  * OpenSSL.  Returns null if no client certificate is present, or if
     257             :  * there is no field with such name in the certificate.
     258             :  */
     259           0 : PG_FUNCTION_INFO_V1(ssl_issuer_field);
     260             : Datum
     261           0 : ssl_issuer_field(PG_FUNCTION_ARGS)
     262             : {
     263           0 :     text       *fieldname = PG_GETARG_TEXT_PP(0);
     264             :     Datum       result;
     265             : 
     266           0 :     if (!(MyProcPort->peer))
     267           0 :         PG_RETURN_NULL();
     268             : 
     269           0 :     result = X509_NAME_field_to_text(X509_get_issuer_name(MyProcPort->peer), fieldname);
     270             : 
     271           0 :     if (!result)
     272           0 :         PG_RETURN_NULL();
     273             :     else
     274           0 :         return result;
     275             : }
     276             : 
     277             : 
     278             : /*
     279             :  * Equivalent of X509_NAME_oneline that respects encoding
     280             :  *
     281             :  * This function converts X509_NAME structure to the text variable
     282             :  * converting all textual data into current database encoding.
     283             :  *
     284             :  * Parameter: X509_NAME *name X509_NAME structure to be converted
     285             :  *
     286             :  * Returns: text datum which contains string representation of
     287             :  * X509_NAME
     288             :  */
     289             : static Datum
     290           0 : X509_NAME_to_text(X509_NAME *name)
     291             : {
     292           0 :     BIO        *membuf = BIO_new(BIO_s_mem());
     293             :     int         i,
     294             :                 nid,
     295           0 :                 count = X509_NAME_entry_count(name);
     296             :     X509_NAME_ENTRY *e;
     297             :     ASN1_STRING *v;
     298             :     const char *field_name;
     299             :     size_t      size;
     300             :     char        nullterm;
     301             :     char       *sp;
     302             :     char       *dp;
     303             :     text       *result;
     304             : 
     305           0 :     if (membuf == NULL)
     306           0 :         ereport(ERROR,
     307             :                 (errcode(ERRCODE_OUT_OF_MEMORY),
     308             :                  errmsg("could not create OpenSSL BIO structure")));
     309             : 
     310           0 :     (void) BIO_set_close(membuf, BIO_CLOSE);
     311           0 :     for (i = 0; i < count; i++)
     312             :     {
     313           0 :         e = X509_NAME_get_entry(name, i);
     314           0 :         nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
     315           0 :         if (nid == NID_undef)
     316           0 :             ereport(ERROR,
     317             :                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     318             :                      errmsg("could not get NID for ASN1_OBJECT object")));
     319           0 :         v = X509_NAME_ENTRY_get_data(e);
     320           0 :         field_name = OBJ_nid2sn(nid);
     321           0 :         if (field_name == NULL)
     322           0 :             field_name = OBJ_nid2ln(nid);
     323           0 :         if (field_name == NULL)
     324           0 :             ereport(ERROR,
     325             :                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     326             :                      errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
     327           0 :         BIO_printf(membuf, "/%s=", field_name);
     328           0 :         ASN1_STRING_print_ex(membuf, v,
     329             :                              ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
     330             :                               | ASN1_STRFLGS_UTF8_CONVERT));
     331             :     }
     332             : 
     333             :     /* ensure null termination of the BIO's content */
     334           0 :     nullterm = '\0';
     335           0 :     BIO_write(membuf, &nullterm, 1);
     336           0 :     size = BIO_get_mem_data(membuf, &sp);
     337           0 :     dp = pg_any_to_server(sp, size - 1, PG_UTF8);
     338           0 :     result = cstring_to_text(dp);
     339           0 :     if (dp != sp)
     340           0 :         pfree(dp);
     341           0 :     if (BIO_free(membuf) != 1)
     342           0 :         elog(ERROR, "could not free OpenSSL BIO structure");
     343             : 
     344           0 :     PG_RETURN_TEXT_P(result);
     345             : }
     346             : 
     347             : 
     348             : /*
     349             :  * Returns current client certificate subject as one string
     350             :  *
     351             :  * This function returns distinguished name (subject) of the client
     352             :  * certificate used in the current SSL connection, converting it into
     353             :  * the current database encoding.
     354             :  *
     355             :  * Returns text datum.
     356             :  */
     357           0 : PG_FUNCTION_INFO_V1(ssl_client_dn);
     358             : Datum
     359           0 : ssl_client_dn(PG_FUNCTION_ARGS)
     360             : {
     361           0 :     if (!(MyProcPort->peer))
     362           0 :         PG_RETURN_NULL();
     363           0 :     return X509_NAME_to_text(X509_get_subject_name(MyProcPort->peer));
     364             : }
     365             : 
     366             : 
     367             : /*
     368             :  * Returns current client certificate issuer as one string
     369             :  *
     370             :  * This function returns issuer's distinguished name of the client
     371             :  * certificate used in the current SSL connection, converting it into
     372             :  * the current database encoding.
     373             :  *
     374             :  * Returns text datum.
     375             :  */
     376           0 : PG_FUNCTION_INFO_V1(ssl_issuer_dn);
     377             : Datum
     378           0 : ssl_issuer_dn(PG_FUNCTION_ARGS)
     379             : {
     380           0 :     if (!(MyProcPort->peer))
     381           0 :         PG_RETURN_NULL();
     382           0 :     return X509_NAME_to_text(X509_get_issuer_name(MyProcPort->peer));
     383             : }
     384             : 
     385             : 
     386             : /*
     387             :  * Returns information about available SSL extensions.
     388             :  *
     389             :  * Returns setof record made of the following values:
     390             :  * - name of the extension.
     391             :  * - value of the extension.
     392             :  * - critical status of the extension.
     393             :  */
     394           0 : PG_FUNCTION_INFO_V1(ssl_extension_info);
     395             : Datum
     396           0 : ssl_extension_info(PG_FUNCTION_ARGS)
     397             : {
     398           0 :     X509       *cert = MyProcPort->peer;
     399             :     FuncCallContext *funcctx;
     400             :     int         call_cntr;
     401             :     int         max_calls;
     402             :     MemoryContext oldcontext;
     403             :     SSLExtensionInfoContext *fctx;
     404             : 
     405           0 :     if (SRF_IS_FIRSTCALL())
     406             :     {
     407             : 
     408             :         TupleDesc   tupdesc;
     409             : 
     410             :         /* create a function context for cross-call persistence */
     411           0 :         funcctx = SRF_FIRSTCALL_INIT();
     412             : 
     413             :         /*
     414             :          * Switch to memory context appropriate for multiple function calls
     415             :          */
     416           0 :         oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
     417             : 
     418             :         /* Create a user function context for cross-call persistence */
     419           0 :         fctx = (SSLExtensionInfoContext *) palloc(sizeof(SSLExtensionInfoContext));
     420             : 
     421             :         /* Construct tuple descriptor */
     422           0 :         if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
     423           0 :             ereport(ERROR,
     424             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     425             :                      errmsg("function returning record called in context that cannot accept type record")));
     426           0 :         fctx->tupdesc = BlessTupleDesc(tupdesc);
     427             : 
     428             :         /* Set max_calls as a count of extensions in certificate */
     429           0 :         max_calls = cert != NULL ? X509_get_ext_count(cert) : 0;
     430             : 
     431           0 :         if (max_calls > 0)
     432             :         {
     433             :             /* got results, keep track of them */
     434           0 :             funcctx->max_calls = max_calls;
     435           0 :             funcctx->user_fctx = fctx;
     436             :         }
     437             :         else
     438             :         {
     439             :             /* fast track when no results */
     440           0 :             MemoryContextSwitchTo(oldcontext);
     441           0 :             SRF_RETURN_DONE(funcctx);
     442             :         }
     443             : 
     444           0 :         MemoryContextSwitchTo(oldcontext);
     445             :     }
     446             : 
     447             :     /* stuff done on every call of the function */
     448           0 :     funcctx = SRF_PERCALL_SETUP();
     449             : 
     450             :     /*
     451             :      * Initialize per-call variables.
     452             :      */
     453           0 :     call_cntr = funcctx->call_cntr;
     454           0 :     max_calls = funcctx->max_calls;
     455           0 :     fctx = funcctx->user_fctx;
     456             : 
     457             :     /* do while there are more left to send */
     458           0 :     if (call_cntr < max_calls)
     459             :     {
     460             :         Datum       values[3];
     461             :         bool        nulls[3];
     462             :         char       *buf;
     463             :         HeapTuple   tuple;
     464             :         Datum       result;
     465             :         BIO        *membuf;
     466             :         X509_EXTENSION *ext;
     467             :         ASN1_OBJECT *obj;
     468             :         int         nid;
     469             :         int         len;
     470             : 
     471             :         /* need a BIO for this */
     472           0 :         membuf = BIO_new(BIO_s_mem());
     473           0 :         if (membuf == NULL)
     474           0 :             ereport(ERROR,
     475             :                     (errcode(ERRCODE_OUT_OF_MEMORY),
     476             :                      errmsg("could not create OpenSSL BIO structure")));
     477             : 
     478             :         /* Get the extension from the certificate */
     479           0 :         ext = X509_get_ext(cert, call_cntr);
     480           0 :         obj = X509_EXTENSION_get_object(ext);
     481             : 
     482             :         /* Get the extension name */
     483           0 :         nid = OBJ_obj2nid(obj);
     484           0 :         if (nid == NID_undef)
     485           0 :             ereport(ERROR,
     486             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     487             :                      errmsg("unknown OpenSSL extension in certificate at position %d",
     488             :                             call_cntr)));
     489           0 :         values[0] = CStringGetTextDatum(OBJ_nid2sn(nid));
     490           0 :         nulls[0] = false;
     491             : 
     492             :         /* Get the extension value */
     493           0 :         if (X509V3_EXT_print(membuf, ext, 0, 0) <= 0)
     494           0 :             ereport(ERROR,
     495             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     496             :                      errmsg("could not print extension value in certificate at position %d",
     497             :                             call_cntr)));
     498           0 :         len = BIO_get_mem_data(membuf, &buf);
     499           0 :         values[1] = PointerGetDatum(cstring_to_text_with_len(buf, len));
     500           0 :         nulls[1] = false;
     501             : 
     502             :         /* Get critical status */
     503           0 :         values[2] = BoolGetDatum(X509_EXTENSION_get_critical(ext));
     504           0 :         nulls[2] = false;
     505             : 
     506             :         /* Build tuple */
     507           0 :         tuple = heap_form_tuple(fctx->tupdesc, values, nulls);
     508           0 :         result = HeapTupleGetDatum(tuple);
     509             : 
     510           0 :         if (BIO_free(membuf) != 1)
     511           0 :             elog(ERROR, "could not free OpenSSL BIO structure");
     512             : 
     513           0 :         SRF_RETURN_NEXT(funcctx, result);
     514             :     }
     515             : 
     516             :     /* All done */
     517           0 :     SRF_RETURN_DONE(funcctx);
     518             : }

Generated by: LCOV version 1.13