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 : }
|