Age Owner Branch data 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 : :
164 tgl@sss.pgh.pa.us 22 :CBC 20 : PG_MODULE_MAGIC_EXT(
23 : : .name = "sslinfo",
24 : : .version = PG_VERSION
25 : : );
26 : :
27 : : static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
28 : : static Datum ASN1_STRING_to_text(ASN1_STRING *str);
29 : :
30 : : /*
31 : : * Function context for data persisting over repeated calls.
32 : : */
33 : : typedef struct
34 : : {
35 : : TupleDesc tupdesc;
36 : : } SSLExtensionInfoContext;
37 : :
38 : : /*
39 : : * Indicates whether current session uses SSL
40 : : *
41 : : * Function has no arguments. Returns bool. True if current session
42 : : * is SSL session and false if it is local or non-ssl session.
43 : : */
6942 peter_e@gmx.net 44 : 7 : PG_FUNCTION_INFO_V1(ssl_is_used);
45 : : Datum
6912 bruce@momjian.us 46 : 1 : ssl_is_used(PG_FUNCTION_ARGS)
47 : : {
3938 heikki.linnakangas@i 48 : 1 : PG_RETURN_BOOL(MyProcPort->ssl_in_use);
49 : : }
50 : :
51 : :
52 : : /*
53 : : * Returns SSL version currently in use.
54 : : */
5520 rhaas@postgresql.org 55 : 7 : PG_FUNCTION_INFO_V1(ssl_version);
56 : : Datum
57 : 1 : ssl_version(PG_FUNCTION_ARGS)
58 : : {
59 : : const char *version;
60 : :
1768 magnus@hagander.net 61 [ - + ]: 1 : if (!MyProcPort->ssl_in_use)
1768 magnus@hagander.net 62 :UBC 0 : PG_RETURN_NULL();
63 : :
1768 magnus@hagander.net 64 :CBC 1 : version = be_tls_get_version(MyProcPort);
65 [ - + ]: 1 : if (version == NULL)
5520 rhaas@postgresql.org 66 :UBC 0 : PG_RETURN_NULL();
67 : :
1768 magnus@hagander.net 68 :CBC 1 : PG_RETURN_TEXT_P(cstring_to_text(version));
69 : : }
70 : :
71 : :
72 : : /*
73 : : * Returns SSL cipher currently in use.
74 : : */
5520 rhaas@postgresql.org 75 : 7 : PG_FUNCTION_INFO_V1(ssl_cipher);
76 : : Datum
77 : 1 : ssl_cipher(PG_FUNCTION_ARGS)
78 : : {
79 : : const char *cipher;
80 : :
1768 magnus@hagander.net 81 [ - + ]: 1 : if (!MyProcPort->ssl_in_use)
1768 magnus@hagander.net 82 :UBC 0 : PG_RETURN_NULL();
83 : :
1768 magnus@hagander.net 84 :CBC 1 : cipher = be_tls_get_cipher(MyProcPort);
85 [ - + ]: 1 : if (cipher == NULL)
5520 rhaas@postgresql.org 86 :UBC 0 : PG_RETURN_NULL();
87 : :
1768 magnus@hagander.net 88 :CBC 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 : : */
6942 peter_e@gmx.net 98 : 10 : PG_FUNCTION_INFO_V1(ssl_client_cert_present);
99 : : Datum
6912 bruce@momjian.us 100 : 4 : ssl_client_cert_present(PG_FUNCTION_ARGS)
101 : : {
1768 magnus@hagander.net 102 : 4 : 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 : : */
6942 peter_e@gmx.net 114 : 7 : PG_FUNCTION_INFO_V1(ssl_client_serial);
115 : : Datum
6912 bruce@momjian.us 116 : 1 : ssl_client_serial(PG_FUNCTION_ARGS)
117 : : {
118 : : char decimal[NAMEDATALEN];
119 : : Datum result;
120 : :
1768 magnus@hagander.net 121 [ + - - + ]: 1 : if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
1768 magnus@hagander.net 122 :UBC 0 : PG_RETURN_NULL();
123 : :
1768 magnus@hagander.net 124 :CBC 1 : be_tls_get_peer_serial(MyProcPort, decimal, NAMEDATALEN);
125 : :
126 [ - + ]: 1 : if (!*decimal)
6942 peter_e@gmx.net 127 :UBC 0 : PG_RETURN_NULL();
128 : :
6942 peter_e@gmx.net 129 :CBC 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
6912 bruce@momjian.us 151 : 2 : ASN1_STRING_to_text(ASN1_STRING *str)
152 : : {
153 : : BIO *membuf;
154 : : size_t size;
155 : : char nullterm;
156 : : char *sp;
157 : : char *dp;
158 : : text *result;
159 : :
6942 peter_e@gmx.net 160 : 2 : membuf = BIO_new(BIO_s_mem());
3652 alvherre@alvh.no-ip. 161 [ - + ]: 2 : if (membuf == NULL)
3652 alvherre@alvh.no-ip. 162 [ # # ]:UBC 0 : ereport(ERROR,
163 : : (errcode(ERRCODE_OUT_OF_MEMORY),
164 : : errmsg("could not create OpenSSL BIO structure")));
6916 tgl@sss.pgh.pa.us 165 :CBC 2 : (void) BIO_set_close(membuf, BIO_CLOSE);
6912 bruce@momjian.us 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 */
6144 tgl@sss.pgh.pa.us 170 : 2 : nullterm = '\0';
171 : 2 : BIO_write(membuf, &nullterm, 1);
6942 peter_e@gmx.net 172 : 2 : size = BIO_get_mem_data(membuf, &sp);
4213 tgl@sss.pgh.pa.us 173 : 2 : dp = pg_any_to_server(sp, size - 1, PG_UTF8);
6374 174 : 2 : result = cstring_to_text(dp);
6942 peter_e@gmx.net 175 [ - + ]: 2 : if (dp != sp)
6942 peter_e@gmx.net 176 :UBC 0 : pfree(dp);
3652 alvherre@alvh.no-ip. 177 [ - + ]:CBC 2 : if (BIO_free(membuf) != 1)
3651 alvherre@alvh.no-ip. 178 [ # # ]:UBC 0 : elog(ERROR, "could not free OpenSSL BIO structure");
179 : :
6942 peter_e@gmx.net 180 :CBC 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
6912 bruce@momjian.us 197 : 3 : X509_NAME_field_to_text(X509_NAME *name, text *fieldName)
198 : : {
199 : : char *string_fieldname;
200 : : int nid,
201 : : index;
202 : : ASN1_STRING *data;
203 : :
6374 tgl@sss.pgh.pa.us 204 : 3 : string_fieldname = text_to_cstring(fieldName);
6942 peter_e@gmx.net 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(name, nid, -1);
213 [ - + ]: 2 : if (index < 0)
6912 bruce@momjian.us 214 :UBC 0 : return (Datum) 0;
6942 peter_e@gmx.net 215 :CBC 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
6912 bruce@momjian.us 238 : 3 : ssl_client_dn_field(PG_FUNCTION_ARGS)
239 : : {
3100 noah@leadboat.com 240 : 3 : text *fieldname = PG_GETARG_TEXT_PP(0);
241 : : Datum result;
242 : :
1768 magnus@hagander.net 243 [ + - + + ]: 3 : if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
6942 peter_e@gmx.net 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)
6942 peter_e@gmx.net 249 :UBC 0 : PG_RETURN_NULL();
250 : : else
6942 peter_e@gmx.net 251 :CBC 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
6912 bruce@momjian.us 273 : 1 : ssl_issuer_field(PG_FUNCTION_ARGS)
274 : : {
3100 noah@leadboat.com 275 : 1 : text *fieldname = PG_GETARG_TEXT_PP(0);
276 : : Datum result;
277 : :
6942 peter_e@gmx.net 278 [ - + ]: 1 : if (!(MyProcPort->peer))
6942 peter_e@gmx.net 279 :UBC 0 : PG_RETURN_NULL();
280 : :
6942 peter_e@gmx.net 281 :CBC 1 : result = X509_NAME_field_to_text(X509_get_issuer_name(MyProcPort->peer), fieldname);
282 : :
283 [ - + ]: 1 : if (!result)
6942 peter_e@gmx.net 284 :UBC 0 : PG_RETURN_NULL();
285 : : else
6942 peter_e@gmx.net 286 :CBC 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
6912 bruce@momjian.us 301 :UBC 0 : ssl_client_dn(PG_FUNCTION_ARGS)
302 : : {
303 : : char subject[NAMEDATALEN];
304 : :
1768 magnus@hagander.net 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)
6942 peter_e@gmx.net 311 : 0 : PG_RETURN_NULL();
312 : :
1768 magnus@hagander.net 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 : : */
6942 peter_e@gmx.net 326 :CBC 7 : PG_FUNCTION_INFO_V1(ssl_issuer_dn);
327 : : Datum
6912 bruce@momjian.us 328 : 1 : ssl_issuer_dn(PG_FUNCTION_ARGS)
329 : : {
330 : : char issuer[NAMEDATALEN];
331 : :
1768 magnus@hagander.net 332 [ + - - + ]: 1 : if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
6942 peter_e@gmx.net 333 :UBC 0 : PG_RETURN_NULL();
334 : :
1768 magnus@hagander.net 335 :CBC 1 : be_tls_get_peer_issuer_name(MyProcPort, issuer, NAMEDATALEN);
336 : :
337 [ - + ]: 1 : if (!*issuer)
1768 magnus@hagander.net 338 :UBC 0 : PG_RETURN_NULL();
339 : :
1768 magnus@hagander.net 340 :CBC 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 : : */
3652 alvherre@alvh.no-ip. 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 = (SSLExtensionInfoContext *) palloc(sizeof(SSLExtensionInfoContext));
378 : :
379 : : /* Construct tuple descriptor */
380 [ - + ]: 1 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
3652 alvherre@alvh.no-ip. 381 [ # # ]:UBC 0 : ereport(ERROR,
382 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
383 : : errmsg("function returning record called in context that cannot accept type record")));
3652 alvherre@alvh.no-ip. 384 :CBC 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 : :
3278 heikki.linnakangas@i 389 [ + - ]: 1 : if (max_calls > 0)
390 : : {
391 : : /* got results, keep track of them */
3652 alvherre@alvh.no-ip. 392 : 1 : funcctx->max_calls = max_calls;
393 : 1 : funcctx->user_fctx = fctx;
394 : : }
395 : : else
396 : : {
397 : : /* fast track when no results */
3652 alvherre@alvh.no-ip. 398 :UBC 0 : MemoryContextSwitchTo(oldcontext);
399 : 0 : SRF_RETURN_DONE(funcctx);
400 : : }
401 : :
3652 alvherre@alvh.no-ip. 402 :CBC 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 : : X509_EXTENSION *ext;
425 : : 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)
3652 alvherre@alvh.no-ip. 432 [ # # ]:UBC 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 */
3278 heikki.linnakangas@i 437 :CBC 4 : ext = X509_get_ext(cert, call_cntr);
3652 alvherre@alvh.no-ip. 438 : 4 : obj = X509_EXTENSION_get_object(ext);
439 : :
440 : : /* Get the extension name */
441 : 4 : nid = OBJ_obj2nid(obj);
442 [ - + ]: 4 : if (nid == NID_undef)
3652 alvherre@alvh.no-ip. 443 [ # # ]:UBC 0 : ereport(ERROR,
444 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
445 : : errmsg("unknown OpenSSL extension in certificate at position %d",
446 : : call_cntr)));
3652 alvherre@alvh.no-ip. 447 :CBC 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, ext, 0, 0) <= 0)
3652 alvherre@alvh.no-ip. 452 [ # # ]:UBC 0 : ereport(ERROR,
453 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
454 : : errmsg("could not print extension value in certificate at position %d",
455 : : call_cntr)));
3652 alvherre@alvh.no-ip. 456 :CBC 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)
3652 alvherre@alvh.no-ip. 469 [ # # ]:UBC 0 : elog(ERROR, "could not free OpenSSL BIO structure");
470 : :
3652 alvherre@alvh.no-ip. 471 :CBC 4 : SRF_RETURN_NEXT(funcctx, result);
472 : : }
473 : :
474 : : /* All done */
475 : 1 : SRF_RETURN_DONE(funcctx);
476 : : }
|