LCOV - code coverage report
Current view: top level - contrib/sslinfo - sslinfo.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 109 138 79.0 %
Date: 2024-12-12 19:15:15 Functions: 22 23 95.7 %
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             : /*
      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          42 : 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          14 : PG_FUNCTION_INFO_V1(ssl_is_used);
      53             : Datum
      54           2 : ssl_is_used(PG_FUNCTION_ARGS)
      55             : {
      56           2 :     PG_RETURN_BOOL(MyProcPort->ssl_in_use);
      57             : }
      58             : 
      59             : 
      60             : /*
      61             :  * Returns SSL version currently in use.
      62             :  */
      63          14 : PG_FUNCTION_INFO_V1(ssl_version);
      64             : Datum
      65           2 : ssl_version(PG_FUNCTION_ARGS)
      66             : {
      67             :     const char *version;
      68             : 
      69           2 :     if (!MyProcPort->ssl_in_use)
      70           0 :         PG_RETURN_NULL();
      71             : 
      72           2 :     version = be_tls_get_version(MyProcPort);
      73           2 :     if (version == NULL)
      74           0 :         PG_RETURN_NULL();
      75             : 
      76           2 :     PG_RETURN_TEXT_P(cstring_to_text(version));
      77             : }
      78             : 
      79             : 
      80             : /*
      81             :  * Returns SSL cipher currently in use.
      82             :  */
      83          14 : PG_FUNCTION_INFO_V1(ssl_cipher);
      84             : Datum
      85           2 : ssl_cipher(PG_FUNCTION_ARGS)
      86             : {
      87             :     const char *cipher;
      88             : 
      89           2 :     if (!MyProcPort->ssl_in_use)
      90           0 :         PG_RETURN_NULL();
      91             : 
      92           2 :     cipher = be_tls_get_cipher(MyProcPort);
      93           2 :     if (cipher == NULL)
      94           0 :         PG_RETURN_NULL();
      95             : 
      96           2 :     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          22 : PG_FUNCTION_INFO_V1(ssl_client_cert_present);
     107             : Datum
     108          10 : ssl_client_cert_present(PG_FUNCTION_ARGS)
     109             : {
     110          10 :     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          14 : PG_FUNCTION_INFO_V1(ssl_client_serial);
     123             : Datum
     124           2 : ssl_client_serial(PG_FUNCTION_ARGS)
     125             : {
     126             :     char decimal[NAMEDATALEN];
     127             :     Datum       result;
     128             : 
     129           2 :     if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
     130           0 :         PG_RETURN_NULL();
     131             : 
     132           2 :     be_tls_get_peer_serial(MyProcPort, decimal, NAMEDATALEN);
     133             : 
     134           2 :     if (!*decimal)
     135           0 :         PG_RETURN_NULL();
     136             : 
     137           2 :     result = DirectFunctionCall3(numeric_in,
     138             :                                  CStringGetDatum(decimal),
     139             :                                  ObjectIdGetDatum(0),
     140             :                                  Int32GetDatum(-1));
     141           2 :     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           4 : 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           4 :     membuf = BIO_new(BIO_s_mem());
     169           4 :     if (membuf == NULL)
     170           0 :         ereport(ERROR,
     171             :                 (errcode(ERRCODE_OUT_OF_MEMORY),
     172             :                  errmsg("could not create OpenSSL BIO structure")));
     173           4 :     (void) BIO_set_close(membuf, BIO_CLOSE);
     174           4 :     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           4 :     nullterm = '\0';
     179           4 :     BIO_write(membuf, &nullterm, 1);
     180           4 :     size = BIO_get_mem_data(membuf, &sp);
     181           4 :     dp = pg_any_to_server(sp, size - 1, PG_UTF8);
     182           4 :     result = cstring_to_text(dp);
     183           4 :     if (dp != sp)
     184           0 :         pfree(dp);
     185           4 :     if (BIO_free(membuf) != 1)
     186           0 :         elog(ERROR, "could not free OpenSSL BIO structure");
     187             : 
     188           4 :     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           6 : 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           6 :     string_fieldname = text_to_cstring(fieldName);
     213           6 :     nid = OBJ_txt2nid(string_fieldname);
     214           6 :     if (nid == NID_undef)
     215           2 :         ereport(ERROR,
     216             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     217             :                  errmsg("invalid X.509 field name: \"%s\"",
     218             :                         string_fieldname)));
     219           4 :     pfree(string_fieldname);
     220           4 :     index = X509_NAME_get_index_by_NID(name, nid, -1);
     221           4 :     if (index < 0)
     222           0 :         return (Datum) 0;
     223           4 :     data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, index));
     224           4 :     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          18 : PG_FUNCTION_INFO_V1(ssl_client_dn_field);
     245             : Datum
     246           6 : ssl_client_dn_field(PG_FUNCTION_ARGS)
     247             : {
     248           6 :     text       *fieldname = PG_GETARG_TEXT_PP(0);
     249             :     Datum       result;
     250             : 
     251           6 :     if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
     252           2 :         PG_RETURN_NULL();
     253             : 
     254           4 :     result = X509_NAME_field_to_text(X509_get_subject_name(MyProcPort->peer), fieldname);
     255             : 
     256           2 :     if (!result)
     257           0 :         PG_RETURN_NULL();
     258             :     else
     259           2 :         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          14 : PG_FUNCTION_INFO_V1(ssl_issuer_field);
     280             : Datum
     281           2 : ssl_issuer_field(PG_FUNCTION_ARGS)
     282             : {
     283           2 :     text       *fieldname = PG_GETARG_TEXT_PP(0);
     284             :     Datum       result;
     285             : 
     286           2 :     if (!(MyProcPort->peer))
     287           0 :         PG_RETURN_NULL();
     288             : 
     289           2 :     result = X509_NAME_field_to_text(X509_get_issuer_name(MyProcPort->peer), fieldname);
     290             : 
     291           2 :     if (!result)
     292           0 :         PG_RETURN_NULL();
     293             :     else
     294           2 :         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          12 : PG_FUNCTION_INFO_V1(ssl_client_dn);
     308             : Datum
     309           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          14 : PG_FUNCTION_INFO_V1(ssl_issuer_dn);
     335             : Datum
     336           2 : ssl_issuer_dn(PG_FUNCTION_ARGS)
     337             : {
     338             :     char        issuer[NAMEDATALEN];
     339             : 
     340           2 :     if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
     341           0 :         PG_RETURN_NULL();
     342             : 
     343           2 :     be_tls_get_peer_issuer_name(MyProcPort, issuer, NAMEDATALEN);
     344             : 
     345           2 :     if (!*issuer)
     346           0 :         PG_RETURN_NULL();
     347             : 
     348           2 :     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          14 : PG_FUNCTION_INFO_V1(ssl_extension_info);
     361             : Datum
     362          10 : ssl_extension_info(PG_FUNCTION_ARGS)
     363             : {
     364          10 :     X509       *cert = MyProcPort->peer;
     365             :     FuncCallContext *funcctx;
     366             :     int         call_cntr;
     367             :     int         max_calls;
     368             :     MemoryContext oldcontext;
     369             :     SSLExtensionInfoContext *fctx;
     370             : 
     371          10 :     if (SRF_IS_FIRSTCALL())
     372             :     {
     373             : 
     374             :         TupleDesc   tupdesc;
     375             : 
     376             :         /* create a function context for cross-call persistence */
     377           2 :         funcctx = SRF_FIRSTCALL_INIT();
     378             : 
     379             :         /*
     380             :          * Switch to memory context appropriate for multiple function calls
     381             :          */
     382           2 :         oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
     383             : 
     384             :         /* Create a user function context for cross-call persistence */
     385           2 :         fctx = (SSLExtensionInfoContext *) palloc(sizeof(SSLExtensionInfoContext));
     386             : 
     387             :         /* Construct tuple descriptor */
     388           2 :         if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
     389           0 :             ereport(ERROR,
     390             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     391             :                      errmsg("function returning record called in context that cannot accept type record")));
     392           2 :         fctx->tupdesc = BlessTupleDesc(tupdesc);
     393             : 
     394             :         /* Set max_calls as a count of extensions in certificate */
     395           2 :         max_calls = cert != NULL ? X509_get_ext_count(cert) : 0;
     396             : 
     397           2 :         if (max_calls > 0)
     398             :         {
     399             :             /* got results, keep track of them */
     400           2 :             funcctx->max_calls = max_calls;
     401           2 :             funcctx->user_fctx = fctx;
     402             :         }
     403             :         else
     404             :         {
     405             :             /* fast track when no results */
     406           0 :             MemoryContextSwitchTo(oldcontext);
     407           0 :             SRF_RETURN_DONE(funcctx);
     408             :         }
     409             : 
     410           2 :         MemoryContextSwitchTo(oldcontext);
     411             :     }
     412             : 
     413             :     /* stuff done on every call of the function */
     414          10 :     funcctx = SRF_PERCALL_SETUP();
     415             : 
     416             :     /*
     417             :      * Initialize per-call variables.
     418             :      */
     419          10 :     call_cntr = funcctx->call_cntr;
     420          10 :     max_calls = funcctx->max_calls;
     421          10 :     fctx = funcctx->user_fctx;
     422             : 
     423             :     /* do while there are more left to send */
     424          10 :     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           8 :         membuf = BIO_new(BIO_s_mem());
     439           8 :         if (membuf == NULL)
     440           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           8 :         ext = X509_get_ext(cert, call_cntr);
     446           8 :         obj = X509_EXTENSION_get_object(ext);
     447             : 
     448             :         /* Get the extension name */
     449           8 :         nid = OBJ_obj2nid(obj);
     450           8 :         if (nid == NID_undef)
     451           0 :             ereport(ERROR,
     452             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     453             :                      errmsg("unknown OpenSSL extension in certificate at position %d",
     454             :                             call_cntr)));
     455           8 :         values[0] = CStringGetTextDatum(OBJ_nid2sn(nid));
     456           8 :         nulls[0] = false;
     457             : 
     458             :         /* Get the extension value */
     459           8 :         if (X509V3_EXT_print(membuf, ext, 0, 0) <= 0)
     460           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           8 :         len = BIO_get_mem_data(membuf, &buf);
     465           8 :         values[1] = PointerGetDatum(cstring_to_text_with_len(buf, len));
     466           8 :         nulls[1] = false;
     467             : 
     468             :         /* Get critical status */
     469           8 :         values[2] = BoolGetDatum(X509_EXTENSION_get_critical(ext));
     470           8 :         nulls[2] = false;
     471             : 
     472             :         /* Build tuple */
     473           8 :         tuple = heap_form_tuple(fctx->tupdesc, values, nulls);
     474           8 :         result = HeapTupleGetDatum(tuple);
     475             : 
     476           8 :         if (BIO_free(membuf) != 1)
     477           0 :             elog(ERROR, "could not free OpenSSL BIO structure");
     478             : 
     479           8 :         SRF_RETURN_NEXT(funcctx, result);
     480             :     }
     481             : 
     482             :     /* All done */
     483           2 :     SRF_RETURN_DONE(funcctx);
     484             : }

Generated by: LCOV version 1.14