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

Generated by: LCOV version 2.3.2-1