LCOV - code coverage report
Current view: top level - contrib/sslinfo - sslinfo.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 79.0 % 138 109
Test Date: 2026-07-25 22:15:46 Functions: 95.7 % 23 22
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 42.5 % 80 34

             Branch data     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                 :          22 : PG_MODULE_MAGIC_EXT(
      23                 :             :                     .name = "sslinfo",
      24                 :             :                     .version = PG_VERSION
      25                 :             : );
      26                 :             : 
      27                 :             : static Datum X509_NAME_field_to_text(const X509_NAME *name, text *fieldName);
      28                 :             : static Datum ASN1_STRING_to_text(const 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                 :           7 : PG_FUNCTION_INFO_V1(ssl_is_used);
      45                 :             : Datum
      46                 :           1 : ssl_is_used(PG_FUNCTION_ARGS)
      47                 :             : {
      48                 :           1 :     PG_RETURN_BOOL(MyProcPort->ssl_in_use);
      49                 :             : }
      50                 :             : 
      51                 :             : 
      52                 :             : /*
      53                 :             :  * Returns SSL version currently in use.
      54                 :             :  */
      55                 :           7 : PG_FUNCTION_INFO_V1(ssl_version);
      56                 :             : Datum
      57                 :           1 : ssl_version(PG_FUNCTION_ARGS)
      58                 :             : {
      59                 :             :     const char *version;
      60                 :             : 
      61         [ -  + ]:           1 :     if (!MyProcPort->ssl_in_use)
      62                 :           0 :         PG_RETURN_NULL();
      63                 :             : 
      64                 :           1 :     version = be_tls_get_version(MyProcPort);
      65         [ -  + ]:           1 :     if (version == NULL)
      66                 :           0 :         PG_RETURN_NULL();
      67                 :             : 
      68                 :           1 :     PG_RETURN_TEXT_P(cstring_to_text(version));
      69                 :             : }
      70                 :             : 
      71                 :             : 
      72                 :             : /*
      73                 :             :  * Returns SSL cipher currently in use.
      74                 :             :  */
      75                 :           7 : PG_FUNCTION_INFO_V1(ssl_cipher);
      76                 :             : Datum
      77                 :           1 : ssl_cipher(PG_FUNCTION_ARGS)
      78                 :             : {
      79                 :             :     const char *cipher;
      80                 :             : 
      81         [ -  + ]:           1 :     if (!MyProcPort->ssl_in_use)
      82                 :           0 :         PG_RETURN_NULL();
      83                 :             : 
      84                 :           1 :     cipher = be_tls_get_cipher(MyProcPort);
      85         [ -  + ]:           1 :     if (cipher == NULL)
      86                 :           0 :         PG_RETURN_NULL();
      87                 :             : 
      88                 :           1 :     PG_RETURN_TEXT_P(cstring_to_text(cipher));
      89                 :             : }
      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                 :          12 : PG_FUNCTION_INFO_V1(ssl_client_cert_present);
      99                 :             : Datum
     100                 :           6 : ssl_client_cert_present(PG_FUNCTION_ARGS)
     101                 :             : {
     102                 :           6 :     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                 :           7 : PG_FUNCTION_INFO_V1(ssl_client_serial);
     115                 :             : Datum
     116                 :           1 : ssl_client_serial(PG_FUNCTION_ARGS)
     117                 :             : {
     118                 :             :     char decimal[NAMEDATALEN];
     119                 :             :     Datum       result;
     120                 :             : 
     121   [ +  -  -  + ]:           1 :     if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
     122                 :           0 :         PG_RETURN_NULL();
     123                 :             : 
     124                 :           1 :     be_tls_get_peer_serial(MyProcPort, decimal, NAMEDATALEN);
     125                 :             : 
     126         [ -  + ]:           1 :     if (!*decimal)
     127                 :           0 :         PG_RETURN_NULL();
     128                 :             : 
     129                 :           1 :     result = DirectFunctionCall3(numeric_in,
     130                 :             :                                  CStringGetDatum(decimal),
     131                 :             :                                  ObjectIdGetDatum(0),
     132                 :             :                                  Int32GetDatum(-1));
     133                 :           1 :     return result;
     134                 :             : }
     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                 :           2 : ASN1_STRING_to_text(const ASN1_STRING *str)
     152                 :             : {
     153                 :             :     BIO        *membuf;
     154                 :             :     size_t      size;
     155                 :             :     char        nullterm;
     156                 :             :     char       *sp;
     157                 :             :     char       *dp;
     158                 :             :     text       *result;
     159                 :             : 
     160                 :           2 :     membuf = BIO_new(BIO_s_mem());
     161         [ -  + ]:           2 :     if (membuf == NULL)
     162         [ #  # ]:           0 :         ereport(ERROR,
     163                 :             :                 (errcode(ERRCODE_OUT_OF_MEMORY),
     164                 :             :                  errmsg("could not create OpenSSL BIO structure")));
     165                 :           2 :     (void) BIO_set_close(membuf, BIO_CLOSE);
     166                 :           2 :     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                 :           2 :     nullterm = '\0';
     171                 :           2 :     BIO_write(membuf, &nullterm, 1);
     172                 :           2 :     size = BIO_get_mem_data(membuf, &sp);
     173                 :           2 :     dp = pg_any_to_server(sp, size - 1, PG_UTF8);
     174                 :           2 :     result = cstring_to_text(dp);
     175         [ -  + ]:           2 :     if (dp != sp)
     176                 :           0 :         pfree(dp);
     177         [ -  + ]:           2 :     if (BIO_free(membuf) != 1)
     178         [ #  # ]:           0 :         elog(ERROR, "could not free OpenSSL BIO structure");
     179                 :             : 
     180                 :           2 :     PG_RETURN_TEXT_P(result);
     181                 :             : }
     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                 :           3 : X509_NAME_field_to_text(const X509_NAME *name, text *fieldName)
     198                 :             : {
     199                 :             :     char       *string_fieldname;
     200                 :             :     int         nid,
     201                 :             :                 index;
     202                 :             :     const ASN1_STRING *data;
     203                 :             : 
     204                 :           3 :     string_fieldname = text_to_cstring(fieldName);
     205                 :           3 :     nid = OBJ_txt2nid(string_fieldname);
     206         [ +  + ]:           3 :     if (nid == NID_undef)
     207         [ +  - ]:           1 :         ereport(ERROR,
     208                 :             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     209                 :             :                  errmsg("invalid X.509 field name: \"%s\"",
     210                 :             :                         string_fieldname)));
     211                 :           2 :     pfree(string_fieldname);
     212                 :           2 :     index = X509_NAME_get_index_by_NID(unconstify(X509_NAME *, name), nid, -1);
     213         [ -  + ]:           2 :     if (index < 0)
     214                 :           0 :         return (Datum) 0;
     215                 :           2 :     data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, index));
     216                 :           2 :     return ASN1_STRING_to_text(data);
     217                 :             : }
     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                 :           9 : PG_FUNCTION_INFO_V1(ssl_client_dn_field);
     237                 :             : Datum
     238                 :           3 : ssl_client_dn_field(PG_FUNCTION_ARGS)
     239                 :             : {
     240                 :           3 :     text       *fieldname = PG_GETARG_TEXT_PP(0);
     241                 :             :     Datum       result;
     242                 :             : 
     243   [ +  -  +  + ]:           3 :     if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
     244                 :           1 :         PG_RETURN_NULL();
     245                 :             : 
     246                 :           2 :     result = X509_NAME_field_to_text(X509_get_subject_name(MyProcPort->peer), fieldname);
     247                 :             : 
     248         [ -  + ]:           1 :     if (!result)
     249                 :           0 :         PG_RETURN_NULL();
     250                 :             :     else
     251                 :           1 :         return result;
     252                 :             : }
     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                 :           7 : PG_FUNCTION_INFO_V1(ssl_issuer_field);
     272                 :             : Datum
     273                 :           1 : ssl_issuer_field(PG_FUNCTION_ARGS)
     274                 :             : {
     275                 :           1 :     text       *fieldname = PG_GETARG_TEXT_PP(0);
     276                 :             :     Datum       result;
     277                 :             : 
     278         [ -  + ]:           1 :     if (!(MyProcPort->peer))
     279                 :           0 :         PG_RETURN_NULL();
     280                 :             : 
     281                 :           1 :     result = X509_NAME_field_to_text(X509_get_issuer_name(MyProcPort->peer), fieldname);
     282                 :             : 
     283         [ -  + ]:           1 :     if (!result)
     284                 :           0 :         PG_RETURN_NULL();
     285                 :             :     else
     286                 :           1 :         return result;
     287                 :             : }
     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                 :           6 : PG_FUNCTION_INFO_V1(ssl_client_dn);
     300                 :             : Datum
     301                 :           0 : ssl_client_dn(PG_FUNCTION_ARGS)
     302                 :             : {
     303                 :             :     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                 :             : }
     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                 :           7 : PG_FUNCTION_INFO_V1(ssl_issuer_dn);
     327                 :             : Datum
     328                 :           1 : ssl_issuer_dn(PG_FUNCTION_ARGS)
     329                 :             : {
     330                 :             :     char        issuer[NAMEDATALEN];
     331                 :             : 
     332   [ +  -  -  + ]:           1 :     if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
     333                 :           0 :         PG_RETURN_NULL();
     334                 :             : 
     335                 :           1 :     be_tls_get_peer_issuer_name(MyProcPort, issuer, NAMEDATALEN);
     336                 :             : 
     337         [ -  + ]:           1 :     if (!*issuer)
     338                 :           0 :         PG_RETURN_NULL();
     339                 :             : 
     340                 :           1 :     PG_RETURN_TEXT_P(cstring_to_text(issuer));
     341                 :             : }
     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                 :           7 : PG_FUNCTION_INFO_V1(ssl_extension_info);
     353                 :             : Datum
     354                 :           5 : ssl_extension_info(PG_FUNCTION_ARGS)
     355                 :             : {
     356                 :           5 :     X509       *cert = MyProcPort->peer;
     357                 :             :     FuncCallContext *funcctx;
     358                 :             :     int         call_cntr;
     359                 :             :     int         max_calls;
     360                 :             :     MemoryContext oldcontext;
     361                 :             :     SSLExtensionInfoContext *fctx;
     362                 :             : 
     363         [ +  + ]:           5 :     if (SRF_IS_FIRSTCALL())
     364                 :             :     {
     365                 :             : 
     366                 :             :         TupleDesc   tupdesc;
     367                 :             : 
     368                 :             :         /* create a function context for cross-call persistence */
     369                 :           1 :         funcctx = SRF_FIRSTCALL_INIT();
     370                 :             : 
     371                 :             :         /*
     372                 :             :          * Switch to memory context appropriate for multiple function calls
     373                 :             :          */
     374                 :           1 :         oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
     375                 :             : 
     376                 :             :         /* Create a user function context for cross-call persistence */
     377                 :           1 :         fctx = palloc_object(SSLExtensionInfoContext);
     378                 :             : 
     379                 :             :         /* Construct tuple descriptor */
     380         [ -  + ]:           1 :         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                 :           1 :         fctx->tupdesc = BlessTupleDesc(tupdesc);
     385                 :             : 
     386                 :             :         /* Set max_calls as a count of extensions in certificate */
     387         [ +  - ]:           1 :         max_calls = cert != NULL ? X509_get_ext_count(cert) : 0;
     388                 :             : 
     389         [ +  - ]:           1 :         if (max_calls > 0)
     390                 :             :         {
     391                 :             :             /* got results, keep track of them */
     392                 :           1 :             funcctx->max_calls = max_calls;
     393                 :           1 :             funcctx->user_fctx = fctx;
     394                 :             :         }
     395                 :             :         else
     396                 :             :         {
     397                 :             :             /* fast track when no results */
     398                 :           0 :             MemoryContextSwitchTo(oldcontext);
     399                 :           0 :             SRF_RETURN_DONE(funcctx);
     400                 :             :         }
     401                 :             : 
     402                 :           1 :         MemoryContextSwitchTo(oldcontext);
     403                 :             :     }
     404                 :             : 
     405                 :             :     /* stuff done on every call of the function */
     406                 :           5 :     funcctx = SRF_PERCALL_SETUP();
     407                 :             : 
     408                 :             :     /*
     409                 :             :      * Initialize per-call variables.
     410                 :             :      */
     411                 :           5 :     call_cntr = funcctx->call_cntr;
     412                 :           5 :     max_calls = funcctx->max_calls;
     413                 :           5 :     fctx = funcctx->user_fctx;
     414                 :             : 
     415                 :             :     /* do while there are more left to send */
     416         [ +  + ]:           5 :     if (call_cntr < max_calls)
     417                 :             :     {
     418                 :             :         Datum       values[3];
     419                 :             :         bool        nulls[3];
     420                 :             :         char       *buf;
     421                 :             :         HeapTuple   tuple;
     422                 :             :         Datum       result;
     423                 :             :         BIO        *membuf;
     424                 :             :         const X509_EXTENSION *ext;
     425                 :             :         const ASN1_OBJECT *obj;
     426                 :             :         int         nid;
     427                 :             :         int         len;
     428                 :             : 
     429                 :             :         /* need a BIO for this */
     430                 :           4 :         membuf = BIO_new(BIO_s_mem());
     431         [ -  + ]:           4 :         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                 :           4 :         ext = X509_get_ext(cert, call_cntr);
     438                 :           4 :         obj = X509_EXTENSION_get_object(unconstify(X509_EXTENSION *, ext));
     439                 :             : 
     440                 :             :         /* Get the extension name */
     441                 :           4 :         nid = OBJ_obj2nid(obj);
     442         [ -  + ]:           4 :         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                 :           4 :         values[0] = CStringGetTextDatum(OBJ_nid2sn(nid));
     448                 :           4 :         nulls[0] = false;
     449                 :             : 
     450                 :             :         /* Get the extension value */
     451         [ -  + ]:           4 :         if (X509V3_EXT_print(membuf, unconstify(X509_EXTENSION *, 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                 :           4 :         len = BIO_get_mem_data(membuf, &buf);
     457                 :           4 :         values[1] = PointerGetDatum(cstring_to_text_with_len(buf, len));
     458                 :           4 :         nulls[1] = false;
     459                 :             : 
     460                 :             :         /* Get critical status */
     461                 :           4 :         values[2] = BoolGetDatum(X509_EXTENSION_get_critical(ext));
     462                 :           4 :         nulls[2] = false;
     463                 :             : 
     464                 :             :         /* Build tuple */
     465                 :           4 :         tuple = heap_form_tuple(fctx->tupdesc, values, nulls);
     466                 :           4 :         result = HeapTupleGetDatum(tuple);
     467                 :             : 
     468         [ -  + ]:           4 :         if (BIO_free(membuf) != 1)
     469         [ #  # ]:           0 :             elog(ERROR, "could not free OpenSSL BIO structure");
     470                 :             : 
     471                 :           4 :         SRF_RETURN_NEXT(funcctx, result);
     472                 :             :     }
     473                 :             : 
     474                 :             :     /* All done */
     475                 :           1 :     SRF_RETURN_DONE(funcctx);
     476                 :             : }
        

Generated by: LCOV version 2.0-1