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