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 44 : 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 : */
44 14 : PG_FUNCTION_INFO_V1(ssl_is_used);
45 : Datum
46 2 : ssl_is_used(PG_FUNCTION_ARGS)
47 : {
48 2 : PG_RETURN_BOOL(MyProcPort->ssl_in_use);
49 : }
50 :
51 :
52 : /*
53 : * Returns SSL version currently in use.
54 : */
55 14 : PG_FUNCTION_INFO_V1(ssl_version);
56 : Datum
57 2 : ssl_version(PG_FUNCTION_ARGS)
58 : {
59 : const char *version;
60 :
61 2 : if (!MyProcPort->ssl_in_use)
62 0 : PG_RETURN_NULL();
63 :
64 2 : version = be_tls_get_version(MyProcPort);
65 2 : if (version == NULL)
66 0 : PG_RETURN_NULL();
67 :
68 2 : PG_RETURN_TEXT_P(cstring_to_text(version));
69 : }
70 :
71 :
72 : /*
73 : * Returns SSL cipher currently in use.
74 : */
75 14 : PG_FUNCTION_INFO_V1(ssl_cipher);
76 : Datum
77 2 : ssl_cipher(PG_FUNCTION_ARGS)
78 : {
79 : const char *cipher;
80 :
81 2 : if (!MyProcPort->ssl_in_use)
82 0 : PG_RETURN_NULL();
83 :
84 2 : cipher = be_tls_get_cipher(MyProcPort);
85 2 : if (cipher == NULL)
86 0 : PG_RETURN_NULL();
87 :
88 2 : 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 : */
98 24 : PG_FUNCTION_INFO_V1(ssl_client_cert_present);
99 : Datum
100 12 : ssl_client_cert_present(PG_FUNCTION_ARGS)
101 : {
102 12 : 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 : */
114 14 : PG_FUNCTION_INFO_V1(ssl_client_serial);
115 : Datum
116 2 : ssl_client_serial(PG_FUNCTION_ARGS)
117 : {
118 : char decimal[NAMEDATALEN];
119 : Datum result;
120 :
121 2 : if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
122 0 : PG_RETURN_NULL();
123 :
124 2 : be_tls_get_peer_serial(MyProcPort, decimal, NAMEDATALEN);
125 :
126 2 : if (!*decimal)
127 0 : PG_RETURN_NULL();
128 :
129 2 : result = DirectFunctionCall3(numeric_in,
130 : CStringGetDatum(decimal),
131 : ObjectIdGetDatum(0),
132 : Int32GetDatum(-1));
133 2 : 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
151 4 : 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 :
160 4 : membuf = BIO_new(BIO_s_mem());
161 4 : if (membuf == NULL)
162 0 : ereport(ERROR,
163 : (errcode(ERRCODE_OUT_OF_MEMORY),
164 : errmsg("could not create OpenSSL BIO structure")));
165 4 : (void) BIO_set_close(membuf, BIO_CLOSE);
166 4 : 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 */
170 4 : nullterm = '\0';
171 4 : BIO_write(membuf, &nullterm, 1);
172 4 : size = BIO_get_mem_data(membuf, &sp);
173 4 : dp = pg_any_to_server(sp, size - 1, PG_UTF8);
174 4 : result = cstring_to_text(dp);
175 4 : if (dp != sp)
176 0 : pfree(dp);
177 4 : if (BIO_free(membuf) != 1)
178 0 : elog(ERROR, "could not free OpenSSL BIO structure");
179 :
180 4 : 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
197 6 : 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 :
204 6 : string_fieldname = text_to_cstring(fieldName);
205 6 : nid = OBJ_txt2nid(string_fieldname);
206 6 : if (nid == NID_undef)
207 2 : ereport(ERROR,
208 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
209 : errmsg("invalid X.509 field name: \"%s\"",
210 : string_fieldname)));
211 4 : pfree(string_fieldname);
212 4 : index = X509_NAME_get_index_by_NID(name, nid, -1);
213 4 : if (index < 0)
214 0 : return (Datum) 0;
215 4 : data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, index));
216 4 : 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 18 : PG_FUNCTION_INFO_V1(ssl_client_dn_field);
237 : Datum
238 6 : ssl_client_dn_field(PG_FUNCTION_ARGS)
239 : {
240 6 : text *fieldname = PG_GETARG_TEXT_PP(0);
241 : Datum result;
242 :
243 6 : if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
244 2 : PG_RETURN_NULL();
245 :
246 4 : result = X509_NAME_field_to_text(X509_get_subject_name(MyProcPort->peer), fieldname);
247 :
248 2 : if (!result)
249 0 : PG_RETURN_NULL();
250 : else
251 2 : 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 14 : PG_FUNCTION_INFO_V1(ssl_issuer_field);
272 : Datum
273 2 : ssl_issuer_field(PG_FUNCTION_ARGS)
274 : {
275 2 : text *fieldname = PG_GETARG_TEXT_PP(0);
276 : Datum result;
277 :
278 2 : if (!(MyProcPort->peer))
279 0 : PG_RETURN_NULL();
280 :
281 2 : result = X509_NAME_field_to_text(X509_get_issuer_name(MyProcPort->peer), fieldname);
282 :
283 2 : if (!result)
284 0 : PG_RETURN_NULL();
285 : else
286 2 : 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 12 : PG_FUNCTION_INFO_V1(ssl_client_dn);
300 : Datum
301 0 : ssl_client_dn(PG_FUNCTION_ARGS)
302 : {
303 : char subject[NAMEDATALEN];
304 :
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)
311 0 : PG_RETURN_NULL();
312 :
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 : */
326 14 : PG_FUNCTION_INFO_V1(ssl_issuer_dn);
327 : Datum
328 2 : ssl_issuer_dn(PG_FUNCTION_ARGS)
329 : {
330 : char issuer[NAMEDATALEN];
331 :
332 2 : if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
333 0 : PG_RETURN_NULL();
334 :
335 2 : be_tls_get_peer_issuer_name(MyProcPort, issuer, NAMEDATALEN);
336 :
337 2 : if (!*issuer)
338 0 : PG_RETURN_NULL();
339 :
340 2 : 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 : */
352 14 : PG_FUNCTION_INFO_V1(ssl_extension_info);
353 : Datum
354 10 : ssl_extension_info(PG_FUNCTION_ARGS)
355 : {
356 10 : X509 *cert = MyProcPort->peer;
357 : FuncCallContext *funcctx;
358 : int call_cntr;
359 : int max_calls;
360 : MemoryContext oldcontext;
361 : SSLExtensionInfoContext *fctx;
362 :
363 10 : if (SRF_IS_FIRSTCALL())
364 : {
365 :
366 : TupleDesc tupdesc;
367 :
368 : /* create a function context for cross-call persistence */
369 2 : funcctx = SRF_FIRSTCALL_INIT();
370 :
371 : /*
372 : * Switch to memory context appropriate for multiple function calls
373 : */
374 2 : oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
375 :
376 : /* Create a user function context for cross-call persistence */
377 2 : fctx = (SSLExtensionInfoContext *) palloc(sizeof(SSLExtensionInfoContext));
378 :
379 : /* Construct tuple descriptor */
380 2 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
381 0 : ereport(ERROR,
382 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
383 : errmsg("function returning record called in context that cannot accept type record")));
384 2 : fctx->tupdesc = BlessTupleDesc(tupdesc);
385 :
386 : /* Set max_calls as a count of extensions in certificate */
387 2 : max_calls = cert != NULL ? X509_get_ext_count(cert) : 0;
388 :
389 2 : if (max_calls > 0)
390 : {
391 : /* got results, keep track of them */
392 2 : funcctx->max_calls = max_calls;
393 2 : funcctx->user_fctx = fctx;
394 : }
395 : else
396 : {
397 : /* fast track when no results */
398 0 : MemoryContextSwitchTo(oldcontext);
399 0 : SRF_RETURN_DONE(funcctx);
400 : }
401 :
402 2 : MemoryContextSwitchTo(oldcontext);
403 : }
404 :
405 : /* stuff done on every call of the function */
406 10 : funcctx = SRF_PERCALL_SETUP();
407 :
408 : /*
409 : * Initialize per-call variables.
410 : */
411 10 : call_cntr = funcctx->call_cntr;
412 10 : max_calls = funcctx->max_calls;
413 10 : fctx = funcctx->user_fctx;
414 :
415 : /* do while there are more left to send */
416 10 : 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 8 : membuf = BIO_new(BIO_s_mem());
431 8 : if (membuf == NULL)
432 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 */
437 8 : ext = X509_get_ext(cert, call_cntr);
438 8 : obj = X509_EXTENSION_get_object(ext);
439 :
440 : /* Get the extension name */
441 8 : nid = OBJ_obj2nid(obj);
442 8 : if (nid == NID_undef)
443 0 : ereport(ERROR,
444 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
445 : errmsg("unknown OpenSSL extension in certificate at position %d",
446 : call_cntr)));
447 8 : values[0] = CStringGetTextDatum(OBJ_nid2sn(nid));
448 8 : nulls[0] = false;
449 :
450 : /* Get the extension value */
451 8 : if (X509V3_EXT_print(membuf, ext, 0, 0) <= 0)
452 0 : ereport(ERROR,
453 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
454 : errmsg("could not print extension value in certificate at position %d",
455 : call_cntr)));
456 8 : len = BIO_get_mem_data(membuf, &buf);
457 8 : values[1] = PointerGetDatum(cstring_to_text_with_len(buf, len));
458 8 : nulls[1] = false;
459 :
460 : /* Get critical status */
461 8 : values[2] = BoolGetDatum(X509_EXTENSION_get_critical(ext));
462 8 : nulls[2] = false;
463 :
464 : /* Build tuple */
465 8 : tuple = heap_form_tuple(fctx->tupdesc, values, nulls);
466 8 : result = HeapTupleGetDatum(tuple);
467 :
468 8 : if (BIO_free(membuf) != 1)
469 0 : elog(ERROR, "could not free OpenSSL BIO structure");
470 :
471 8 : SRF_RETURN_NEXT(funcctx, result);
472 : }
473 :
474 : /* All done */
475 2 : SRF_RETURN_DONE(funcctx);
476 : }
|