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