LCOV - differential code coverage report
Current view: top level - contrib/sslinfo - sslinfo.c (source / functions) Coverage Total Hit UBC CBC
Current: Differential Code Coverage HEAD vs 15 Lines: 79.0 % 138 109 29 109
Current Date: 2023-04-08 15:15:32 Functions: 95.7 % 23 22 1 22
Baseline: 15
Baseline Date: 2023-04-08 15:09:40
Legend: Lines: hit not hit

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

Generated by: LCOV version v1.16-55-g56c0a2a